Binary DB v0:Snapshot 与内容记录
展开 Snapshot 父级沿革、Snapshot 链接、Tree、Blob、对象包、manifest 以及可选的文件缓存布局。
适用人群: 存储实现者与恢复工具作者
本章定义不可变的内容身份及其物理存储关系。有序的 parent edge 和归一化的 Tree 区间是权威,而下文明确点名的那几类缓存则如所述保持可重建或可选。
内容 Snapshot 记录#
CONTENT_SNAPSHOT_RECORD_SIZE = 88
ContentSnapshotRecord — snapshot.bin:
u8 snapshot_meta
u8 history_flags
u16 payload_len
u64 payload_offset
u64 snapshot_hash48
u32 parent_snapshot_index_plus1
u32 root_tree_pack_index_plus1
u32 root_entry_ordinal
u32 line_index_plus1
u8 manifest_hash[32]
u32 file_count
u64 total_bytes
u64 created_at_s规范的公开 Snapshot ID 是 SNP- 后跟十二个大写十六进制字符。 snapshot_hash48 把该后缀存放在它的低 48 位;高 16 位必须为零。非标准的 Snapshot ID 在这个 layout 中无法表示,必须拒绝。
history_flags 属于固定的 Snapshot schema,不是 payload。位 0 标记一个导入的 remote-head 历史边界:源 Snapshot 至少有一个父节点,但本地导入有意没有物化该 head 之前的祖先。这样的 Snapshot 不是真正的根。位 1 到位 7 保留,必须为零。
有序 Snapshot Parent 记录#
SNAPSHOT_PARENT_EDGE_RECORD_SIZE = 12
SnapshotParentEdgeRecord — snapshot_parent_edge.bin:
u32 child_snapshot_index
u32 parent_snapshot_index
u16 parent_ordinal
u16 flags当 ContentSnapshotRecord.snapshot_meta 位 4 置位时, snapshot_parent_edge.bin 中的有序行是该 Snapshot 唯一的 parent 权威。 此时固定字段 parent_snapshot_index_plus1 只是 ordinal 零的一份经过校验的 缓存:对真正的根或导入的 remote-head 历史边界,它为零,否则必须等于 ordinal 为零那条 edge 的 parent_snapshot_index + 1。Snapshot 的 payload 字节绝不携带 parent 索引。
被接纳的 parent 集合最多有 1,024 行。非空集合从 ordinal 零开始,使用连续且不 重复的 ordinal。ordinal 零是显式 first-parent 遍历所用的主 parent。子节点不得 引用自身、缺失的 Snapshot、已打墓碑的 Snapshot,也不得构成环。v0 中每个 flags 值都是零。parent 顺序不可变,并参与 Snapshot 的 manifest 身份。
history_flags 位 0 置位的 Snapshot 还必须置位 parent_edges_authority、 满足 parent_snapshot_index_plus1 = 0,且没有 parent edge 行。parent 遍历和 first-parent 遍历在该 Snapshot 处停止。导出、可达性和祖先证明必须保留这个边界 区分,不得把本地被截断的图当作完整历史来报告。在源根上、在带有任何本地 parent 的 Snapshot 上、或者在没有证据表明源确有 parent 的情况下设置该标志,属于损坏, 失败拒绝。
位 4 未置位的记录以原有的固定指针作为它完整的"零或一个 parent"权威,并且没有 parent-edge 行。这个状态只是为了接纳前代 layout-1 generation 作为离线转换输入 而存在。新的写入和新激活的 generation 对每个存活的 Snapshot(包括根)都使用 edge 权威。同一个 Snapshot 上出现混合权威、payload 中的 parent 扩展、不连续的 ordinal、指针与 edge 不一致,或为位 4 未置位的子节点存在 edge,都属于损坏, 失败拒绝。
Snapshot 权威是一个 DAG 森林,不是单根树。存活 Snapshot 数为零的权威是合法的。 每个存活的非边界 Snapshot 都必须能到达一个被证明的真正的根,但不同的连通分量 可以到达不同的真正的根。前代位 4 未置位、且其完整固定指针为零的记录证明了一个 真正的根;转换写入不带 parent 行的 edge 权威,并保持 history_flags 为零。 记录位置、Line 归属、分量大小和当前 head 状态,都不足以允许合并分量,或把一个 真正的根转成 remote-head 历史边界。
Task Snapshot Link 记录#
TASK_SNAPSHOT_INDEX_RECORD_SIZE = 8
TaskSnapshotIndexRecord — task_snapshot_index.bin:
u32 latest_snapshot_link_index_plus1
u16 snapshot_count
u8 next_snapshot_ordinal
u8 reserved0
CHANGE_SNAPSHOT_INDEX_RECORD_SIZE = 8
ChangeSnapshotIndexRecord — change_snapshot_index.bin:
u32 latest_snapshot_link_index_plus1
u16 snapshot_count
u16 reserved0AUTHORITATIVE_SNAPSHOT_LINK_RECORD_SIZE = 40
LocalTaskSnapshotLinkRecord / ServerTaskSnapshotLinkRecord — snapshot_link.bin:
u8 link_meta
u8 snapshot_ordinal
u16 payload_len
u64 payload_offset
u32 task_index
u32 change_index_plus1
u32 content_snapshot_index
u32 previous_task_snapshot_link_index_plus1
u32 previous_change_snapshot_link_index_plus1
u64 created_at_sREMOTE_MIRROR_SNAPSHOT_LINK_RECORD_SIZE = 52
RemoteMirrorTaskSnapshotLinkRecord — snapshot_link.bin:
u8 link_meta
u8 remote_meta
u16 reserved1
u8 snapshot_ordinal
u8 reserved0
u16 payload_len
u64 payload_offset
u32 task_index
u32 change_index_plus1
u32 content_snapshot_index
u32 previous_task_snapshot_link_index_plus1
u32 previous_change_snapshot_link_index_plus1
u64 created_at_s
u64 fetched_at_ssnapshot_ordinal 使用完整的 u8 范围。值 0..255 渲染为 S-01..S-256。 写入方必须拒绝 ordinal 255 之后的下一个 link,不得回绕、截断、复用,也不得 发明扩展的 Snapshot 句柄。
Tree 记录#
TREE_PACK_RECORD_SIZE = 32
TreePackRecord — tree_pack.bin:
u8 pack_meta
u8 pack_format_kind
u16 pack_hash_hi16
u32 pack_hash_lo32
u32 first_tree_index
u32 tree_count
u64 total_bytes
u64 created_at_sTREE_RECORD_SIZE = 20
TreeRecord — tree.bin:
u8 tree_meta
u8 reserved0
u32 pack_entry_ordinal
u32 entry_count
u8 tree_hash80[10]TREE_ENTRY_RANGE_RECORD_SIZE = 4
TreeEntryRangeRecord — tree_entry_range.bin:
u32 first_entry_indexTREE_ENTRY_RECORD_SIZE = 16
TreeEntryRecord — tree_entry.bin:
u8 entry_meta
u8 name_len
u16 mode_bits
u64 name_offset
u32 target_indexTree entry 的名字是单个归一化的 UTF-8 路径片段,不是完整路径,且限制为 255 字节。Blob entry 指向 blob.bin;Tree entry 指向 tree.bin。
TreeRecord.pack_entry_ordinal 是提供该 Tree 原始 payload 的那个 Tree pack 归档内部确切的物理 entry ordinal。对于 sparse_physical_ordinals 未置位的 Tree Pack,其逻辑区间中的每个 Tree 都满足 pack_entry_ordinal = tree_index - first_tree_index,且该 ordinal 小于 tree_count。当该位置位时,这个 ordinal 改为确切的稀疏物理归档定位符,不必 等于逻辑偏移,也不必小于逻辑上的 tree_count;归档边界、解码出的 Tree ID 和 解码出的 entry 数仍然要校验。
TreeEntryRangeRecord 属于固定 schema,不是 payload。它的记录 ordinal 就是 tree_index,并且在每个稳定的激活边界上,tree_entry_range.bin 的记录数与 tree.bin 完全相同。结合所属的 TreeRecord.entry_count,first_entry_index 指明该 Tree 在 tree_entry.bin 中权威的、连续的归一化行。对于空 Tree,规范的 first_entry_index 是当前 tree_entry.bin 的记录数。该区间必须落在 tree_entry.bin 之内;新的写入按 Tree 顺序追加归一化的 entry 行,绝不与已提交 Tree 的区间重叠。
归一化区间对元数据遍历具有权威性。物理 pack 定位符对定位和校验已归档的原始 Tree payload 具有权威性。名字、mode、目标种类、目标、Tree ID 和 entry_count 在归一化区间与解码出的 pack entry 之间必须一致;不一致属于损坏,失败拒绝。
创建 Tree 时先追加并 fsync 它的归一化 entry 行和 range 依赖,然后才追加 Tree 的提交记录。恢复流程忽略或截断孤立的 entry/range 依赖,拒绝已提交 Tree 缺失 range 的情况,并校验每一个已提交的 range 和物理 pack 定位符。
没有 tree_entry_range.bin 的前代 layout-1 generation 只能作为离线转换输入。 转换保留已有的 pack_entry_ordinal,从 pack payload 重建并校验每一个归一化 区间,并在激活之前物化出完整的旁路文件。
Blob 与 object-pack 记录#
OBJECT_PACK_RECORD_SIZE = 32
ObjectPackRecord — object_pack.bin:
u8 pack_meta
u8 pack_format_kind
u16 pack_hash_hi16
u32 pack_hash_lo32
u32 first_member_index
u32 member_count
u64 total_bytes
u64 created_at_sOBJECT_PACK_MEMBER_RECORD_SIZE = 16
ObjectPackMemberRecord — object_pack_member.bin:
u8 member_meta
u8 delta_chain_depth
u16 reserved0
u32 pack_index
u32 blob_index
u32 base_blob_index_plus1BLOB_RECORD_SIZE = 64
BlobRecord — blob.bin:
u8 blob_meta
u8 hash_kind
u16 reserved0
u64 size_bytes
u32 pack_member_index_plus1
u64 created_at_s
u64 pruned_at_s
u8 sha256[32]Blob 的 SHA-256 以 32 个原始字节存储。公开的 BLB-* 身份由前 80 位渲染而来。 object pack 和 Tree pack 记录以 (pack_hash_hi16, pack_hash_lo32) 存放它们 48 位的公开 ID 后缀。
在被接纳的遗留转换中,完整 32 字节 SHA-256 相同的多条源 Blob 行,只有在 blob_meta、归一化后的 hash_kind、reserved0、size_bytes 和 pruned_at_s 都一致时,才会合并成一条目标 Blob。不同的 pack-member 指针是同一内容的物理 副本,每一处 member、base-Blob、Tree-entry 和重建索引的引用都会被稠密重映射。 目标的 created_at_s 取源创建时间中最小的合法非零值。时间为零、元数据不一致、 大小不一致、prune 不一致、哈希前缀相同但完整 SHA-256 字节不同的碰撞,或者 字节与哈希不匹配,都失败拒绝。这项归一化不会新增任何 Blob 别名行或映射文件。
可选的 manifest 与 file 缓存#
这些记录是可丢弃的本地加速手段,从不具有权威性:
SNAPSHOT_FILE_INDEX_RECORD_SIZE = 4
SnapshotFileIndexRecord — snapshot_file_index.bin:
u32 first_file_index_plus1SNAPSHOT_FILE_RECORD_SIZE = 24
SnapshotFileRecord — snapshot_file.bin:
u64 path_hash64
u64 path_offset
u32 tree_entry_index
u16 path_len
u16 reserved0权威路径仍然是 snapshot.bin -> tree_pack.bin/tree.bin/ tree_entry.bin -> blob.bin。当所有可选的 manifest/file 缓存都不存在时,读取方 必须仍然正确。