Skip to content

packfile: close records at a byte limit - #1078

Draft
tamirms wants to merge 1 commit into
mainfrom
tamirms/packfile-byte-limit
Draft

tamirms wants to merge 1 commit into
mainfrom
tamirms/packfile-byte-limit

Conversation

@tamirms

@tamirms tamirms commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Why

A record is the unit of a read: reading any item in it reads all of it. Records closed only on their item count, so a few large items made a record large and every read of a small neighbour paid for them. In index.pack a rare term's bitmap shares a 128-item record with popular terms' bitmaps of up to a megabyte, so a lookup of the rare term read whatever its neighbours weighed: the worst small-term read was 1,062 KB on a real pubnet chunk and 5,757 KB on one folded to seven times its density. In events.pack one contract's big events inflate the reads of other contracts' events. And index.pack's new layout (a later PR in this stack) needs records bounded by bytes alone, so that every index read is one small EBS I/O.

The design

Records get a byte limit, WriterOptions.MaxRecordBytes, which closes a record before an item that would take its raw bytes past the limit; only a record holding a single item can exceed it. The item limit stays, because events.pack's 128 events come to about 32 KB raw, so its records keep closing on count and store no counts; a byte-only budget there would grow the index read per open by 55% on the largest chunk (1.25 MB instead of 0.82 MB). ItemsPerRecord 0 now means no item limit and requires a byte limit.

A record closed early holds fewer items than the limit, so the index has to say how many. A count column per group, a compressed list of the group's record counts marked by the directory's flag bit, costs 0.9 KB on a real pubnet chunk and 6.0 KB on the largest against 2.0 and 13.1 KB for a flat per-record list, and in the worst case of ten million 16 KiB events it is 206 KB decoded per open instead of 10 MB. Counts in every group would add 13 to 74 KB to every open, and a pack-wide "counts present" flag is redundant with the directory's per-group bit. The directory's first items become running totals, so lookups keep their binary search. Packs whose records all fill carry no counts and are byte-identical to before, and a reader without counts rejects a pack with them.

How it works

Create now rejects options with no limit at all, and a byte limit with one item per record; every production writer already sets an item limit. Open checks each group's flag and item span, and a read checks a group's counts when it decodes it. A record closed early keeps the shape its pack's limit sets, so it still carries its item size index and CRC. The code that assumed a positive item limit is gone with the assumption.

Measured

Over six interleaved rounds with the file in the page cache, BenchmarkReader stays within 2.4% of before and BenchmarkOpen within 2.3%. On the same records closed at 128 items by a byte limit under an item limit of 256, so that every group has counts, a ReadItem costs 0.3 µs more for the count column's decode, and ReadItems and ReadRange under 1% more. Bit-flip sweeps of 9,920 and 18,312 single-bit mutations over packs with counts found one gap, a last group that carried no counts when its predecessor did, which is fixed.

🤖 Generated with Claude Code

@tamirms
tamirms added this pull request to stack #1081 October 5, 2026 10:56
@tamirms
tamirms removed this pull request from stack #1081 October 5, 2026 16:19
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from b7a055b to 6ab0f8d Compare October 5, 2026 16:19
@tamirms
tamirms changed the base branch from tamirms/packfile-index-directory to tamirms/events-windowed-matches October 5, 2026 16:24
@tamirms
tamirms added this pull request to stack #1089 October 5, 2026 16:24
@tamirms
tamirms removed this pull request from stack #1089 October 8, 2026 08:18
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 6ab0f8d to 51104ee Compare October 8, 2026 08:18
@tamirms
tamirms added this pull request to stack #1102 October 8, 2026 08:19
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 3 times, most recently from ce2cbc5 to fb5d3d5 Compare October 9, 2026 06:02
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 2 times, most recently from f5fec6d to 45d0638 Compare October 15, 2026 19:15
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 45d0638 to 3f63c70 Compare October 19, 2026 13:00
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 2 times, most recently from 706417e to 32c9ac4 Compare October 21, 2026 09:12
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 3 times, most recently from 90ca6e0 to 7a96c85 Compare October 21, 2026 11:16
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 2 times, most recently from 165bf23 to a0cd30d Compare October 21, 2026 14:07
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from a0cd30d to faa318e Compare October 21, 2026 14:43
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from faa318e to 04e3395 Compare October 21, 2026 17:05
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 04e3395 to 1f4b96e Compare October 21, 2026 17:15
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch 7 times, most recently from 58d0466 to 7d4ee1b Compare October 24, 2026 02:49
@tamirms
tamirms removed this pull request from stack #1102 October 27, 2026 10:19
@tamirms
tamirms added this pull request to stack #1157 October 27, 2026 10:21
@tamirms tamirms moved this from To Do to In Progress in Platform Scrum Oct 27, 2026
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 7d4ee1b to f12aca8 Compare October 28, 2026 08:05
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from f12aca8 to 0277985 Compare November 4, 2026 10:07
@tamirms
tamirms removed this pull request from stack #1157 November 4, 2026 10:20
@tamirms
tamirms added this pull request to stack #1184 November 4, 2026 10:21
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 0277985 to 884b080 Compare November 4, 2026 19:03
Base automatically changed from tamirms/events-windowed-matches to main November 4, 2026 19:12
A record closed only on its item count, so a few large items made it
large, and a read of any item in it reads all of it. index.pack's large
bitmaps share 128-item records with small ones, so a lookup of a rare
term reads whatever its neighbors weigh, and events.pack's records grow
when other contracts emit big events. index.pack's planned byte-only
budget needs records limited by bytes alone.

WriterOptions.MaxRecordBytes closes a record before an item that would
take its raw bytes past the limit, so only a record of one item can
exceed it. ItemsPerRecord 0 now means no item limit and needs
MaxRecordBytes. The default of 128 for an unset ItemsPerRecord goes, and
Create rejects options with no limit, and a byte limit with one item per
record. Every production writer already sets ItemsPerRecord.

A record closed early holds fewer items than the limit, so its index
group gets a count column, a FOR group of its records' item counts
marked by the directory's flag bit, and the directory's first items
become running totals. Every group has counts when there is no item
limit, and a pack of one item per record never does. Open checks each
group's flag and item span, and a read checks a group's counts when it
decodes it. Lookups already search first items, so they need no change.
A record keeps the shape the pack's limit sets, so one closed at one
item still carries its item size index and CRC.

A pack whose records all fill is byte-identical to before, and a reader
without counts rejects a pack with them. The trailer check for an item
limit above 0 goes with its test, as do Open's reading of 0 as 1 and
the test helper writeTestPackfile, which read an unset ItemsPerRecord
as 1.

Over six interleaved rounds with the file in the page cache,
BenchmarkReader stays within 2.4% of before and BenchmarkOpen within
2.3%. On the same records closed at 128 items by a byte limit under an
item limit of 256, so every group has counts, a ReadItem costs 0.3 µs
more for the count column's decode, and ReadItems and ReadRange under 1%
more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKssDnVqUbxVZqpxPN3FpP
@tamirms
tamirms force-pushed the tamirms/packfile-byte-limit branch from 884b080 to 7d9a433 Compare November 4, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant