MDB Shard 文件格式规范
Shard 是一个序列化对象,包含文件重建信息和用于去重目的的 xorb 元数据。
Shard 格式是上传文件重建上传并传达有关 xorbs 和块信息的载体,客户端可以针对这些信息进行数据去重。
概述
MDB(Merkle 数据库)shard 文件格式是一种二进制格式,用于存储文件元数据和内容寻址存储(CAS)信息,以实现高效的去重和检索。 本文档描述了 shard 格式的二进制布局和反序列化过程。 xet 协议的实现者在实现上传协议时必须使用 shard 格式。 Shard 格式用于 shard 上传(记录文件)和全局去重 API。
用作 API 请求和响应 Body
Shard 格式在 shard 上传 API 中用作请求负载,在全局去重/块查询 API 中用作响应负载。
Shard 上传
在这种情况下,shard 是一种序列化格式,允许客户端表示它们正在上传的文件。 每个文件重建映射到文件信息部分中的文件信息块。 此外,客户端创建的所有新 xorbs 的列表映射到 CAS 信息部分中的项(CAS 信息块),以便将来可以针对它们进行去重。
上传 shard 时,必须省略页脚部分。
可用于文件上传的 shard 示例可以在 Xet 参考文件 中找到。 此 shard 的也包含页脚的版本在 Xet 参考文件 中,有关更多上下文,请参见参考文件数据集的 README。
全局去重
全局去重 API 返回的 Shard 具有空的文件信息部分,仅在 CAS 信息部分包含相关信息。 此 API 返回的 CAS 信息部分包含 xorbs,其中 CAS 信息部分中描述的 xorb 包含被查询的块。 客户端可以针对返回的 shard 的 CAS 信息部分中任何 CAS 信息块中描述的任何其他 xorbs 进行内容去重。 在 shard 中返回的其他 xorb 描述可能更有可能引用客户端拥有的内容。
可用于全局去重查询的 shard 示例可以在 Xet 参考文件 中找到。
文件结构
Shard 文件按顺序由以下部分组成:
┌─────────────────────┐
│ Header │
├─────────────────────┤
│ File Info Section │
├─────────────────────┤
│ CAS Info Section │
├─────────────────────┤
│ Footer │
└─────────────────────┘
带字节偏移的整体文件布局
Offset 0:
┌───────────────────────────────────────────────────────┐
│ Header (48 bytes) │ ← 固定大小
└───────────────────────────────────────────────────────┘
Offset footer.file_info_offset:
┌───────────────────────────────────────────────────────┐
│ │
│ File Info Section │ ← 可变大小
│ (Multiple file blocks + │
│ bookend entry) │
│ │
└───────────────────────────────────────────────────────┘
Offset footer.cas_info_offset:
┌───────────────────────────────────────────────────────┐
│ │
│ CAS Info Section │ ← 可变大小
│ (Multiple CAS blocks + │
│ bookend entry) │
│ │
└───────────────────────────────────────────────────────┘
Offset footer.footer_offset:
┌───────────────────────────────────────────────────────┐
│ Footer (200 bytes, sometimes omitted) │ ← 固定大小
└───────────────────────────────────────────────────────┘
常量
MDB_SHARD_HEADER_VERSION: 2MDB_SHARD_FOOTER_VERSION: 1MDB_FILE_INFO_ENTRY_SIZE: 48 字节(每个文件信息结构的大小)MDB_CAS_INFO_ENTRY_SIZE: 48 字节(每个 CAS 信息结构的大小)MDB_SHARD_HEADER_TAG: 32 字节魔术标识符
数据类型
所有多字节整数都以小端格式存储。
u8: 8 位无符号整数u32: 32 位无符号整数u64: 64 位无符号整数- 字节数组类型在 Rust 中表示为
[u8; N],其中N是数组中的字节数。 - Hash: 32 字节哈希值,特殊的
[u8; 32]
1. 标头(MDBShardFileHeader)
位置:文件开始(偏移 0) 大小:48 字节
struct MDBShardFileHeader {
tag: [u8; 32], // 魔术数字标识符
version: u64, // 标头版本(必须为 2)
footer_size: u64, // 页脚大小(字节),如果省略页脚则设置为 0
}
内存布局:
┌────────────────────────────────────────────────────────────────┬───────────┬───────────┐
│ tag (32 bytes) │ version │ footer_sz │
│ Magic Number Identifier │ (8 bytes) │ (8 bytes) │
└────────────────────────────────────────────────────────────────┴───────────┴───────────┘
0 32 40 48
反序列化步骤:
- 读取 32 字节的魔术标签
- 验证标签匹配
MDB_SHARD_HEADER_TAG - 读取 8 字节的版本(u64)
- 验证版本等于 2
- 读取 8 字节的 footer_size(u64)
序列化时,footer_size 必须是组成页脚的字节数,如果省略页脚则为 0。
2. 文件信息部分
位置:footer.file_info_offset 到 footer.cas_info_offset 或直接在标头之后
此部分包含 0 个或多个文件信息(File Info)块的序列,每个块至少包含一个标头和至少 1 个数据序列条目,以及可选的验证条目和元数据扩展部分。 文件信息部分在到达书挡条目时结束。
整个部分中的每个文件信息块都是文件重建到二进制格式的序列化。
对于每个文件,有一个 FileDataSequenceHeader,对于每个 term,有一个 FileDataSequenceEntry,可选地有一个匹配的 FileVerificationEntry,并且在末尾还有一个可选的 FileMetadataExt。
Shard 文件信息部分可以连续包含多个文件信息块,在完成读取 1 个文件描述的所有内容后,下一个立即开始。 如果在读取下一部分的标头时,读取器遇到书挡条目,这意味着文件信息部分结束;你已经读取了此 shard 中的最后一个文件描述。
文件信息部分布局
无可选组件:
┌─────────────────────┐
│ FileDataSeqHeader │ ← 文件 1
├─────────────────────┤
│ FileDataSeqEntry │
├─────────────────────┤
│ FileDataSeqEntry │
├─────────────────────┤
│ ... │
├──── ─────────────────┤
│ FileDataSeqHeader │ ← 文件 2
├─────────────────────┤
│ FileDataSeqEntry │
├─────────────────────┤
│ ... │
├─────────────────────┤
│ Bookend Entry │ ← 全 0xFF 哈希 + 零
└─────────────────────┘
包含所有可选组件:
┌─────────────────────┐
│ FileDataSeqHeader │ ← 文件 1(标志指示验证 + 元数据)
├── ───────────────────┤
│ FileDataSeqEntry │
├─────────────────────┤
│ FileDataSeqEntry │
├─────────────────────┤
│ ... │
├─────────────────────┤
│ FileVerifyEntry │ ← 每个 FileDataSeqEntry 一个
├─────────────────────┤
│ FileVerifyEntry │
├─────────────────────┤
│ ... │
├─────────────────────┤
│ FileMetadataExt │ ← 每个文件一个(如果设置了标志)
├─────────────────────┤
│ FileDataSeqHeader │ ← 文件 2
├─────────────────────┤
│ ... │
├─────────────────────┤
│ Bookend Entry │ ← 全 0xFF 哈希 + 零
└─────────────────────┘
FileDataSequenceHeader
struct FileDataSequenceHeader {
file_hash: Hash, // 32 字节文件哈希
file_flags: u32, // 指示后续条件部分的标志
num_entries: u32, // FileDataSequenceEntry 结构的数量
_unused: [u8; 8], // 保留空间 8 字节
}
文件标志:
MDB_FILE_FLAG_WITH_VERIFICATION(0x80000000 或 1 << 31):具有验证条目MDB_FILE_FLAG_WITH_METADATA_EXT(0x40000000 或 1 << 30):具有元数据扩展
给定 file_data_sequence_header.file_flags & MASK(按位与)操作,如果结果 != 0,则效果为真。
Memory Layout:
┌────────────────────────────────────────────────────────────────┬──────────┬───────────┬────────────┐
│ file_hash (32 bytes) │file_flags│num_entries│ _unused │
│ File Hash Value │(4 bytes) │(4 bytes) │ (8 bytes) │
└───────────────────────────────────────── ───────────────────────┴──────────┴───────────┴────────────┘
0 32 36 40 48
FileDataSequenceEntry
每个 FileDataSequenceEntry 是 1 个 term,本质上是文件重建 term 的二进制序列化。
struct FileDataSequenceEntry {
cas_hash: Hash, // term 中的 32 字节 Xorb 哈希
cas_flags: u32, // CAS 标志(保留供将来使用,设置为 0)
unpacked_segment_bytes: u32, // 解包时的 term 大小
chunk_index_start: u32, // term 在 Xorb 内的起始块索引
chunk_index_end: u32, // term 在 Xorb 内的结束块索引(独占)
}
请注意,在 FileDataSequenceEntry 中描述块范围时,使用起始包含但结束独占的范围,即 [chunk_index_start, chunk_index_end)
内存布局:
┌────────────────────────────────────────────────────────────────┬─────────┬─────────┬─────────┬─────────┐
│ cas_hash (32 bytes) │cas_flags│unpacked │chunk_idx│chunk_idx│
│ CAS Block Hash │(4 bytes)│seg_bytes│start │end │
│ │ │(4 bytes)│(4 bytes)│(4 bytes)│
└────────────────────────────────────────────────────────────────┴─────────┴─────────┴─────────┴─────────┘
0 32 36 40 44 48
FileVerificationEntry(可选)
Shard 上传必须设置验证条目。
要为 shard 上传生成验证哈希,请阅读有关验证哈希的部分。
struct FileVerificationEntry {
range_hash: Hash, // 32 字节验证哈希
_unused: [u8; 16], // 保留(16 字节)
}
内存布局:
┌────────────────────────────────────────────────────────────────┬────────────────────────────────┐
│ range_hash (32 bytes) │ _unused (16 bytes) │
│ Verification Hash │ Reserved Space │
└────────────────────────────────────────────────────────────────┴────────────────────────────────┘
0 32 48
当 shard 具有验证条目时,所有文件信息部分必须具有验证条目。
如果 shard 中只有部分文件具有验证条目,则认为 shard 无效。
在这种情况下,每个 FileDataSequenceEntry 将有一个匹配的 FileVerificationEntry,其中 range_hash 使用该块范围的块哈希计算。
对于任何文件,第 n 个 FileVerificationEntry 与第 n 个 FileDataSequenceEntry 相关,并且像 FileDataSequenceEntries 一样,如果有验证条目,将有 file_data_sequence_header.num_entries 个验证条目(跟随 num_entries 数据序列条目)。
FileMetadataExt(可选)
对于通过 shard 上传 API 上传的 shard,每个文件都需要此部分。
每个文件信息块只有 1 个 FileMetadataExt 实例,当存在时,它是该文件信息块的最后一个组件。
sha256 字段是所描述文件内容的 32 字节 SHA256。
struct FileMetadataExt {
sha256: Hash, // 32 字节 SHA256 哈希
_unused: [u8; 16], // 保留(16 字节)
}
内存布局:
┌────────────────────────────────────────────────────────────────┬────────────────────────────────┐
│ sha256 (32 bytes) │ _unused (16 bytes) │
│ SHA256 Hash │ Reserved Space │
└─────────────────────────────────── ─────────────────────────────┴────────────────────────────────┘
0 32 48
文件信息书挡
文件信息部分的结束由书挡条目标记。
书挡条目为 48 字节长,前 32 字节全为 0xFF,后跟 16 字节全为 0x00。
假设你尝试反序列化 FileDataSequenceHeader,其文件哈希全为 1 位,则此条目是书挡条目,下一个字节开始下一部分。
由于文件信息部分紧跟在标头之后,客户端可以跳过反序列化页脚以知道它开始反序列化此部分的位置。 文件信息部分从标头之后立即开始,并在到达书挡时结束。
反序列化步骤:
- 寻址到
footer.file_info_offset - 读取
FileDataSequenceHeader - 检查
file_hash是否全为0xFF(书挡标记)- 如果是,停止 - 读取
file_data_sequence_header.num_entries×FileDataSequenceEntry结构 - 如果
file_flags & MDB_FILE_FLAG_WITH_VERIFICATION != 0:读取file_data_sequence_header.num_entries×FileVerificationEntry - 如果
file_flags & MDB_FILE_FLAG_WITH_METADATA_EXT != 0:读取 1 ×FileMetadataExt - 从步骤 2 重复,直到找到书挡