{"thread":{"id":"46441","subject":"Re: reftable [v3]: new ref storage format","startedAt":"2017-07-22T18:31:11Z","lastAt":"2017-07-29T02:31:57Z","messageCount":9,"participants":["Shawn Pearce","Ævar Arnfjörð Bjarmason","Stefan Beller","Michael Haggerty"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"324904","messageId":"CAJo=hJvxWg2J-yRiCK3szux=eYM2ThjT0KWo-SFFOOc1RkxXzg@mail.gmail.com","threadId":"46441","inReplyTo":null,"subject":"Re: reftable [v3]: new ref storage format","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2017-07-22T18:29:17Z","receivedAt":"2017-07-22T18:31:11Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"3rd iteration of the reftable storage format.\n\nYou can read a rendered version of this here:\nhttps://googlers.googlesource.com/sop/jgit/+/reftable/Documentation/technical/reftable.md\n\nSignificant changes from v2:\n- efficient lookup by SHA-1 for allow-tip-sha1-in-want.\n- type 0x4 for FETCH_HEAD, MERGE_HEAD.\n- file size up (27.7 M in v1, 34.4 M in v3)\n\nThe file size increase is due to lookup by SHA-1 support. By using\nunique abbreviations its adding about 7 MiB to the file size for\n865,258 objects behind 866,456 refs. Average entry for this direction\ncosts 8 bytes, using a 6 byte/12 hex unique abbreviation.\n\n\n## Overview\n\n### Problem statement\n\nSome repositories contain a lot of references (e.g.  android at 866k,\nrails at 31k).  The existing packed-refs format takes up a lot of\nspace (e.g.  62M), and does not scale with additional references.\nLookup of a single reference requires linearly scanning the file.\n\nAtomic pushes modifying multiple references require copying the\nentire packed-refs file, which can be a considerable amount of data\nmoved (e.g. 62M in, 62M out) for even small transactions (2 refs\nmodified).\n\nRepositories with many loose references occupy a large number of disk\nblocks from the local file system, as each reference is its own file\nstoring 41 bytes (and another file for the corresponding reflog).\nThis negatively affects the number of inodes available when a large\nnumber of repositories are stored on the same filesystem.  Readers can\nbe penalized due to the larger number of syscalls required to traverse\nand read the `$GIT_DIR/refs` directory.\n\n### Objectives\n\n- Near constant time lookup for any single reference, even when the\n  repository is cold and not in process or kernel cache.\n- Near constant time verification a SHA-1 is referred to by at least\n  one reference (for allow-tip-sha1-in-want).\n- Efficient lookup of an entire namespace, such as `refs/tags/`.\n- Support atomic push `O(size_of_update)` operations.\n- Combine reflog storage with ref storage.\n\n### Description\n\nA reftable file is a portable binary file format customized for\nreference storage. References are sorted, enabling linear scans,\nbinary search lookup, and range scans.\n\nStorage in the file is organized into blocks.  Prefix compression\nis used within a single block to reduce disk space.  Block size is\ntunable by the writer.\n\n### Performance\n\nSpace used, packed-refs vs. reftable:\n\nrepository | packed-refs | reftable | % original | avg ref  | avg obj\n-----------|------------:|---------:|-----------:|---------:|--------:\nandroid    |      62.2 M |   34.4 M |     55.2%  | 33 bytes | 8 bytes\nrails      |       1.8 M |    1.1 M |     57.7%  | 29 bytes | 6 bytes\ngit        |      78.7 K |   44.0 K |     60.0%  | 50 bytes | 6 bytes\ngit (heads)|       332 b |    239 b |     72.0%  | 31 bytes | 0 bytes\n\nScan (read 866k refs), by reference name lookup (single ref from 866k\nrefs), and by SHA-1 lookup (refs with that SHA-1, from 866k refs):\n\nformat      | scan    | by name        | by SHA-1\n------------|--------:|---------------:|---------------:\npacked-refs |  402 ms | 409,660.1 usec | 412,535.8 usec\nreftable    |  112 ms |      42.7 usec |     340.8 usec\n\nSpace used for 149,932 log entries for 43,061 refs,\nreflog vs. reftable:\n\nformat        | size  | avg log\n--------------|------:|-----------:\n$GIT_DIR/logs | 173 M | 1209 bytes\nreftable      |   4 M |   30 bytes\n\n## Details\n\n### Peeling\n\nReferences in a reftable are always peeled.\n\n### Reference name encoding\n\nReference names should be encoded with UTF-8.\n\n### Network byte order\n\nAll multi-byte, fixed width fields are in network byte order.\n\n### Ordering\n\nBlocks are lexicographically ordered by their first reference.\n\n### Directory/file conflicts\n\nThe reftable format accepts both `refs/heads/foo` and\n`refs/heads/foo/bar` as distinct references.\n\nThis property is useful for retaining log records in reftable, but may\nconfuse versions of Git using `$GIT_DIR/refs` directory tree to\nmaintain references.  Users of reftable may choose to continue to\nreject `foo` and `foo/bar` type conflicts to prevent problems for\npeers.\n\n## File format\n\n### Structure\n\nA reftable file has the following high-level structure:\n\n    first_block {\n      header\n      first_ref_block\n    }\n    ref_blocks*\n    ref_index?\n    obj_blocks*\n    obj_index?\n    log_blocks*\n    log_index?\n    footer\n\n### Block size\n\nThe `block_size` is arbitrarily determined by the writer, and does not\nhave to be a power of 2.  The block size must be larger than the\nlongest reference name or deflated log entry used in the repository,\nas references cannot span blocks.\n\nPowers of two that are friendly to the virtual memory system or\nfilesystem (such as 4k or 8k) are recommended.  Larger sizes (64k) can\nyield better compression, with a possible increased cost incurred by\nreaders during access.\n\nThe largest block size is `16777215` bytes (15.99 MiB).\n\n### Header\n\nAn 8-byte header appears at the beginning of the file:\n\n    '\\1REF'\n    uint8( version_number = 1 )\n    uint24( block_size )\n\n### First ref block\n\nThe first ref block shares the same block as the file header, and is 8\nbytes smaller than all other blocks in the file.  The first block\nimmediately begins after the file header, at offset 8.\n\n### Ref block format\n\nA ref block is written as:\n\n    'r'\n    uint24 ( block_len )\n    ref_record+\n    uint32( restart_offset )+\n    uint16( restart_count_m1 )\n    padding?\n\nBlocks begin with `block_type = 'r'` and a 3-byte `block_len` which\nencodes the number of bytes in the block up to, but not including the\noptional `padding`.  This is almost always shorter than the file's\n`block_size`.  In the first ref block, `block_len` includes 8 bytes\nfor the file header.\n\nThe 4-byte block header is followed by a variable number of\n`ref_record`, describing reference names and values.  The format\nis described below.\n\nA variable number of 4-byte `restart_offset` values follow the\nrecords.  Offsets are relative to the start of the block and refer to\nthe first byte of any `ref_record` whose name has not been prefix\ncompressed.  Readers can start linear scans from any of these records.\nOffsets in the first block are relative to the start of the file\n(position 0), and include the file header.  This requires the first\nrestart in the first block to be at offset 8.\n\nThe 2-byte `restart_count_m1` stores *one less* than the number of\nentries in the `restart_offset` list.  There is always a restart\ncorresponding to the first ref record. Readers are responsible for\ncomputing `restart_count = restart_count_m1 + 1`.\n\nReaders can use the restart count to binary search between restarts\nbefore starting a linear scan.  The `restart_count_m1` field must be\nthe last 2 bytes of the block as specified by `block_len` from the\nblock header.\n\nThe end of the record may be filled with `padding` NUL bytes to fill\nout the block to the common `block_size` as specified in the file\nheader.  Padding may be necessary to ensure the following block starts\nat a block alignment, and does not spill into the tail of this block.\nPadding may be omitted if the block is the last block of the file, or\nthere is no index block.  This allows reftable to efficiently scale\ndown to a small number of refs.\n\n#### ref record\n\nA `ref_record` describes a single reference, storing both the name and\nits value(s). Records are formatted as:\n\n    varint( prefix_length )\n    varint( (suffix_length << 3) | value_type )\n    suffix\n    value?\n\nThe `prefix_length` field specifies how many leading bytes of the\nprior reference record's name should be copied to obtain this\nreference's name.  This must be 0 for the first reference in any\nblock, and also must be 0 for any `ref_record` whose offset is listed\nin the `restart_offset` table at the end of the block.\n\nRecovering a reference name from any `ref_record` is a simple concat:\n\n    this_name = prior_name[0..prefix_length] + suffix\n\nThe `suffix_length` value provides the number of bytes to copy from\n`suffix` to complete the reference name.\n\nThe `value` follows.  Its format is determined by `value_type`, one of\nthe following:\n\n- `0x0`: deletion; no value data (see transactions, below)\n- `0x1`: one 20-byte object id; value of the ref\n- `0x2`: two 20-byte object ids; value of the ref, peeled target\n- `0x3`: symbolic reference: `varint( target_len ) target`\n- `0x4`: length delimited extension: `varint( data_len ) data`\n\nSymbolic references use a varint length followed by a variable number\nof bytes to encode the complete reference target.  No compression is\napplied to the target name.\n\nType `0x4` is available for use by systems that need to store\nadditional data under a reference name like `FETCH_HEAD` or\n`MERGE_HEAD`.\n\nTypes `0x5..0x7` are reserved.\n\n### Ref index\n\nThe ref index stores the name of the last reference from every ref\nblock in the file, enabling constant O(1) disk seeks for all lookups.\nAny reference can be found by binary searching the index, identifying\nthe containing block, and searching within that block.\n\nIf present, the ref index block appears after the last ref block.  The\nprior ref block should be padded to ensure the ref index starts on a\nblock alignment.\n\nAn index block should only be written if there are at least 4 blocks\nin the file, as cold reads using the index requires 2 disk reads, and\nbinary searching <= 4 blocks also requires <= 2 reads.  Omitting the\nindex block from smaller files saves space.\n\nIndex block format:\n\n    uint32( (0x80 << 24) | block_len )\n    index_record+\n    uint32( restart_offset )+\n    uint16( restart_count_m1 )\n    padding?\n\nThe index block header starts with the high bit set.  This identifies\nthe block as an index block, and not as a ref block, log block or file\nfooter.  The `block_len` field in an index block is 30-bits network\nbyte order, and allowed to occupy space normally used by the block\ntype in other blocks.  This supports indexes significantly larger than\nthe file's `block_size`.\n\nThe `restart_offset` and `restart_count_m1` fields are identical in\nformat, meaning and usage as in ref blocks.\n\nTo reduce the number of reads required for random access in very large\nfiles, the index block may be larger than the other blocks.  However,\nreaders must hold the entire index in memory to benefit from this, so\nit's a time-space tradeoff in both file size, and reader memory.\nIncreasing the block size in the writer decreases the index size.\n\nWhen object blocks are present the ref index block is padded with\n`padding` to maintain alignment for the next block. No padding is\nnecessary if log blocks or the file trailer follows the ref index.\n\n#### index record\n\nAn index record describes the last entry in another block.\nIndex records are written as:\n\n    varint( prefix_length )\n    varint( (suffix_length << 3) | 0 )\n    suffix\n    varint( block_offset )\n\nIndex records use prefix compression exactly like `ref_record`.\n\nIndex records store `block_offset` after the suffix, specifying the\noffset in bytes (from the start of the file) of the block that ends\nwith this reference.\n\n#### Reading the index\n\nReaders loading the ref index must first read the footer (below) to\nobtain `ref_index_offset`. If not present, the offset will be 0.\n\n### Obj block format\n\nObject blocks use unique, abbreviated 2-20 byte SHA-1s keys, mapping\nto ref blocks containing references pointing to that object directly,\nor as the peeled value of an annotated tag.  Like ref blocks, object\nblocks use the file's standard `block_size`.\n\nTo save space in small files, object blocks may be omitted if the ref\nindex is not present.  When missing readers should brute force a\nlinear search of all references to lookup by SHA-1.\n\nAn object block is written as:\n\n    'o'\n    uint24 ( block_len )\n    obj_record+\n    uint32( restart_offset )+\n    uint16( restart_count_m1 )\n    padding?\n\nFields are identical to ref block.  Binary search using the restart\ntable works the same as in reference blocks.\n\nBecause object identifiers are abbreviated by writers to the shortest\nunique abbreviation within the reftable, obj key lengths are variable\nbetween 2 and 20 bytes.  Readers must compare only for common prefix\nmatch within an obj block or obj index.\n\nObject blocks should be block aligned, according to `block_size` from\nthe file header.  The `padding` field is filled with NULs to maintain\nalignment for the next block.\n\n#### obj record\n\nAn `obj_record` describes a single object abbreviation, and the blocks\ncontaining references using that unique abbreviation:\n\n    varint( prefix_length )\n    varint( (suffix_length << 3) | cnt_3 )\n    suffix\n    varint( cnt_rest )?\n    varint( block_delta )+\n\nLike in reference blocks, abbreviations are prefix compressed within\nan obj block.  On large reftable files with many unique objects,\nhigher block sizes (64k), and higher restart interval (128), a\n`prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in\nobj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits).\n\nEach record contains `block_count` number of block identifiers for ref\nblocks.  The `block_count` is determined by:\n\n    block_count = cnt_3\n    if (cnt_3 == 0x7) {\n      block_count += cnt_rest\n    }\n\nThe `cnt_rest` field is only present when `block_count >= 0x7` and\ncould overflow the `cnt_3` field available in the record start.  This\nencoding scheme is used as the vast majority of abbreviations are\nonly one reference (or at most a few), and unlikely to exceed 6 blocks.\n\nThe first `block_delta` is the absolute block identifier counting from\nthe start of the file. The offset of that block can be obtained by\n`block_delta[0] * block_size`.  Additional `block_delta` entries are\nrelative to the prior entry, e.g. a reader would perform:\n\n    block_id = block_delta[0]\n    prior = block_id\n    for (j = 1; j < block_count; j++) {\n      block_id = prior + block_delta[j]\n      prior = block_id\n    }\n\nWith a `block_id` in hand, a reader must linearly scan the ref block\nat `block_id * block_size` offset in the file, starting from the first\n`ref_record`, testing each reference's SHA-1s (for `value_type = 0x1`\nor `0x2`) for full equality.  Faster searching by SHA-1 within a\nsingle ref block is not supported by the reftable format.  Smaller\nblock sizes reduces the number of candidates this step must consider.\n\n### Obj index\n\nThe obj index stores the abbreviation from the last entry for every\nobj block in the file, enabling constant O(1) disk seeks for all\nlookups.  It is formatted exactly the same as the ref index, but\nrefers to obj blocks.\n\nThe obj index should be present if obj blocks are present, as\nobj blocks should only be written in larger files.\n\nThe obj index should be block aligned, according to `block_size` from\nthe file header.  This requires padding the last obj block to maintain\nalignment.\n\nReaders loading the obj index must first read the footer (below) to\nobtain `obj_index_offset`.  If not present, the offset will be 0.\n\n### Log block format\n\nUnlike ref and obj blocks, log block sizes are variable in size, and\ndo not match the `block_size` specified in the file header or footer.\nWriters should choose an appropriate buffer size to prepare a log block\nfor deflation, such as `2 * block_size`.\n\nA log block is written as:\n\n    'g'\n    uint24( block_len )\n    zlib_deflate {\n      log_record+\n      int32( restart_offset )+\n      int16( restart_count_m1 )\n    }\n\nLog blocks look similar to ref blocks, except `block_type = 'g'`.\n\nThe 4-block header is followed by the deflated block contents using\nzlib deflate.  The `block_len` in the header is the inflated size\n(including 4-byte block header), and should be used by readers to\npreallocate the inflation output buffer.  Offsets within the block\n(e.g. `restart_offset`) still include the 4-byte header.  Readers may\nprefer prefixing the inflation output buffer with the 4-byte header.\n\nWithin the deflate container, a variable number of `log_record`\ndescribe reference changes.  The log record format is described\nbelow.  See ref block format (above) for a description of\n`restart_offset` and `restart_count_m1`.\n\nUnlike ref blocks, log blocks are written at any alignment, without\npadding.  The first log block immediately follows the end of the prior\nblock, which omits its trailing padding.  In very small files the log\nblock may appear in the first block.\n\nBecause log blocks have no alignment or padding between blocks,\nreaders must keep track of the bytes consumed by the inflater to\nknow where the next log block begins.\n\n#### log record\n\nLog record keys are structured as:\n\n    ref_name '\\0' reverse_int64( time_usec )\n\nwhere `time_usec` is the update time in microseconds since the epoch.\nThe `reverse_int64` function inverses the value so lexographical\nordering the network byte order time sorts more recent records first:\n\n    reverse_int(int64 t) {\n      return 0xffffffffffffffff - t;\n    }\n\nThe `time_usec` field must be unique within the scope of a `ref_name`.\nWriters working from seconds precision source are recomended to add\n`999999` microseconds to the timestamp, and decrement microseconds\nfrom older entries within the same second to prevent duplicates.\nTruncating to seconds on read will restore the original values.\n\nLog records have a similar starting structure to ref and index\nrecords, utilizing the same prefix compression scheme applied to the\nkey described above.\n\n```\n    varint( prefix_length )\n    varint( (suffix_length << 3) | 0 )\n    suffix\n\n    old_id\n    new_id\n    sint16( tz_offset )\n    varint( name_length  )   name\n    varint( email_length )   email\n    varint( message_length ) message\n```\n\nThe value data following the key suffix is complex:\n\n- two 20-byte SHA-1s (old id, new id)\n- 2-byte timezone offset (signed)\n- varint string of committer's name\n- varint string of committer's email\n- varint string of message\n\n`tz_offset` is the absolute number of minutes from GMT the committer\nwas at the time of the update.  For example `GMT-0800` is encoded in\nreftable as `int16(-480)` and `GMT+0230` is `int16(150)`.\n\nThe `message_length` may be 0, in which case there was no message\nsupplied for the update.\n\n#### Reading the log\n\nReaders accessing the log must first read the footer (below) to\ndetermine the `log_offset`.  The first block of the log begins at\n`log_offset` bytes since the start of the file.  The `log_offset` is\nnot block aligned.\n\n### Log index\n\nThe log index stores the log key (`refname \\0 reverse_int32(time_sec)`)\nfor the last log record of every log block in the file, supporting\nbounded-time lookup.\n\nA log index block must be written if 2 or more log blocks are written\nto the file.  If present, the log index appears after the first log\nblock.  There is no padding used to align the log index to block\nalignment.\n\nLog index format is identical to ref index, except the keys are 5\nbytes longer to include `'\\0'` and the 4-byte `reverse_int32(time)`.\nRecords use `block_offset` to refer to the start of a log block.\n\n#### Reading the index\n\nReaders loading the log index must first read the footer (below) to\nobtain `log_index_offset`. If not present, the offset will be 0.\n\n### Footer\n\nAfter the last block of the file, a file footer is written.  It begins\nlike the file header, but is extended with additional data.\n\nA 52-byte footer appears at the end:\n\n```\n    '\\1REF'\n    uint8( version_number = 1 )\n    uint24( block_size )\n    uint64( ref_index_offset )\n\n    uint64( obj_offset )\n    uint64( obj_index_offset )\n\n    uint64( log_offset )\n    uint64( log_index_offset )\n\n    uint32( CRC-32 of prior )\n```\n\nIf a section is missing (e.g. ref index) the corresponding offset\nfield (e.g. `ref_index_offset`) will be 0.\n\n- `obj_offset`: byte offset for the first obj block.\n- `log_offset`: byte offset for the first log block.\n- `ref_index_offset`: byte offset for the start of the ref index.\n- `obj_index_offset`: byte offset for the start of the obj index.\n- `log_index_offset`: byte offset for the start of the log index.\n\n#### Reading the footer\n\nReaders must seek to `file_length - 64` to access the footer.  A\ntrusted external source (such as `stat(2)`) is necessary to obtain\n`file_length`.  When reading the footer, readers must verify:\n\n- 4-byte magic is correct\n- 1-byte version number is recognized\n- 4-byte CRC-32 matches the other 48 bytes (including magic, and version)\n\nOnce verified, the other fields of the footer can be accessed.\n\n### Varint encoding\n\nVarint encoding is identical to the ofs-delta encoding method used\nwithin pack files.\n\nDecoder works such as:\n\n    val = buf[ptr] & 0x7f\n    while (buf[ptr] & 0x80) {\n      ptr++\n      val++\n      val = val << 7\n      val = val | (buf[ptr] & 0x7f)\n    }\n\n### Binary search\n\nBinary search within a block is supported by the `restart_offset`\nfields at the end of the block.  Readers can binary search through the\nrestart table to locate between which two restart points the sought\nreference or key should appear.\n\nEach record identified by a `restart_offset` stores the complete key\nin the `suffix` field of the record, making the compare operation\nduring binary search straightforward.\n\nOnce a restart point lexicographically before the sought reference has\nbeen identified, readers can linearly scan through the following\nrecord entries to locate the sought record, terminating if the current\nrecord sorts after (and therefore the sought key is not present).\n\n#### Restart point selection\n\nWriters determine the restart points at file creation.  The process is\narbitrary, but every 16 or 64 records is recommended.  Every 16 may\nbe more suitable for smaller block sizes (4k or 8k), every 64 for\nlarger block sizes (64k).\n\nMore frequent restart points reduces prefix compression and increases\nspace consumed by the restart table, both of which increase file size.\n\nLess frequent restart points makes prefix compression more effective,\ndecreasing overall file size, with increased penalities for readers\nwalking through more records after the binary search step.\n\nA maximum of `65536` restart points per block is supported.  The\n`restart_count` field stores restart points `-1`, with a maximum\nvalue of `65535`.\n\n## Considerations\n\n### Lightweight refs dominate\n\nThe reftable format assumes the vast majority of references are single\nSHA-1 valued with common prefixes, such as Gerrit Code Review's\n`refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many\nlightweight tags in the `refs/tags/` namespace.\n\nAnnotated tags storing the peeled object cost only an additional 20\nbytes per reference.\n\n### Low overhead\n\nA reftable with very few references (e.g. git.git with 5 heads)\nis 251 bytes for reftable, vs. 332 bytes for packed-refs.  This\nsupports reftable scaling down for transaction logs (below).\n\n### Block size\n\nFor a Gerrit Code Review type repository with many change refs, larger\nblock sizes (64 KiB) and less frequent restart points (every 64) yield\nbetter compression due to more references within the block compressing\nagainst the prior reference.\n\nLarger block sizes reduces the index size, as the reftable will\nrequire fewer blocks to store the same number of references.\n\n### Minimal disk seeks\n\nAssuming the index block has been loaded into memory, binary searching\nfor any single reference requires exactly 1 disk seek to load the\ncontaining block.\n\n### Scans and lookups dominate\n\nScanning all references and lookup by name (or namespace such as\n`refs/heads/`) is the most common activity performed by repositories.\nSHA-1s are stored twice when obj blocks are present, avoiding disk\nseeks for the common cases of scan and lookup by name.\n\n### Logs are infrequently read\n\nLogs are infrequently accessed, but can be large.  Deflating log\nblocks saves disk space, with some increased penalty at read time.\n\nLogs are stored in an isolated section from refs, reducing the burden\non reference readers that want to ignore logs.\n\n### Logs are read backwards\n\nLogs are frequently accessed backwards (most recent N records for master),\nso log records are grouped by reference, and sorted descending by time.\n\n## Repository format\n\n### Version 1\n\nA repository must set its `$GIT_DIR/config` to configure reftable:\n\n    [core]\n        repositoryformatversion = 1\n    [extensions]\n        reftable = 1\n\n### Layout\n\nThe `$GIT_DIR/refs` path is a file when reftable is configured, not a\ndirectory.  This prevents loose references from being stored.\n\nA collection of reftable files are stored in the `$GIT_DIR/reftable/`\ndirectory:\n\n    1500398516_UF4paF\n    1500398517_bUVgy4\n\nwhere reftable files are named by a unique name such as produced by\nthe function:\n\n    mktemp \"${seconds_since_epoch}_XXXXXX\"\n\nThe stack ordering file is `$GIT_DIR/refs` and lists the current\nfiles, one per line, in order, from oldest (base) to newest (most\nrecent):\n\n    $ cat .git/refs\n    1500398516_UF4paF\n    1500398517_bUVgy4\n\nReaders must read `$GIT_DIR/refs` to determine which files are\nrelevant right now, and search through the stack in reverse order\n(last reftable is examined first).\n\nReftable files not listed in `refs` may be new (and about to be added\nto the stack by the active writer), or ancient and ready to be pruned.\n\n### Update transactions\n\nAlthough reftables are immutable, mutations are supported by writing a\nnew reftable and atomically appending it to the stack:\n\n1. Read `refs` to determine current reftables.\n2. Prepare new reftable `${time}_XXXXXX`, including log entries.\n3. Acquire `refs.lock`.\n4. If `refs` differs from (1), verify file from (2) does not conflict.\n5. Copy `refs` to `refs.lock`, appending file from (2).\n6. Rename `refs.lock` to `refs`.\n\nBecause a single `refs.lock` file is used to manage locking, the\nrepository is single-threaded for writers.  Writers may have to\nbusy-spin (with some small backoff) around creating `refs.lock`,\nfor up to an acceptable wait period, aborting if the repository is too\nbusy to mutate.  Application servers wrapped around repositories (e.g.\nGerrit Code Review) can layer their own in memory thread lock/wait\nqueue to improve fairness.\n\nBy preparing the update in steps 1-2 without the lock held, concurrent\nupdaters may be able to update unrelated references in a safe way,\nwith a minimal critical section.  This requires updaters to verify in\n4 that the stack has not changed in a meaningful way, and to\ngracefully abort by deleting `${time}_XXXXXX` if a conflict is\ndetected.\n\n### Reference deletions\n\nDeletion of any reference can be explicitly stored by setting the\n`type` to `0x0` and omitting the `value` field of the `ref_record`.\nThis entry shadows the reference in earlier files in the stack.\n\n### Compaction\n\nA partial stack of reftables can be compacted by merging references\nusing a straightforward merge join across reftables, selecting the\nmost recent value for output, and omitting deleted references that do\nnot appear in remaining, lower reftables.\n\nFor sake of illustration, assume the stack currently consists of\nreftable files (from oldest to newest): A, B, C, and D. The compactor\nis going to compact B and C, leaving A and D alone.\n\n1.  Obtain lock `refs.lock` and read the `refs` file.\n2.  Obtain locks `B.lock` and `C.lock`.\n    Ownership of these locks prevents other processes from trying\n    to compact these files.\n3.  Release `refs.lock`.\n4.  Compact `B` and `C` into a new file `${time}_XXXXXX`, where `${time}`\n    is the latest time prefix, likely from `C`.\n5.  Reacquire lock `refs.lock`.\n6.  Verify that `B` and `C` are still in the stack, in that order. This\n    should always be the case, assuming that other processes are adhering\n    to the locking protocol.\n7.  Write the new stack to `refs.lock`, replacing `B` and `C` with the\n    file from (4).\n8.  Rename `refs.lock` to `refs`.\n9.  Delete `B` and `C`, perhaps after a short sleep to avoid forcing\n    readers to backtrack.\n\nThis strategy permits compactions to proceed independently of updates.\n\n## Alternatives considered\n\n### bzip packed-refs\n\n`bzip2` can significantly shrink a large packed-refs file (e.g. 62\nMiB compresses to 23 MiB, 37%).  However the bzip format does not support\nrandom access to a single reference. Readers must inflate and discard\nwhile performing a linear scan.\n\nBreaking packed-refs into chunks (individually compressing each chunk)\nwould reduce the amount of data a reader must inflate, but still\nleaves the problem of indexing chunks to support readers efficiently\nlocating the correct chunk.\n\nGiven the compression achieved by reftable's encoding, it does not\nseem necessary to add the complexity of bzip/gzip/zlib.\n\n### JGit Ketch RefTree\n\n[JGit Ketch][ketch] proposed [RefTree][reftree], an encoding of\nreferences inside Git tree objects stored as part of the repository's\nobject database.\n\nThe RefTree format adds additional load on the object database storage\nlayer (more loose objects, more objects in packs), and relies heavily\non the packer's delta compression to save space.  Namespaces which are\nflat (e.g.  thousands of tags in refs/tags) initially create very\nlarge loose objects, and so RefTree does not address the problem of\ncopying many references to modify a handful.\n\nFlat namespaces are not efficiently searchable in RefTree, as tree\nobjects in canonical formatting cannot be binary searched. This fails\nthe need to handle a large number of references in a single namespace,\nsuch as GitHub's `refs/pulls`, or a project with many tags.\n\n[ketch]: https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03073.html\n[reftree]: https://public-inbox.org/git/CAJo=hJvnAPNAdDcAAwAvU9C4RVeQdoS3Ev9WTguHx4fD0V_nOg@mail.gmail.com/\n\n### LMDB\n\nDavid Turner proposed [using LMDB][dt-lmdb], as LMDB is lightweight\n(64k of runtime code) and GPL-compatible license.\n\nA downside of LMDB is its reliance on a single C implementation.  This\nmakes embedding inside JGit (a popular reimplemenation of Git)\ndifficult, and hoisting onto virtual storage (for JGit DFS) virtually\nimpossible.\n\nA common format that can be supported by all major Git implementations\n(git-core, JGit, libgit2) is strongly preferred.\n\n[dt-lmdb]: https://public-inbox.org/git/1455772670-21142-26-git-send-email-dturner@twopensource.com/\n\n## Future\n\n### Longer hashes\n\nVersion will bump (e.g.  2) to indicate `value` uses a different\nobject id length other than 20.  The length could be stored in an\nexpanded file header, or hardcoded as part of the version.\n"},{"id":"324919","messageId":"CACBZZX4RwAmdQFeLCNG4oYLrQjafw2g2PBy6-Jn_LLp4t6hXsA@mail.gmail.com","threadId":"46441","inReplyTo":"CAJo=hJvxWg2J-yRiCK3szux=eYM2ThjT0KWo-SFFOOc1RkxXzg@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2017-07-23T20:32:11Z","receivedAt":"2017-07-23T20:32:39Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Sat, Jul 22, 2017 at 8:29 PM, Shawn Pearce <spearce@spearce.org> wrote:\n> 3rd iteration of the reftable storage format.\n>\n> You can read a rendered version of this here:\n> https://googlers.googlesource.com/sop/jgit/+/reftable/Documentation/technical/reftable.md\n>\n> Significant changes from v2:\n> - efficient lookup by SHA-1 for allow-tip-sha1-in-want.\n> - type 0x4 for FETCH_HEAD, MERGE_HEAD.\n> - file size up (27.7 M in v1, 34.4 M in v3)\n\nI had some feedback on v2 here which still applies:\nhttps://public-inbox.org/git/87k234tti7.fsf@gmail.com/\n\nIt would be good to either get a reply to that, or if you don't think\nit's sensible for whatever reason and left it out of v3 have a\n\"feedback received but discarded for <reason>\" in these summaries as\nyou're sending new versions.\n\nAside from the mail I sent I think that would be very useful in\ngeneral if there's been any other such feedback (I honestly don't know\nif there has, I haven't been following this actively).\n"},{"id":"325011","messageId":"CAGZ79kZ48zTsHxrW-fvHRFQih=vBuywExSxA8a_=wzjdJPbtrg@mail.gmail.com","threadId":"46441","inReplyTo":"CAJo=hJvxWg2J-yRiCK3szux=eYM2ThjT0KWo-SFFOOc1RkxXzg@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-07-24T22:22:28Z","receivedAt":"2017-07-24T22:22:35Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sat, Jul 22, 2017 at 11:29 AM, Shawn Pearce <spearce@spearce.org> wrote:\n> 3rd iteration of the reftable storage format.\n>\n> You can read a rendered version of this here:\n> https://googlers.googlesource.com/sop/jgit/+/reftable/Documentation/technical/reftable.md\n>\n> Significant changes from v2:\n> - efficient lookup by SHA-1 for allow-tip-sha1-in-want.\n\nI'll focus on that in the review, it sounds exciting.\n\n> - type 0x4 for FETCH_HEAD, MERGE_HEAD.\n\nSo we'd have\n    varint( (suffix_length << 3) | value_type )\nwhich leaves us with 5 bits for the varint. One bit is needed\nto indicate if ew have more bytes coming in the varint, so\nonly 4 bits in the first byte to encode the suffix length.\nThat means we can have suffixes up to 15 and we'd still be fine\nwith just one byte IIUC. That should be enough, specifically for\ncontiguous numbers as ref names.\n\n> ### Block size\n>\n> The `block_size` is arbitrarily determined by the writer, and does not\n> have to be a power of 2.  The block size must be larger than the\n> longest reference name or deflated log entry used in the repository,\n> as references cannot span blocks.\n>\n> Powers of two that are friendly to the virtual memory system or\n> filesystem (such as 4k or 8k) are recommended.  Larger sizes (64k) can\n> yield better compression, with a possible increased cost incurred by\n> readers during access.\n>\n> The largest block size is `16777215` bytes (15.99 MiB).\n\nThanks for calling this out, 16MB ought to be enough for everyone.\n(Not a joke, as we can have multiple blocks)\n\n> ### Ref block format\n...\n\n> A variable number of 4-byte `restart_offset` values follow the\n> records.  Offsets are relative to the start of the block and refer to\n> the first byte of any `ref_record` whose name has not been prefix\n> compressed.  Readers can start linear scans from any of these records.\n> Offsets in the first block are relative to the start of the file\n> (position 0), and include the file header.  This requires the first\n> restart in the first block to be at offset 8.\n>\n> The 2-byte `restart_count_m1` stores *one less* than the number of\n> entries in the `restart_offset` list.  There is always a restart\n> corresponding to the first ref record. Readers are responsible for\n> computing `restart_count = restart_count_m1 + 1`.\n\nI had to reread these two paragraphs a couple of times as it calls out\nthe uninteresting things[1] and the interesting part is the logical\nconclusion that one has to make themselves,\nhere is how I would write it:\n\n    Readers can start linear scans from any record whose name has\n    not been prefix compressed. The first record of a block must not\n    be prefix-compressed.\n\n    To aid finding the entry points for linear scans, a variable number\n    of 4-byte `restart_offset` values follow the records. Offsets are\n    relative to the start of the block and refer to the first byte of any\n    such `ref_record` that is not prefix compressed.\n    The first record can be omitted in the `restart_offset` values as it\n    is implicit.\n\n    The `restart_offset' values must be sorted (ascending,\n    descending?).\n\n    The 2-byte `restart_count_m1` stores the number of optional\n    entry points, i.e. the number of values in `restart_offset'.\n    As the first record is omitted in the offset table, there is at\n    least one entry at 0. the `restart_count_m1` can be zero for\n    best compression.\n\n[1] The last paragraph in my proposal sounds less like an\noff-by-one error to me, but just states what the number is and how\nit relates to the surroundings in the file format.\n\n...\n\n> The `value` follows.  Its format is determined by `value_type`, one of\n> the following:\n>\n> - `0x0`: deletion; no value data (see transactions, below)\n> - `0x1`: one 20-byte object id; value of the ref\n> - `0x2`: two 20-byte object ids; value of the ref, peeled target\n\nUp to here it is easy to spot a pattern:\nThe number indicates how many oids follow.\n\n> - `0x3`: symbolic reference: `varint( target_len ) target`\n> - `0x4`: length delimited extension: `varint( data_len ) data`\n\nThis breaks the pattern.\n\nInstead of hardcoding the numbers here, I wonder if we rather\nwant to make the bits more meaningful:\n\n  bit 0, 1: number of oids iff bit 2 unset\n\n  Iff bit 2 set, then we have a \"varint (len) data\"\n  that follows, bits 0,1 are used for a different purpose,\n  00 indicates 'symlink' and data is the string\n  01 indicates 'multihead' such as FETCH_HEAD\n  1* is reserved for now.\n\nThis *may* be neat micro optimization, but hardcoding all bits\nto a lookup table is fine, too.\n\n  Note that symrefs and multiheads could share the same\n  type, iff we had dissallowed '\\n' in refnames. (we do?\n  otherwise FETCH_HEAD would be broken)\n  The differentiator would be the '\\n' or '\\0' at the end of the\n  first target.\n\n> Types `0x5..0x7` are reserved.\n\nThe proposal above would make 0x3, 0x6, 0x7 be reserved.\n\n> ### Ref index\n...\n>\n> An index block should only be written if there are at least 4 blocks\n> in the file, as cold reads using the index requires 2 disk reads, and\n> binary searching <= 4 blocks also requires <= 2 reads.\n\nThis is the bare minimum to be recommended. Fine with me, but we'd\nwant to tune it eventually. (I imagine for 5 blocks in the file it may still be\ncheaper to not have the index block)\n\n> #### index record\n>\n> An index record describes the last entry in another block.\n> Index records are written as:\n>\n>     varint( prefix_length )\n>     varint( (suffix_length << 3) | 0 )\n>     suffix\n>     varint( block_offset )\n>\n> Index records use prefix compression exactly like `ref_record`.\n>\n> Index records store `block_offset` after the suffix, specifying the\n> offset in bytes (from the start of the file) of the block that ends\n> with this reference.\n\nInstead of hardcoding the \"0\" in the last 3 bits, maybe pick one\nof the reserved bit patterns to be there? I would imagine this\nmakes debugging easier:\n\n    0x5? Hah that must be an index block I have been\n    looking at the wrong block!\n\nBy picking a reserved bit pattern from above you can tell\nthe types apart. (No, it is not a deletion of a ref!)\n\n> ### Obj block format\n>\n> Object blocks use unique, abbreviated 2-20 byte SHA-1s keys, mapping\n> to ref blocks containing references pointing to that object directly,\n> or as the peeled value of an annotated tag.  Like ref blocks, object\n> blocks use the file's standard `block_size`.\n>\n> To save space in small files, object blocks may be omitted if the ref\n> index is not present.  When missing readers should brute force a\n> linear search of all references to lookup by SHA-1.\n>\n> An object block is written as:\n>\n>     'o'\n>     uint24 ( block_len )\n>     obj_record+\n>     uint32( restart_offset )+\n>     uint16( restart_count_m1 )\n>     padding?\n>\n> Fields are identical to ref block.  Binary search using the restart\n> table works the same as in reference blocks.\n>\n> Because object identifiers are abbreviated by writers to the shortest\n> unique abbreviation within the reftable, obj key lengths are variable\n> between 2 and 20 bytes.  Readers must compare only for common prefix\n> match within an obj block or obj index.\n>\n> Object blocks should be block aligned, according to `block_size` from\n> the file header.  The `padding` field is filled with NULs to maintain\n> alignment for the next block.\n>\n> #### obj record\n>\n> An `obj_record` describes a single object abbreviation, and the blocks\n> containing references using that unique abbreviation:\n>\n>     varint( prefix_length )\n>     varint( (suffix_length << 3) | cnt_3 )\n>     suffix\n>     varint( cnt_rest )?\n>     varint( block_delta )+\n>\n> Like in reference blocks, abbreviations are prefix compressed within\n> an obj block.  On large reftable files with many unique objects,\n> higher block sizes (64k), and higher restart interval (128), a\n> `prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in\n> obj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits).\n>\n> Each record contains `block_count` number of block identifiers for ref\n> blocks.  The `block_count` is determined by:\n>\n>     block_count = cnt_3\n>     if (cnt_3 == 0x7) {\n>       block_count += cnt_rest\n>     }\n\nInstead of having the first seven blocks treated special (omission of\ncnt_rest), we could also pick one of the reserved bit patterns here to\naid debuggers, as a downside we'd require cnt_rest to be present at\nall times.\n\n> The `cnt_rest` field is only present when `block_count >= 0x7` and\n> could overflow the `cnt_3` field available in the record start.  This\n> encoding scheme is used as the vast majority of abbreviations are\n> only one reference (or at most a few), and unlikely to exceed 6 blocks.\n\nok, I misunderstood what the cnt/block_count was aiming to tell, it\nis very likely to have fewer than 7 here even for very large reftables.\n\n> The first `block_delta` is the absolute block identifier counting from\n> the start of the file. The offset of that block can be obtained by\n> `block_delta[0] * block_size`.  Additional `block_delta` entries are\n> relative to the prior entry, e.g. a reader would perform:\n>\n>     block_id = block_delta[0]\n>     prior = block_id\n>     for (j = 1; j < block_count; j++) {\n>       block_id = prior + block_delta[j]\n>       prior = block_id\n>     }\n>\n> With a `block_id` in hand, a reader must linearly scan the ref block\n> at `block_id * block_size` offset in the file, starting from the first\n> `ref_record`, testing each reference's SHA-1s (for `value_type = 0x1`\n> or `0x2`) for full equality.\n\nSo this fails to detect sha1s in FETCH_HEAD, okay.\n\n> Faster searching by SHA-1 within a\n> single ref block is not supported by the reftable format.  Smaller\n> block sizes reduces the number of candidates this step must consider.\n\nBy having block_delta as deltas of each other, we enforce an ordering\n(well, varint could be negative, but who does that?)\n\nAdditionally we could give an offset to a restart block to start searching\nfrom inside a ref block.\n\nCoupled with the idea from above of having a reserved bit pattern, an\nalternative design could look like:\n\n    varint( prefix_length )\n    varint( (suffix_length << 3) | special_bit_pattern )\n    suffix\n    varint( cnt_rest )\n    (varint( block_delta ), varint( restart_offset ))+\n\n>\n> #### log record\n>\n> Log record keys are structured as:\n>\n>     ref_name '\\0' reverse_int64( time_usec )\n\nrepeating from v2:\n\n  The size of the integer\n  is determined by the suffix length encoding and the preceding '\\0',\n  such that the file format allows arbitrary integer size. So instead of\n  pretending we can only do 64 bit here, just say 'reverse_int' ?\n\n\n>\n> ### Log index\n>\n> The log index stores the log key (`refname \\0 reverse_int32(time_sec)`)\n\nint64 now?\n\n\n> Log index format is identical to ref index, except the keys are 5\n> bytes longer to include `'\\0'` and the 4-byte `reverse_int32(time)`.\n> Records use `block_offset` to refer to the start of a log block.\n\nalso 64? /me is confused.\n\n> ### Footer\n>\n> After the last block of the file, a file footer is written.  It begins\n> like the file header, but is extended with additional data.\n>\n> A 52-byte footer appears at the end:\n>\n> ```\n>     '\\1REF'\n>     uint8( version_number = 1 )\n>     uint24( block_size )\n>     uint64( ref_index_offset )\n>\n>     uint64( obj_offset )\n>     uint64( obj_index_offset )\n>\n>     uint64( log_offset )\n>     uint64( log_index_offset )\n>\n>     uint32( CRC-32 of prior )\n> ```\n>\n> If a section is missing (e.g. ref index) the corresponding offset\n> field (e.g. `ref_index_offset`) will be 0.\n>\n> - `obj_offset`: byte offset for the first obj block.\n> - `log_offset`: byte offset for the first log block.\n> - `ref_index_offset`: byte offset for the start of the ref index.\n> - `obj_index_offset`: byte offset for the start of the obj index.\n> - `log_index_offset`: byte offset for the start of the log index.\n\nBy the design of the footer, refs must come first.\nWhether objects or logs come thereafter can both be encoded here,\nhowever the initial design claims an order.\n\n> ## Repository format\n>\n> ### Version 1\n>\n> A repository must set its `$GIT_DIR/config` to configure reftable:\n>\n>     [core]\n>         repositoryformatversion = 1\n>     [extensions]\n>         reftable = 1\n>\n> ### Layout\n>\n> The `$GIT_DIR/refs` path is a file when reftable is configured, not a\n> directory.  This prevents loose references from being stored.\n\nThat is cool.\n\n> Reftable files not listed in `refs` may be new (and about to be added\n> to the stack by the active writer), or ancient and ready to be pruned.\n\nTo prevent confusion between the two in 2038, we may rather want\nto use ISO_8601 instead of seconds since the epoch?\n(\"2017-07-25-XXXXXX\")\n\n> ### Compaction\n...\n> This strategy permits compactions to proceed independently of updates.\n\nNot just that, but D+E can also be compacted at the same time.\nThat implies an unmaintained repo with *lots* of reftables can be\ncompacted in O(n log n) compactions.\n\nThanks for a pleasant read,\nStefan\n"},{"id":"325016","messageId":"CAJo=hJuCXFem8saH9kgBu1ROV3uuhRxHcHXz_Bp7xB+taW5S=Q@mail.gmail.com","threadId":"46441","inReplyTo":"CAGZ79kZ48zTsHxrW-fvHRFQih=vBuywExSxA8a_=wzjdJPbtrg@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2017-07-24T23:00:29Z","receivedAt":"2017-07-24T23:00:55Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Mon, Jul 24, 2017 at 3:22 PM, Stefan Beller <sbeller@google.com> wrote:\n> On Sat, Jul 22, 2017 at 11:29 AM, Shawn Pearce <spearce@spearce.org> wrote:\n>> 3rd iteration of the reftable storage format.\n>>\n>> You can read a rendered version of this here:\n>> https://googlers.googlesource.com/sop/jgit/+/reftable/Documentation/technical/reftable.md\n>>\n>> Significant changes from v2:\n>> - efficient lookup by SHA-1 for allow-tip-sha1-in-want.\n>\n> I'll focus on that in the review, it sounds exciting.\n\n\n>> ### Ref block format\n> ...\n>\n>> A variable number of 4-byte `restart_offset` values follow the\n>> records.  Offsets are relative to the start of the block and refer to\n>> the first byte of any `ref_record` whose name has not been prefix\n>> compressed.  Readers can start linear scans from any of these records.\n>> Offsets in the first block are relative to the start of the file\n>> (position 0), and include the file header.  This requires the first\n>> restart in the first block to be at offset 8.\n>>\n>> The 2-byte `restart_count_m1` stores *one less* than the number of\n>> entries in the `restart_offset` list.  There is always a restart\n>> corresponding to the first ref record. Readers are responsible for\n>> computing `restart_count = restart_count_m1 + 1`.\n>\n> I had to reread these two paragraphs a couple of times as it calls out\n> the uninteresting things[1] and the interesting part is the logical\n> conclusion that one has to make themselves,\n> here is how I would write it:\n>\n>     Readers can start linear scans from any record whose name has\n>     not been prefix compressed. The first record of a block must not\n>     be prefix-compressed.\n>\n>     To aid finding the entry points for linear scans, a variable number\n>     of 4-byte `restart_offset` values follow the records. Offsets are\n>     relative to the start of the block and refer to the first byte of any\n>     such `ref_record` that is not prefix compressed.\n>     The first record can be omitted in the `restart_offset` values as it\n>     is implicit.\n\nNo, the first record must be listed in the restart_offset table. Its\nnot implicit. The count of it is implicit, to allow up to 65536\nrestart_offset entries using only a uint16 restart_count. Maybe that\nis being too cute, and the count should just be the entry count.\n\n>     The `restart_offset' values must be sorted (ascending,\n>     descending?).\n\nSorted, ascending.\n\n\n>> The `value` follows.  Its format is determined by `value_type`, one of\n>> the following:\n>>\n>> - `0x0`: deletion; no value data (see transactions, below)\n>> - `0x1`: one 20-byte object id; value of the ref\n>> - `0x2`: two 20-byte object ids; value of the ref, peeled target\n>\n> Up to here it is easy to spot a pattern:\n> The number indicates how many oids follow.\n>\n>> - `0x3`: symbolic reference: `varint( target_len ) target`\n>> - `0x4`: length delimited extension: `varint( data_len ) data`\n>\n> This breaks the pattern.\n>\n> Instead of hardcoding the numbers here, I wonder if we rather\n> want to make the bits more meaningful:\n>\n>   bit 0, 1: number of oids iff bit 2 unset\n>\n>   Iff bit 2 set, then we have a \"varint (len) data\"\n>   that follows, bits 0,1 are used for a different purpose,\n>   00 indicates 'symlink' and data is the string\n>   01 indicates 'multihead' such as FETCH_HEAD\n>   1* is reserved for now.\n>\n> This *may* be neat micro optimization, but hardcoding all bits\n> to a lookup table is fine, too.\n\nThe problem I have with this bit-based rule is it breaks down later as\nyou use additional codes, or you find yourself limited by the bit\nscheme and don't have the full range of 8 values available. So I\ndecided to be very literal about what the codes mean, and use a lookup\ntable.\n\n\n>   Note that symrefs and multiheads could share the same\n>   type, iff we had dissallowed '\\n' in refnames. (we do?\n>   otherwise FETCH_HEAD would be broken)\n>   The differentiator would be the '\\n' or '\\0' at the end of the\n>   first target.\n\nTrue, \\n is not allowed in a ref name, and neither is \\0.\n\nSymrefs in loose ref format use \"ref: <target>\\n\" as their content. We\ncould use that format in reftable, and then HEAD and FETCH_HEAD could\nuse the same value code, 0x3.\n\n\n>> #### index record\n>>\n>> An index record describes the last entry in another block.\n>> Index records are written as:\n>>\n>>     varint( prefix_length )\n>>     varint( (suffix_length << 3) | 0 )\n>>     suffix\n>>     varint( block_offset )\n>>\n>> Index records use prefix compression exactly like `ref_record`.\n>>\n>> Index records store `block_offset` after the suffix, specifying the\n>> offset in bytes (from the start of the file) of the block that ends\n>> with this reference.\n>\n> Instead of hardcoding the \"0\" in the last 3 bits, maybe pick one\n> of the reserved bit patterns to be there? I would imagine this\n> makes debugging easier:\n>\n>     0x5? Hah that must be an index block I have been\n>     looking at the wrong block!\n\nThis is an excellent suggestion. I'll include it in the next iteration.\n\n\n>> ### Obj block format\n>>\n>> Object blocks use unique, abbreviated 2-20 byte SHA-1s keys, mapping\n>> to ref blocks containing references pointing to that object directly,\n...\n>> Each record contains `block_count` number of block identifiers for ref\n>> blocks.  The `block_count` is determined by:\n>>\n>>     block_count = cnt_3\n>>     if (cnt_3 == 0x7) {\n>>       block_count += cnt_rest\n>>     }\n>\n> Instead of having the first seven blocks treated special (omission of\n> cnt_rest), we could also pick one of the reserved bit patterns here to\n> aid debuggers, as a downside we'd require cnt_rest to be present at\n> all times.\n\nAlways including cnt_rest adds a byte per object, which I'm not in favor of.\n\n> Additionally we could give an offset to a restart block to start searching\n> from inside a ref block.\n>\n> Coupled with the idea from above of having a reserved bit pattern, an\n> alternative design could look like:\n>\n>     varint( prefix_length )\n>     varint( (suffix_length << 3) | special_bit_pattern )\n>     suffix\n>     varint( cnt_rest )\n>     (varint( block_delta ), varint( restart_offset ))+\n\nThis adds a lot of complexity for a writer to maintain the\nbookkeeping, increases the size per object by at least 1 byte, and\ndoesn't improve the performance very much on lookups.\n\n\n>> #### log record\n>>\n>> Log record keys are structured as:\n>>\n>>     ref_name '\\0' reverse_int64( time_usec )\n>\n> repeating from v2:\n>\n>   The size of the integer\n>   is determined by the suffix length encoding and the preceding '\\0',\n>   such that the file format allows arbitrary integer size. So instead of\n>   pretending we can only do 64 bit here, just say 'reverse_int' ?\n\nI was trying to simplify things for readers and writers by forcing the\nsize to be fixed width.\n\n>> ### Log index\n>>\n>> The log index stores the log key (`refname \\0 reverse_int32(time_sec)`)\n>\n> int64 now?\n>\n>\n>> Log index format is identical to ref index, except the keys are 5\n>> bytes longer to include `'\\0'` and the 4-byte `reverse_int32(time)`.\n>> Records use `block_offset` to refer to the start of a log block.\n>\n> also 64? /me is confused.\n\nYes, two mistakes. I'll fix them s/32/64/, thank you.\n\n\n>> ### Footer\n...\n> By the design of the footer, refs must come first.\n> Whether objects or logs come thereafter can both be encoded here,\n> however the initial design claims an order.\n\nEven though the footer seems to allow changing the order of objects\nvs. logs, the format is clear the objects (if present) come before\nlogs (if present).\n"},{"id":"325179","messageId":"CAGZ79kaLN-_13Y4UifZXy6A1A2Peud90z=oEOw9_1DaXHRWKbQ@mail.gmail.com","threadId":"46441","inReplyTo":"CAJo=hJuCXFem8saH9kgBu1ROV3uuhRxHcHXz_Bp7xB+taW5S=Q@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-07-27T10:04:48Z","receivedAt":"2017-07-27T10:04:56Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jul 24, 2017 at 4:00 PM, Shawn Pearce <spearce@spearce.org> wrote:\n> On Mon, Jul 24, 2017 at 3:22 PM, Stefan Beller <sbeller@google.com> wrote:\n\n>>> #### index record\n>>>\n>>> An index record describes the last entry in another block.\n>>> Index records are written as:\n>>>\n>>>     varint( prefix_length )\n>>>     varint( (suffix_length << 3) | 0 )\n>>>     suffix\n>>>     varint( block_offset )\n>>>\n>>> Index records use prefix compression exactly like `ref_record`.\n>>>\n>>> Index records store `block_offset` after the suffix, specifying the\n>>> offset in bytes (from the start of the file) of the block that ends\n>>> with this reference.\n>>\n>> Instead of hardcoding the \"0\" in the last 3 bits, maybe pick one\n>> of the reserved bit patterns to be there? I would imagine this\n>> makes debugging easier:\n>>\n>>     0x5? Hah that must be an index block I have been\n>>     looking at the wrong block!\n>\n> This is an excellent suggestion. I'll include it in the next iteration.\n\nI was thinking about this a bit more and wondering if this would\nallow mixing all sorts of entries in the same block. Think about one\nof the most common cases client side:\n\n    git commit -m \"just a lonely ref update, no fancy stuff\"\n\nFor that we need (a) a single ref update and (b) now that the\nformat evolved a single reflog entry, maybe (c) an obj entry, too.\n\nI just realize that in this case we'd crank down the block size\nsuch that there is very little padding required, but I still wonder\nif it would be possible to omit the second (and third) block,\nbut rather go for a \"m\"ixed block containing just these 3 entries.\n\nAdditionally by setting the block_size to a special value \"0\",\nwe could indicate the omission of a footer, such that a single\nref update reftable is super small.\n"},{"id":"325185","messageId":"CAMy9T_GeE5Xh7HJ7VZPX5B5SGUmjUbELER07-u0se5Y0ZJ5YtQ@mail.gmail.com","threadId":"46441","inReplyTo":"CAJo=hJvxWg2J-yRiCK3szux=eYM2ThjT0KWo-SFFOOc1RkxXzg@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2017-07-27T14:28:04Z","receivedAt":"2017-07-27T14:28:17Z","isPatch":false,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Thanks for your continued work on this.\n\nI have some comments about v3 (inline below).\n\nOn Sat, Jul 22, 2017 at 11:29 AM, Shawn Pearce <spearce@spearce.org> wrote:\n> 3rd iteration of the reftable storage format.\n>\n> [...]\n> ### Objectives\n>\n> - Near constant time lookup for any single reference, even when the\n>   repository is cold and not in process or kernel cache.\n> - Near constant time verification a SHA-1 is referred to by at least\n>   one reference (for allow-tip-sha1-in-want).\n> - Efficient lookup of an entire namespace, such as `refs/tags/`.\n> - Support atomic push `O(size_of_update)` operations.\n> - Combine reflog storage with ref storage.\n\nI think that the optimization enabled by obj_records is only\ninteresting for server-side repositories that will frequently be\nfetched from, and which additionally have allow-tip-sha1-in-want\nturned on, right? So could we make it explicitly optional?\n\n> [...]\n> #### obj record\n>\n> An `obj_record` describes a single object abbreviation, and the blocks\n> containing references using that unique abbreviation:\n>\n>     varint( prefix_length )\n>     varint( (suffix_length << 3) | cnt_3 )\n>     suffix\n>     varint( cnt_rest )?\n>     varint( block_delta )+\n>\n> Like in reference blocks, abbreviations are prefix compressed within\n> an obj block.  On large reftable files with many unique objects,\n> higher block sizes (64k), and higher restart interval (128), a\n> `prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in\n> obj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits).\n>\n> Each record contains `block_count` number of block identifiers for ref\n> blocks.  The `block_count` is determined by:\n>\n>     block_count = cnt_3\n>     if (cnt_3 == 0x7) {\n>       block_count += cnt_rest\n>     }\n>\n> The `cnt_rest` field is only present when `block_count >= 0x7` and\n> could overflow the `cnt_3` field available in the record start.  This\n> encoding scheme is used as the vast majority of abbreviations are\n> only one reference (or at most a few), and unlikely to exceed 6 blocks.\n\nI agree that this `cnt_3` handling is overly cute. There will never be\nrecords with `block_count == 0`, will there? Then I propose that\n`cnt_3` be set to zero if `block_count` is greater than 7, in which\ncase the full block count is stored as a varint. It's simpler, and I\nthink the only `block_count`s for which this scheme costs a byte more\nthan your scheme are implausible: 128-134, 16383-16390, etc.\n\n> [...]\n> ### Log block format\n\nI'm still not happy about how you store reflogs. Here are my objections:\n\n* You store reflogs by appending a high-resolution time to the\nrefname. This is awkward:\n  * There is always some uncertainty about uniqueness, even with a\nhigh-resolution clock. If you want to eliminate that uncertainty, you\nhave to read the last entry in the reflog for the reference *for every\nupdate* to be sure that your timestamp is not already \"in use\".\n  * There is a need to invent microsecond values on systems with\nlow-resolution clocks, again to ensure uniqueness.\n  * If there is clock skew, then either you lose information about the\n*order* of reference updates (which I would consider unacceptable), or\nyou might have to invent fictional times for new updates to retain the\norder relative to previous updates. But inventing fictional times is\nvery problematic: suppose somebody's clock is incorrectly set to the\nyear 2020 when they make a commit. As far as Git is concerned, that is\nplausible, so the reflog entry gets that timestamp. Then the user\ncorrects the clock and makes another commit. We would either be forced\nto rewrite the old reflog entry(ies) or to give a timestamp in 2020 to\nthe new update.\n* With reftable storage, multiple-reference updates can be done\natomically. But the reflogs don't retain the information about which\nreferences were updated together.\n* Even if all reflogs are retained, it is not possible to deduce the\nglobal state of all references at a moment in the past.\n\nI propose to solve all of the above problems, plus some others, as follows:\n\nLet's keep an integer `update_index`, which starts at zero and\nincreases by one each atomic reference update (i.e., it increases by\none even if the update touches multiple references).\n\nLet's store reflog entries under keys `(refname, update_index)`, and\nstore the timestamp of the entry *within* the record rather than as\npart of the key. The tuple `(refname, update_index)` has to be encoded\nsomehow that ensures the desired order; see below. This encoding\nensures that the order of reference updates is well-defined regardless\nof clock skew, and that one can use the reflog to determine which\nreferences were updated together and what the complete state of\nreferences was after any update (assuming that the user retains all\nreflogs).\n\nLet's store in the header of each reftable file the `min_update_index`\nand `max_update_index` that are covered by the file. Do this such that\nthe indexes chain together properly for the reftables in the stack;\ni.e. `reftable[n].min_update_index == reftable[n-1].max_update_index +\n1`. This has two purposes:\n* The current `update_index` can be read quickly from the header of\nthe top reftable in the stack. When you want to create a new reference\nupdate, you can set its `update_index` to that value plus one and know\nthat it is unique.\n* If the table of contents should somehow get lost, the order of the\nreftable files can be reconstructed from their `update_index` ranges.\n\nIf a range of reftable files is compacted together, the new file gets\nits `min_update_index` from the oldest file being compacted and its\n`max_update_index` from the newest file. If reflogs are expired, that\ndoesn't affect the `min_update_index` or `max_update_index` for the\nreftables even if some of the `update_index`es in that range no longer\nappear in the file.\n\nHow to encode the `(refname, update_index)` tuple so that it sorts the\nway we want, namely with entries grouped by refname and backwards in\ntime? The simplest way would be `refname + \\0 + uint32(0xffffffff -\nupdate_index)`, and that should prefix-compress relatively well. One\ncould squeeze that down a bit by using a custom sort function, but\nthat would complicate the code, and anyway, zlib hides all sins,\ndoesn't it?\n\nI'm also still unhappy that completely packing references down to one\nreftable will require all reflogs to be fully rewritten, even though\nthe reflogs will mostly be append-only. And I'm worried that your\ncomforting numbers about how well the reflogs compress won't apply to\nGitHub, because we store lots of information in our reflog messages. I\nthink that some small tweaks would improve the situation.\n\nSuppose one could create `reflog-only` reftables. Then a `pack-refs`\nimplementation could choose to create one or more `reflog-only`\nreftables at the bottom of the stack for accumulating reflogs (such\nfiles would rarely have to be compacted), and on top of that a big\nreftable holding most of the reference values, and on top of that\nmultiple small reftables containing both reference values and reflogs\n(for recent updates). Perhaps one would even record in the table of\ncontents which files in the stack contain what kinds of data, to avoid\n(for example) having to open reflog-only files at all if you are only\ninterested in reference values.\n\nThis would require a small amendment to my `update_index` suggestion\nabove, namely that reflog-only and ref-only reftables might have\noverlapping `update_index` ranges, but the end result should be that\nthe files together cover the whole range of `update_index`es for refs\nand also for reflogs.\n\nMichael\n"},{"id":"325231","messageId":"CAJo=hJvR_rV356Mna0cMoTY2mrVnG9MH39wDS1tLCRT1h_f65Q@mail.gmail.com","threadId":"46441","inReplyTo":"CAMy9T_GeE5Xh7HJ7VZPX5B5SGUmjUbELER07-u0se5Y0ZJ5YtQ@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2017-07-28T22:12:23Z","receivedAt":"2017-07-28T22:12:51Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Thu, Jul 27, 2017 at 7:28 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> On Sat, Jul 22, 2017 at 11:29 AM, Shawn Pearce <spearce@spearce.org> wrote:\n>> 3rd iteration of the reftable storage format.\n>>\n>> [...]\n>> ### Objectives\n>>\n>> - Near constant time lookup for any single reference, even when the\n>>   repository is cold and not in process or kernel cache.\n>> - Near constant time verification a SHA-1 is referred to by at least\n>>   one reference (for allow-tip-sha1-in-want).\n>> - Efficient lookup of an entire namespace, such as `refs/tags/`.\n>> - Support atomic push `O(size_of_update)` operations.\n>> - Combine reflog storage with ref storage.\n>\n> I think that the optimization enabled by obj_records is only\n> interesting for server-side repositories that will frequently be\n> fetched from, and which additionally have allow-tip-sha1-in-want\n> turned on, right? So could we make it explicitly optional?\n\nCorrect. Its already optional. Maybe I'm just not clear enough about\nthat in the document. I agree its not required in the file, and\nwriters can choose to omit obj_record.\n\n\n>> #### obj record\n>>\n>> An `obj_record` describes a single object abbreviation, and the blocks\n>> containing references using that unique abbreviation:\n>>\n>>     varint( prefix_length )\n>>     varint( (suffix_length << 3) | cnt_3 )\n>>     suffix\n>>     varint( cnt_rest )?\n>>     varint( block_delta )+\n>>\n> [...]\n>> Each record contains `block_count` number of block identifiers for ref\n>> blocks.  The `block_count` is determined by:\n>>\n>>     block_count = cnt_3\n>>     if (cnt_3 == 0x7) {\n>>       block_count += cnt_rest\n>>     }\n>>\n>> The `cnt_rest` field is only present when `block_count >= 0x7` and\n>> could overflow the `cnt_3` field available in the record start.  This\n>> encoding scheme is used as the vast majority of abbreviations are\n>> only one reference (or at most a few), and unlikely to exceed 6 blocks.\n>\n> I agree that this `cnt_3` handling is overly cute. There will never be\n> records with `block_count == 0`, will there? Then I propose that\n> `cnt_3` be set to zero if `block_count` is greater than 7, in which\n> case the full block count is stored as a varint. It's simpler, and I\n> think the only `block_count`s for which this scheme costs a byte more\n> than your scheme are implausible: 128-134, 16383-16390, etc.\n\nThat is a great idea, thanks. I've updated the draft to reflect your\napproach of cnt_3 = 0 indicates the actual count follows in a varint.\n\n\n>> [...]\n>> ### Log block format\n>\n> I'm still not happy about how you store reflogs. Here are my objections:\n>\n> * You store reflogs by appending a high-resolution time to the\n> refname. This is awkward:\n>   * There is always some uncertainty about uniqueness, even with a\n> high-resolution clock. If you want to eliminate that uncertainty, you\n> have to read the last entry in the reflog for the reference *for every\n> update* to be sure that your timestamp is not already \"in use\".\n>   * There is a need to invent microsecond values on systems with\n> low-resolution clocks, again to ensure uniqueness.\n>   * If there is clock skew, then either you lose information about the\n> *order* of reference updates (which I would consider unacceptable), or\n> you might have to invent fictional times for new updates to retain the\n> order relative to previous updates. But inventing fictional times is\n> very problematic: suppose somebody's clock is incorrectly set to the\n> year 2020 when they make a commit. As far as Git is concerned, that is\n> plausible, so the reflog entry gets that timestamp. Then the user\n> corrects the clock and makes another commit. We would either be forced\n> to rewrite the old reflog entry(ies) or to give a timestamp in 2020 to\n> the new update.\n> * With reftable storage, multiple-reference updates can be done\n> atomically. But the reflogs don't retain the information about which\n> references were updated together.\n> * Even if all reflogs are retained, it is not possible to deduce the\n> global state of all references at a moment in the past.\n>\n> I propose to solve all of the above problems, plus some others, as follows:\n>\n> Let's keep an integer `update_index`, which starts at zero and\n> increases by one each atomic reference update (i.e., it increases by\n> one even if the update touches multiple references).\n>\n> Let's store reflog entries under keys `(refname, update_index)`, and\n> store the timestamp of the entry *within* the record rather than as\n> part of the key. The tuple `(refname, update_index)` has to be encoded\n> somehow that ensures the desired order; see below. This encoding\n> ensures that the order of reference updates is well-defined regardless\n> of clock skew, and that one can use the reflog to determine which\n> references were updated together and what the complete state of\n> references was after any update (assuming that the user retains all\n> reflogs).\n>\n> Let's store in the header of each reftable file the `min_update_index`\n> and `max_update_index` that are covered by the file. Do this such that\n> the indexes chain together properly for the reftables in the stack;\n> i.e. `reftable[n].min_update_index == reftable[n-1].max_update_index +\n> 1`. This has two purposes:\n> * The current `update_index` can be read quickly from the header of\n> the top reftable in the stack. When you want to create a new reference\n> update, you can set its `update_index` to that value plus one and know\n> that it is unique.\n> * If the table of contents should somehow get lost, the order of the\n> reftable files can be reconstructed from their `update_index` ranges.\n>\n> If a range of reftable files is compacted together, the new file gets\n> its `min_update_index` from the oldest file being compacted and its\n> `max_update_index` from the newest file. If reflogs are expired, that\n> doesn't affect the `min_update_index` or `max_update_index` for the\n> reftables even if some of the `update_index`es in that range no longer\n> appear in the file.\n>\n> How to encode the `(refname, update_index)` tuple so that it sorts the\n> way we want, namely with entries grouped by refname and backwards in\n> time? The simplest way would be `refname + \\0 + uint32(0xffffffff -\n> update_index)`, and that should prefix-compress relatively well. One\n> could squeeze that down a bit by using a custom sort function, but\n> that would complicate the code, and anyway, zlib hides all sins,\n> doesn't it?\n\nI'm with you this far, and like the {min,max}_update_index in the\nheader. I'm concerned about update_index in 32 bits. At some point you\nneed to reset the counter, or the repository is broken. 4b updates is\nenough for anyone? I'd feel better about this being a 64 bit field.\n\n\n> I'm also still unhappy that completely packing references down to one\n> reftable will require all reflogs to be fully rewritten, even though\n> the reflogs will mostly be append-only. And I'm worried that your\n> comforting numbers about how well the reflogs compress won't apply to\n> GitHub, because we store lots of information in our reflog messages. I\n> think that some small tweaks would improve the situation.\n>\n> Suppose one could create `reflog-only` reftables. Then a `pack-refs`\n> implementation could choose to create one or more `reflog-only`\n> reftables at the bottom of the stack for accumulating reflogs (such\n> files would rarely have to be compacted), and on top of that a big\n> reftable holding most of the reference values, and on top of that\n> multiple small reftables containing both reference values and reflogs\n> (for recent updates). Perhaps one would even record in the table of\n> contents which files in the stack contain what kinds of data, to avoid\n> (for example) having to open reflog-only files at all if you are only\n> interested in reference values.\n>\n> This would require a small amendment to my `update_index` suggestion\n> above, namely that reflog-only and ref-only reftables might have\n> overlapping `update_index` ranges, but the end result should be that\n> the files together cover the whole range of `update_index`es for refs\n> and also for reflogs.\n\nAck, I see the value in your suggestion for a reflog-only table.\n\nI'll rework the document to incorporate your feedback above, and post\nanother iteration to the list.\n"},{"id":"325235","messageId":"CAMy9T_F9oH926ShUGpFqJz4bMrJofvbsfb0_oME2NhsYvObq9Q@mail.gmail.com","threadId":"46441","inReplyTo":"CAJo=hJvR_rV356Mna0cMoTY2mrVnG9MH39wDS1tLCRT1h_f65Q@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2017-07-29T02:18:16Z","receivedAt":"2017-07-29T02:31:03Z","isPatch":false,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On Fri, Jul 28, 2017 at 3:12 PM, Shawn Pearce <spearce@spearce.org> wrote:\n> I'm with you this far, and like the {min,max}_update_index in the\n> header. I'm concerned about update_index in 32 bits. At some point you\n> need to reset the counter, or the repository is broken. 4b updates is\n> enough for anyone? I'd feel better about this being a 64 bit field.\n\nYes, I was a little bit nervous about 32 bits, too. But that's a *lot*\nof updates: one per second for 136 years. If that limit were ever\nreached, there could be a compaction step, where any update indices\nthat don't have associated reflog entries are \"compacted out\" of the\nnumerical sequence and the remaining indices are renumbered\ncontiguously.\n\nBut it's ok with me to make it 64 bits. Usually those extra bytes\nwould be appear as FFFFFFFF and so should prefix- and zlib-compress\nwell.\n\nMichael\n"},{"id":"325236","messageId":"CAJo=hJt5A6wL1i4d8WAivL-+LymZd_M6XjUBJ+vVoYfvuVm23w@mail.gmail.com","threadId":"46441","inReplyTo":"CAMy9T_F9oH926ShUGpFqJz4bMrJofvbsfb0_oME2NhsYvObq9Q@mail.gmail.com","subject":"Re: reftable [v3]: new ref storage format","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2017-07-29T02:31:31Z","receivedAt":"2017-07-29T02:31:57Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Fri, Jul 28, 2017 at 7:18 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> On Fri, Jul 28, 2017 at 3:12 PM, Shawn Pearce <spearce@spearce.org> wrote:\n>> I'm with you this far, and like the {min,max}_update_index in the\n>> header. I'm concerned about update_index in 32 bits. At some point you\n>> need to reset the counter, or the repository is broken. 4b updates is\n>> enough for anyone? I'd feel better about this being a 64 bit field.\n>\n> Yes, I was a little bit nervous about 32 bits, too. But that's a *lot*\n> of updates: one per second for 136 years. If that limit were ever\n> reached, there could be a compaction step, where any update indices\n> that don't have associated reflog entries are \"compacted out\" of the\n> numerical sequence and the remaining indices are renumbered\n> contiguously.\n\nI considered that, but its a bit of a pain for the writer to renumber\nthe remaining records.\n\n> But it's ok with me to make it 64 bits. Usually those extra bytes\n> would be appear as FFFFFFFF and so should prefix- and zlib-compress\n> well.\n\nThat was my thought. Within a single reference these will prefix\ncompress right out, and zlib will fix any sins within the log block at\nrestart points.\n"}]}