1. 结论
Pageserver 的 Key 不是 SQL 主键;它用tablespace/database/relation/fork/block等物理地址形成 18 字节 Key,用 LSN 形成版本维度,Value 保存完整页 Image 或可重放 WAL delta。表名、列、SQL 类型、约束和 PostgreSQL 索引结构都仍在 PostgreSQL 页内,不形成独立 Pageserver KV。
2. 总体 Key/Value 布局
租户 / shard / timeline ← 位于目录、对象名和 layer summary,不进入 18B Key └─ Pageserver 版本化 Key/Value ├─ Schema / 物理元数据 │ ├─ 00 DB directory / relmap / relation directory / relation size │ ├─ 01 SLRU directory / segment size │ ├─ 02 pg_twophase directory │ ├─ 03 control / checkpoint / 旧 AUX v1 │ └─ 60..7E 稀疏元数据 │ ├─ 62 AUX v2 hash Key -> 路径/文件 Value │ ├─ 63 replication origin origin Key -> LSN Value │ └─ 65 relation existence v2 relation Key -> "r"/empty └─ Data ├─ 00 relation fork block Key -> 8KiB Image 或 WAL delta ├─ 01 pg_xact / pg_multixact block Key -> 8KiB Image 或专用 delta └─ 02 pg_twophase file Key -> 原始文件 Image 写入: (18B Key, 8B LSN, Value) -> Value::ser 追加到 ephemeral file -> 内存 BTreeMap<CompactKey, LSN -> offset> -> freeze -> delta layer: B-tree<(18B Key | 8B LSN), BlobRef> + serialized Value blob 读取: get(Key, requested LSN) -> 从新到旧遍历 delta/image layers -> 收集 WAL delta,直到 Image 或 will_init delta -> 按 LSN 正序 redo -> 返回该 Key 在 requested LSN 的完整 Bytes 压实: 多个历史 Value -> reconstruct -> image layer image layer: B-tree<18B Key, blob offset>,LSN 位于 layer header,Value 是原始完整 Image2.1 公共逻辑 Key
所有实际 layer 索引中的逻辑 Key 都是 18 字节:
Key = field1:u8 | field2:u32 BE | field3:u32 BE | field4:u32 BE | field5:u8 | field6:u32 BE| Key 规则 | 源码事实 |
|---|---|
| 总宽度 | 固定 18B,无分隔符、长度前缀或转义。 |
| 排序 | 字段顺序与 18B 大端字节序一致;无符号数值按字节比较即可保持升序。 |
| 版本 | 逻辑 Key 本身不含版本。Delta layer 的物理索引 Key 追加LSN:u64 BE,成为 26B;Image layer 把单一 LSN 放在 layer header。 |
| metadata Key | 稀疏 metadata 编码器先构造 16B:首字节 + 15B payload;转为通用 Key 时,field2高 16 位补零,最终 layer 索引仍写 18B。 |
| CompactKey | 内存使用 128 位整数;field2只保留低 16 位。因此正常生产 relation 依赖内置 tablespace OID 可表示,用户自定义 tablespace 不受支持。 |
| 空间边界 | 0x00..0x03为 dense 对象;0x60..0x7E被当作 sparse metadata,可容忍 Key 缺失。 |
| NULL/collation/降序 | 不适用。Key 字段都是物理标识;SQL NULL、collation 和索引升降序留在 PostgreSQL 页内部。 |
2.2 layer 中真实的 Key/Value 关系
| 层 | B-tree Key | B-tree Value | blob Value | LSN |
|---|---|---|---|---|
| Delta layer | Key(18B) | LSN(BE u64),共 26B | 5B 大端BlobRef;逻辑值为blob_offset << 1 | will_init | 完整Value::ser;1B/4B blob 长度头;不压缩 | 在 B-tree Key 中 |
| Image layer | Key(18B) | 5B 大端 blob offset | 重建后的原始 Image Bytes,不带Value::Image枚举标签;1B/4B blob 长度头;可 Zstd | 在 layer summary 中 |
B-tree 使用 8KiB 页。节点头是child_count:u16 BE | level:u8 | prefix_len:u8 | suffix_len:u8,后接公共 Key 前缀、定长 Key 后缀数组和每项 5B Value。写入必须严格递增;页满时可提取页内公共前缀。
blob 长度头:
payload_len < 128: 0LLLLLLL payload_len >= 128: 1CCCllll llllllll llllllll llllllll CCC=000:未压缩 CCC=001:Zstd 最大 payload 长度:0x0FFF_FFFF当前 layer blob 和 B-tree 节点没有独立 checksum 字段;完整性主要由文件、summary 一致性检查及远端对象层承担。
2.3 逻辑 Value envelope
Delta layer 使用大端、定宽整数的 bincode/Serde 编码:
Value::Image(bytes) = variant:u32 BE = 0 | len:u64 BE | bytes Value::WalRecord(record) = variant:u32 BE = 1 | NeonWalRecord variant:u32 BE | variant fieldsValue 没有独立格式版本字段。枚举顺序和字段序列属于永久格式兼容面;layer 外层另有 storage format version。Image layer 不再保存上述 envelope,只保存重建后的 Image payload。
3. 真实 Key/Value 示例
以下 5 组字节由临时 codec probe 直接调用当前生产 Key 构造器、AUX 编码器,并直接编译当前Value/NeonWalRecord定义后生成,统一选择LSN=0/100,即数值0x100。Delta index Key是真实持久 delta B-tree 的完整 26B Key;Delta Value是 blob 长度头之后的完整 Value payload。
3.1 初始化空 DB directory
- 逻辑输入:新 timeline,尚无数据库目录项,LSN
0/100。 - 写入形式:
put(DBDIR_KEY, Value::Image(BeSer(DbDirectory{dbdirs:{}})))
Key(定位整个 PGDATA 的数据库目录)
可读分段:00/00000000/00000000/00000000/00/00000000 逻辑 Key 转义/Display: 000000000000000000000000000000000000 逻辑 Key Hex: 000000000000000000000000000000000000 Delta index Key Hex(逻辑 Key | LSN): 0000000000000000000000000000000000000000000000000100Value(保存空目录集合)
内层 Image 解码:HashMap<(spcnode, dbnode), relmap_exists>,元素数 0 内层 Image Hex:0000000000000000 Delta Value 长度:20B Delta Value Hex: 0000000000000000000000080000000000000000 分段: 00000000 -> Value::Image tag 0000000000000008 -> Image 长度 8 0000000000000000 -> HashMap 元素数 0 delta blob 完整前缀:14(payload 长度 20)| 上述 20B编码与使用
Key: 固定 DBDIR 地址 -> 18B BE Key -> 追加 LSN 得 26B delta Key Value:空 HashMap -> BE bincode 8B -> Value::Image envelope 写入: commit -> put_batch -> ephemeral file -> delta layer 读取: get(DBDIR_KEY, LSN) -> Value::des -> Image -> DbDirectory::des 回表: 不需要;这是 Pageserver 自己的物理目录元数据3.2 relation 16384 main fork 的大小为 42 页
- 逻辑输入:默认 tablespace
1663、database OID5、relnode16384、main fork0,大小42页,LSN0/100。 - 写入形式:
put(rel_size_key, Value::Image(42u32.to_le_bytes()))
Key(定位 relation fork size)
可读分段:00/0000067F/00000005/00004000/00/FFFFFFFF 逻辑 Key 转义/Display: 000000067F000000050000400000FFFFFFFF 逻辑 Key Hex: 000000067F000000050000400000FFFFFFFF Delta index Key Hex: 000000067F000000050000400000FFFFFFFF0000000000000100Value(保存页数)
内层 Image 解码:42 内层 Image Hex:2A000000 Delta Value 长度:16B Delta Value Hex: 0000000000000000000000042A000000 分段: 00000000 -> Value::Image tag 0000000000000004 -> Image 长度 4 2A000000 -> u32 little-endian 42 delta blob 完整前缀:10(payload 长度 16)| 上述 16B编码与使用
Key: spc/db/rel/fork + FFFFFFFF size sentinel -> 18B BE -> 追加 LSN Value:nblocks=42 -> u32 LE -> Value::Image 写入: relation create/extend/truncate 与受影响页在同一 modification 中维护 读取: get(size Key, LSN) -> Image -> get_u32_le 回表: 读取 relation page 前先查 size;越过 EOF 返回零页3.3 relation block 7 的零页 Image
- 逻辑输入:同一 relation 的 block
7,写入 8192 个零字节,LSN0/100。这是 relation 扩展产生 gap 时当前生产路径实际使用的 Image。 - 写入形式:
put(rel_block_key, Value::Image(ZERO_PAGE))
Key(定位一个 PostgreSQL 物理页)
可读分段:00/0000067F/00000005/00004000/00/00000007 逻辑 Key 转义/Display: 000000067F00000005000040000000000007 逻辑 Key Hex: 000000067F00000005000040000000000007 Delta index Key Hex: 000000067F000000050000400000000000070000000000000100Value(保存完整 8KiB 页)
内层 Image:8192B,全 00 Delta Value 总长度:8204B Delta Value Hex(真实首尾;中间 8172B 00 省略): 首 32B: 0000000000000000000020000000000000000000000000000000000000000000 ... 尾 32B: 0000000000000000000000000000000000000000000000000000000000000000 分段: 00000000 -> Value::Image tag 0000000000002000 -> Image 长度 8192 00 × 8192 -> PostgreSQL 页 delta blob 4B 头:8000200C(payload 长度 8204,未压缩) 物理 blob 总长度:8208B编码与使用
Key: relation 物理地址 + blkno 7 -> 18B BE -> 追加 LSN Value:ZERO_PAGE 8192B -> Value::Image envelope 写入: put_rel_page_image_zero -> serialized batch -> delta layer 读取: get(Key, requested LSN) 命中 Image 后直接返回,无 WAL redo 回表: 不需要;它本身就是 PostgreSQL heap/index/catalog/TOAST 等 relation 的物理页3.4 visibility map 页的 ClearVisibilityMapFlags delta
- 逻辑输入:同一 relation 的 VM fork
2、block3;清除 heap block0x11对应位,旧 heap block 为空,flags=0x03,LSN0/100。 - 写入形式:
put(vm_page_key, Value::WalRecord(ClearVisibilityMapFlags{...}))
Key(定位 visibility map 物理页)
可读分段:00/0000067F/00000005/00004000/02/00000003 逻辑 Key 转义/Display: 000000067F00000005000040000200000003 逻辑 Key Hex: 000000067F00000005000040000200000003 Delta index Key Hex: 000000067F000000050000400002000000030000000000000100Value(保存可重放页增量)
可读解码: Value::WalRecord( ClearVisibilityMapFlags { new_heap_blkno: Some(17), old_heap_blkno: None, flags: 3 } ) Delta Value 长度:15B Delta Value Hex: 000000010000000101000000110003 分段: 00000001 -> Value::WalRecord tag 00000001 -> ClearVisibilityMapFlags tag 01 00000011 -> Some(u32 BE 17) 00 -> None 03 -> flags delta blob 完整前缀:0F(payload 长度 15)| 上述 15B编码与使用
Key: relation VM fork 地址 -> 18B BE -> 追加 LSN Value:专用 delta 字段 -> BE bincode enum 写入: WAL ingest 解析记录 -> put_rel_wal_record -> delta layer 读取: 从目标 LSN 向旧版本收集到 Image/will_init -> 按 LSN 正序应用 delta 回表: 不需要 SQL 回表;redo 需要该 VM Key 的旧 Image,且会按字段修改页3.5 AUX 文件pg_logical/mappings/test1
- 逻辑输入:路径
pg_logical/mappings/test1,文件内容abc,LSN0/100。 - 写入形式:
put(aux_hash_key, Value::Image(aux_v1_bucket))
Key(定位 AUX hash bucket)
16B metadata 输入: 62 01 01 7F8B83D94F7081693471ABF91C 可读分段: 62/00000101/7F8B83D9/4F708169/34/71ABF91C 逻辑 Key 转义/Display: 62000001017F8B83D94F7081693471ABF91C 逻辑 Key Hex(通用 18B 形式): 62000001017F8B83D94F7081693471ABF91C Delta index Key Hex: 62000001017F8B83D94F7081693471ABF91C0000000000000100 分段: 62 -> AUX v2 prefix 0000 -> 16B metadata Key 展开到 18B 时的 field2 高位补零 01 01 -> pg_logical / mappings 分类 7F8B...F91C -> FNV-1a-128("test1") 的低 104 位Value(保存完整路径以处理 hash bucket 与碰撞)
可读解码:[("pg_logical/mappings/test1", b"abc")] 内层 AUX payload 长度:37B Delta Value 长度:49B Delta Value Hex: 000000000000000000000025010000001970675F6C6F676963616C2F6D617070696E67732F746573743100000003616263 分段: 00000000 -> Value::Image tag 0000000000000025 -> Image 长度 37 01 -> AUX value format version 1 00000019 -> path 长度 25 7067...7431 -> UTF-8 path 00000003 -> content 长度 3 616263 -> "abc" delta blob 完整前缀:31(payload 长度 49)| 上述 49B编码与使用
Key: 路径分类 + 文件名 hash -> 16B metadata Key -> 通用 18B Key -> 追加 LSN Value:version + 完整 path/content -> Value::Image 写入: 先 get(bucket),按完整 path 更新/删除,再整体重写该 bucket 读取: scan([62..63), LSN) -> 每个 Value 解码 -> 汇总完整路径 回表: 不需要;主定位依赖 Key,碰撞消歧依赖 Value 中完整 path当前源码不存在独立 SQL 主键、唯一索引或非唯一二级索引 KV,因此没有伪造相应示例。PostgreSQL 的 B-tree/GIN/GiST 等索引页与示例 3 使用完全相同的 relation block Key/Value 格式。
4. Schema 如何保存
4.1 SQL Schema 与 Pageserver 物理元数据的边界
| 逻辑对象 | 持久化状态 | Key | Value | 创建/修改/删除 |
|---|---|---|---|---|
| tenant / shard / timeline | 不进入用户数据 KV | 不适用;位于目录、远端对象路径和 layer summary | 控制面 metadata/index JSON,不是本报告的 Value | 由 Pageserver 控制面维护 |
| PostgreSQL database 目录 | 独立 KV | 固定 DB directory Key | 整体 BE bincode map:(spcnode,dbnode)->relmap_exists | 新 DB 或 relmap 首次出现重写整个 map;drop 删除 map 项 |
| SQL schema/namespace | 嵌入其他 Value | pg_namespace 等 catalog relation 的 block Key | 原始 catalog 页 Image/WAL delta | DDL 作为 catalog 页 WAL 更新 |
| table / materialized view / sequence / TOAST relation | 物理 relation 独立成多组 KV;定义嵌入 catalog 页 | spcnode/dbnode/relnode/fork/block | relation page Image/WAL delta;名字、owner 等定义在 catalog 页 | create/drop 改 relation directory、size 与 catalog pages |
| column、逻辑类型、nullable、default、generated | 嵌入 catalog/heap Value | 不形成字段级 Key | pg_attribute、pg_type、pg_attrdef 等 relation 页 | 字段变化只重写受影响页,不形成 Pageserver schema Value |
| primary key、unique/check/foreign key、index definition、predicate/include columns | 嵌入 catalog Value | 不形成定义级 Key | pg_constraint、pg_index 等 catalog 页 | DDL 由 catalog 页 WAL 表示 |
| 普通/唯一/表达式/部分/覆盖/全文/空间索引 | 索引主体是普通 relation block KV | Key 不含索引列或唯一性,只含索引 relation 的 relnode/fork/block | PostgreSQL 索引页原始 bytes | PostgreSQL 维护页;Pageserver 不判重、不解析索引项 |
| relation directory v1 | 独立 KV,默认 | 每个(spcnode,dbnode)一个 Key | 整体HashSet<(relnode,forknum)> | create/drop 会重写整个集合 |
| relation existence v2 | 可选独立稀疏 KV | 每个(spc,db,rel,fork)一个0x65Key | r表示存在;空 Value 表示 removed | 可选迁移时逐 relation 建 Key;drop 写空 Image |
| relation size | 独立版本化 KV | relation block 空间末尾,blknum=FFFFFFFF | 4B LEnblocks | create/extend/truncate 写新版本 |
relmap (pg_filenode.map) | 独立 KV | (spc,db,0,0,0) | 文件原始 bytes | put 重写完整 Value;drop 当前未实现 |
| sequence/identity 当前值 | 嵌入 relation page | sequence relation block Key | PostgreSQL sequence 页 | 与普通 relation 页相同 |
| 权限、函数、视图定义、统计信息 | 嵌入 catalog relation pages;部分运行统计可在 AUX | 无对象级 Key | catalog 页或 AUX 文件 bytes | 由对应 WAL/AUX 更新 |
| object/OID allocator | 无 Pageserver allocator | 不适用 | PG control/catalog/SLRU 页中的状态 | 由 PostgreSQL 产生并通过页/WAL 持久化 |
| partition、placement、TTL、DDL job | 没有 Pageserver 独立 schema KV | 不适用 | PostgreSQL 自己的 catalog 页;Pageserver TTL/DDL job 不适用 | 不适用 |
4.2 目录 Value 的重写粒度
源码事实:
- DB directory、relation directory v1、SLRU segment directory、two-phase directory 都把整个 map/set 放在一个 Image Value。
- 这些集合内一个元素变化会重新序列化并写入完整新版本。
- HashMap/HashSet 的非空元素迭代顺序不是 Key 排序协议;读取不能依赖 Value 内条目顺序。
- relation directory v2 把一个 relation fork 拆成一个稀疏 KV,避免 v1 单 Value 随 relation 数量增长,但会增加 Key 数和 metadata 扫描项。
机制推导:
- v1 点查目录只需一个 Key,但目录越大,单项 DDL 的 Value 重写量越大。
- v2 的创建/删除只改一个短 Value,代价是扫描、layer index 和版本项数量增加。
5. Data 如何保存
5.1 当前 Key space 全集
| 对象 | Key 布局 | Value 布局 | 访问 |
|---|---|---|---|
| DB directory | `00 | 0 | 0 |
| relmap file | `00 | spc | db |
| relation directory v1 | `00 | spc | db |
| relation block | `00 | spc | db |
| relation size | `00 | spc | db |
| SLRU directory | `01 | kind | 0 |
| SLRU block | `01 | kind | 1 |
| SLRU segment size | `01 | kind | 1 |
| two-phase directory | `02 | 0 | 0 |
| two-phase file | `02 | 0 | 0 |
| control file | `03 | 0 | 0 |
| checkpoint | `03 | 0 | 0 |
| AUX v1(兼容保留) | `03 | 0 | 0 |
| relation existence v2 | `65 | spc | db |
| AUX v2 bucket | `62 | class2B | hash104` 的 16B 设计,展开为 18B |
| replication origin | `63 | 0 | 0 |
0x61relation-size metadata prefix 和0x64DB-directory metadata prefix 在当前版本只保留常量,没有生产 Key 构造或写入。它们不能视为已实现格式。
5.2 relation 主数据
四种 fork 都显式进入 Key:
| forknum | fork | Pageserver Value |
|---|---|---|
| 0 | main | 8KiB 页 Image 或 PostgreSQL WAL delta |
| 1 | FSM | 8KiB 页 Image 或 WAL delta |
| 2 | visibility map | 8KiB 页 Image、PG WAL delta或专用 VM delta |
| 3 | init fork | 8KiB 页 Image 或 WAL delta;分片规则把它保留在 shard 0 |
同一个 relation fork 的 Key 范围是:
[00|spc|db|rel|fork|00000000, 00|spc|db|rel|(fork+1)|00000000)该范围包含所有 block,并在末尾包含FFFFFFFFsize Key。普通点查先用 size Key 验证 EOF,再取 block Key。Pageserver 不知道该页是 heap tuple、B-tree internal node、GIN posting list、catalog tuple 还是 TOAST chunk。
5.3 所有 Value 类型与字段编码族
| 类型/变体 | Key 中编码 | Delta Value 中编码 | NULL/边界/状态 |
|---|---|---|---|
Value::Image(Bytes) | 无类型 tag | `u32 tag=0 | u64 len |
Value::WalRecord(Postgres) | 无类型 tag | `Value tag=1 | record tag=0 |
ClearVisibilityMapFlags | 目标 VM 页在 Key | record tag=1;两个Option<u32>+u8 flags | Option 用 0/1 标记;当前生产 |
ClogSetCommitted | 目标 pg_xact 页在 Key | record tag=2;Vec<u32>+i64 timestamp | Vec 为u64 count+ BE 元素;当前生产 |
ClogSetAborted | 目标 pg_xact 页在 Key | record tag=3;Vec<u32> | 当前生产 |
MultixactOffsetCreate | 目标 offsets 页在 Key | record tag=4;`mid:u32 | moff:u32` |
MultixactMembersCreate | 目标 members 页在 Key | record tag=5;`moff:u32 | Vec<(xid,status)>` |
AuxFile | 旧固定 AUX v1 Key | record tag=6;String path + Option Bytes | 当前源码没有生产构造者;兼容旧 Value |
TruncateVisibilityMap | 目标 VM 页在 Key | record tag=7;两个usize | 当前生产 |
Test | 任意测试 Key | testing feature 下的 record tag=8 | 非生产 |
| relation/SLRU size | size sentinel 在 Key | Image 内层u32 LE | 最大u32页数;无 NULL |
| replication origin LSN | origin ID 在 Key | Image 内层u64 BE | LSN=0表示 drop |
| AUX path/content | 分类/hash 在 Key | Image 内层版本 1、u32 BE长度和 bytes | 删除最后一项得到空内层 Value |
| SQL boolean/int/numeric/text/date/JSON/array/UUID/自定义类型等 | SQL 类型和值都不进入 Pageserver Key | 作为 PostgreSQL tuple/index page 的不透明 bytes | SQL NULL bitmap、排序、collation、TOAST 由 PostgreSQL 页格式负责;全部共享 relation-page 编码族 |
这里没有独立 Pageserver SQL 类型注册表,也没有“每列 Value codec”。因此不能把 PostgreSQL tuple 内的类型编码冒充为 Pageserver KV Value 格式。
5.4 PostgreSQL 索引与 Pageserver 物理索引
| 索引含义 | Key | Value | 主记录定位符/唯一性 |
|---|---|---|---|
| PostgreSQL primary/unique/non-unique B-tree | 索引 relation 的物理页 Key | 原始 B-tree 页 Image/WAL delta | TID、排序、NULL 与唯一性都在 PostgreSQL 索引页;Pageserver 不解析,查询仍由 PostgreSQL 回 heap |
| PostgreSQL hash/GIN/GiST/SP-GiST/BRIN | 对应索引 relation 的物理页 Key | 对应 AM 的原始页 | 与上相同 |
| PostgreSQL expression/partial/include index | 定义在 catalog 页;主体仍是索引 relation 页 | 原始页 | predicate/expression/include 不进入 Pageserver Key |
| Pageserver delta layer B-tree | `18B Key | 8B LSN` | 5BBlobRef |
| Pageserver image layer B-tree | 18B Key | 5B blob offset | 定位该 layer LSN 下的完整 Image |
Delta B-tree 以(Key,LSN)字节序范围扫描。读取一个 Key 的目标版本时,从Key|LSN下界开始遍历并过滤请求 LSN 区间;遍历层次时从新到旧收集,Value redo 时再反转为 LSN 正序。
5.5 版本、删除、墓碑与分片
版本
- LSN 是 Pageserver 的 MVCC 版本轴,不是 SQL transaction ID 后缀。
- 同一 Key/LSN 在进入内存 index 时保留最后一次写入;早先追加的 raw bytes 可留在 ephemeral file,但不再被索引。
- Image 或
will_init=truedelta 截断对更老版本的依赖。 - Image layer 把一个 LSN 的完整状态物化,降低后续必须重放的历史链长度;这是机制性质,不代表本文提供了性能基准。
删除
- Dense relation/SLRU/database/two-phase range delete 最终调用的 in-memory tombstone 写入当前是 no-op。逻辑删除依赖 directory/size 新版本让旧 Key 不再可达;源码明确承认旧物理存储暂时泄漏。
- relation directory v2 的Value空 bytes 是 sparse tombstone;
r是存在标记。 - replication origin drop 的Value是 8B 全零
LSN::INVALID;读取扫描跳过它。祖先 detach 路径还可能产生 sparse 空 Image。 - AUX 删除会重写 bucket;bucket 无文件时内层Value为空。Key 仍可存在,不等同 SQL NULL。
- 没有用户数据 TTL。
分片与 namespace
- Tenant/shard/timeline 不重复编码在 18B Key,而是由本地路径、远端对象路径、layer 文件名及 summary 隔离。
- relation 普通页按
hash(relnode, block/stripe_size)分布;默认 stripe 是 2048 个 8KiB 页,即 16MiB。 - init fork 和非 relation page 的特殊对象落在 shard 0;relation size 等部分 metadata 可全 shard 保留。
- sharding 改变 Key 放在哪个 layer 集合,不改变 18B Key 或 Value bytes。
6. KV 数据库的启示
| 可借鉴设计 | 适用访问模式 | 直接收益 | 生效前提与代价 |
|---|---|---|---|
| 固定宽度、BE 复合 Key | 物理对象点查、连续 block 扫描 | 字节序即字段数值序;范围上下界简单 | 字段预算固定;tablespace 压缩限制扩展性 |
Key | LSNdelta 索引 | 多版本 point lookup、PITR | 同一 Key 的版本相邻且按时间排序 | 历史层多时读放大,需要 compaction/image |
| Image + redo delta 双 Value | 页级 WAL 系统 | 可以选择写完整页或小 delta;will_init 明确历史截断点 | 读取可能需要多条 delta 和 WAL redo |
| LSN 移到 image layer header | 同一快照覆盖很多 Key | image index Key 从 26B 降到 18B | 一个 layer 只能代表一个 materialization LSN |
| relation size 放在 block range 末尾 | extend/truncate 与尾页访问 | 同一 fork 的 block 与 size Key 相邻、单范围覆盖 | FFFFFFFF被保留,不能作为普通 block |
| 稀疏 metadata Key space | 可选对象、扫描目录 | missing/empty 可自然表示不存在 | empty Value 语义必须按对象区分;扫描仅限 metadata |
| hash Key + 完整路径 Value | 路径较长、需要固定 Key | Key 固定 18B;Value 可处理 hash collision | 更新 collision bucket 要读改写整个 Value;不支持按任意路径字典序扫描 |
| immutable layer + 内嵌 B-tree | 本地文件与对象存储共用格式 | layer 可原样上传;不依赖外部 KV 引擎 | compaction、GC、下载和多 layer 选择由 Pageserver 自己承担 |
| 物理页作为 Value | 托管完整 PostgreSQL 语义 | 无需复制 SQL 类型/索引 codec,能覆盖扩展类型 | 无法用 Pageserver Key 做 SQL 行级/索引值级查询或局部列更新 |
| 小目录整体 Value / 大目录拆 Key | 元数据规模分层 | 可在点查次数与单项重写量之间选择 | 需要迁移状态和双写/双读兼容逻辑 |