FoundationDB 核心只提供有序、事务性的键值 API。结构化记录、schema、二级索引放在 Layer。本篇只写 Record Layer 与内核的交界:Protocol Buffers 记录、Tuple / Subspace 键空间、二级索引与主数据同事务原子维护;不写查询优化器全书,也不展开 Document Layer。
版本锚定:Record Layer 官方 Overview / Extending / Indexes 文档;Chrysafis et al., FoundationDB Record Layer: A Multi-Tenant Structured Datastore, SIGMOD 2019;CloudKit 用法以该论文与 FoundationDB 官方博客宣布文为准。
一、Layer 边界:内核保证什么、Layer 负责什么
| 层级 | 职责 | 本系列 |
|---|---|---|
| FDB 核心 | 严格可串行化 KV、版本、冲突范围、TLog/Storage | 第 1–14、16–18 篇 |
| Record Layer | 记录类型、主键/二级索引、多租户 subspace、查询计划片段 | 本篇 |
| 应用 / CloudKit 等 | 产品 API、租户模型、运维策略 | 仅引用已发表机制 |
内核不知道「索引与行是否一致」——一致性来自 Layer 把对主记录与索引的写入放进同一个 FDB 事务。丢了这一层,直接双写两个 key 前缀且无事务,就会在冲突与重试下出现经典索引漂移。
flowchart TB
APP["Application or CloudKit API"]
RL["Record Layer: meta-data indexes planners"]
FDB["FoundationDB KV strict serializability"]
APP --> RL --> FDB
二、Protocol Buffers:记录的序列化契约
Record Layer 以 Protocol Buffers 描述记录类型与字段。官方 Overview 的用法模式是:
- 用 PB descriptor 构建
RecordMetaData; - 在
FDBRecordContext(对 FDB 事务的薄包装)里打开FDBRecordStore; saveRecord/ 查询后commit。
示意(结构摘自官方 Overview 文档模式,非本站运行输出):
final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(clusterFile);
try (FDBRecordContext context = database.openContext()) {
RecordMetaData metaData = RecordMetaData.build(TestRecords1Proto.getDescriptor());
FDBRecordStore recordStore = FDBRecordStore.newBuilder()
.setMetaDataProvider(metaData)
.setContext(context)
.setSubspace(new Subspace(Tuple.from("mydb", "mytest").pack()))
.build();
// build protobuf record, saveRecord(...), then:
context.commit();
}要点:
- Schema 演进由 meta-data 与 PB 兼容规则约束,不是 FDB 核心 DDL;
- 嵌套消息、repeated 字段等由 key expression / index type 解释,而不是 SQL 引擎;
- 主键本身也是一种索引:映射到存放整条记录的位置;二级索引则把主键拼到索引键后缀(Overview 原文语义)。
三、Tuple 与 Subspace:把逻辑库映射到有序键
FoundationDB 推荐用 Tuple
层把字符串、整数等编码为可比较的字节串,用
Subspace(键前缀)隔离逻辑数据库。Record
Layer 要求应用为每个 FDBRecordStore
提供唯一路径(Subspace 或
KeySpacePath)。
flowchart LR
TENANT["Tenant or logical DB prefix"]
STORE["Record store subspace"]
PRIM["Primary key space"]
IDX["Per-index subspaces"]
TENANT --> STORE
STORE --> PRIM
STORE --> IDX
| 结构 | 作用 |
|---|---|
| Tuple | 类型化、有序编码,避免手写字节序错误 |
| Subspace | 前缀隔离;租户 / 逻辑库互不覆盖 |
| Index subspace key | 每个二级索引在 store 内有唯一前缀(可为名字或紧凑整数) |
SIGMOD 2019 论文强调 Record Layer 无状态、面向大规模多租户:每个租户的状态(含索引)封进独立逻辑库。CloudKit 侧把「每用户每应用一个独立 record store」推到极端——论文表述为承载数十亿独立数据库、共享数千 schema(论文主张,非本站计数)。
官方 API 文档提醒:不要在同一事务里对同一
Subspace 并发打开两个
FDBRecordStore(行为未定义,见 Record Layer
issue 讨论);一个 context 一个 store 用法是安全默认。
四、二级索引:同事务维护
官方 Extending 文档写明:二级索引是 record store 内与该索引唯一关联的 subspace;在插入/更新记录的同一事务里更新,从而与主数据始终一致。
4.1 Value 索引形态
最基本的 value 索引:索引 subspace 内一条
KV,键后缀为「被索引值 ∥
主键」,值为空或辅助信息。查询时对目标值做前缀 range
scan,取出主键再回表。
sequenceDiagram
participant APP as Client
participant RL as Record Layer
participant FDB as FDB transaction
APP->>RL: saveRecord(R)
RL->>FDB: set primary key bytes
RL->>FDB: set or clear index entries
APP->>RL: commit
RL->>FDB: commit one transaction
FDB-->>APP: all or nothing
4.2 与内核事务模型的衔接
- 索引维护扩大写集与冲突范围:高扇出索引会放大 第 8 篇 的冲突概率;
- 聚合类索引(count/sum 等)官方实现可走 FDB atomic mutation,减少与其它聚合更新的读写冲突(FDB paper 亦提到 Record Layer 用原子操作维护聚合索引);
- 在线建索引(
OnlineIndexer)把回填与双写分期进行,细节见官方文档;本篇只强调:建成后的稳态维护仍在写事务内完成。
4.3 与 5 秒窗口、冲突范围的相互作用
Record Layer 写路径最终仍是 FDB 事务:
- 一次
saveRecord可能触及主键空间与多个索引 subspace,冲突范围是它们的并集; - 索引越多、扇出越大,越容易在高并发下触发
not_committed(第 8 篇); - 长事务(跨越多轮查询与思考时间)同样撞 5 秒上限(第 9 篇)。
因此 Layer 选型不是「免费获得关系库」:它把关系完整性问题翻译成 KV 冲突与事务切分问题。聚合索引用 atomic mutation,正是为了在计数器类热点上少付读写冲突成本——这是官方文档与 FDB paper 共同提到的工程点,不是应用层可有可无的小优化。
4.4 本篇不展开
查询计划树、RecordQueryPlan、Relational
Layer SQL 方言、自定义 IndexMaintainer
实现——官方 Extending
文档有完整入口。内核系列只需读者建立「索引 = 同事务 KV
维护」的心智模型。
flowchart TD
SAVE["saveRecord"]
SAVE --> PK["Write primary subspace"]
SAVE --> I1["Update index A subspace"]
SAVE --> I2["Update index B subspace"]
PK --> TXN["Single FDB commit"]
I1 --> TXN
I2 --> TXN
TXN --> OK["Commit OK: row and indexes agree"]
TXN --> AB["Abort: nothing partially visible"]
五、CloudKit:可核实来源中的机制案例
可引用链条:
- FoundationDB 官方博客 Announcing The FoundationDB Record Layer(2019):Record Layer 与 FDB 构成 Apple CloudKit 后端的骨干;并指向 SIGMOD’19 论文。
- Chrysafis et al., SIGMOD 2019:CloudKit 用 Record Layer 为海量应用提供强一致结构化存储;多租户下每用户每应用独立 store;借 Layer 特性提供更丰富 API 与更强语义,同时降低维护开销。
- 相关公开论文 CloudKit: Structured Storage for Mobile Applications(VLDB 2018)描述 CloudKit 服务本身;与 Record Layer 论文交叉引用,但不在本篇复述产品指标。
本篇不引用无版本的论坛吞吐、也不把 CloudKit 内部未公开实现细节写成事实。对读者的可迁移结论只有:
- 在严格可串行化 KV 上叠记录层,是经过顶会论文与生产系统验证的路径;
- 多租户隔离优先落在 subspace / 逻辑库,而不是削弱核心隔离级别;
- 「数十亿独立数据库」是论文对 CloudKit 部署形态的描述,选型时当作存在性证明,不当作自己集群的容量公式。
5.1 为何 CloudKit 例子值得写进内核系列
本系列主线是 FDB 内核。CloudKit 出现在此,只为钉住一个常被问到的产品问题:「只有 KV,上面怎么长出应用数据模型?」SIGMOD 2019 的回答是:
- 核心保持最小 API(反特征 / anti-features 哲学与官方文档一致);
- 记录、索引、多租户放进无状态 Layer;
- 用 FDB 事务把跨 subspace 更新绑成原子单位。
这与 第 18 篇「何时选 FDB」直接相关:若团队需要的是 SQL 全家桶,TiKV/TiDB 路径可能更短;若需要严格可串行化 KV + 自研或现成 Layer,FDB 路径与 CloudKit 论文同构。
六、学术谱系、工程间隙与开放问题
谱系:有序 KV + 客户端编码(FDB Tuple 文档)→ Record Layer SIGMOD 2019(记录与索引)→ 应用层(CloudKit VLDB 2018 / QuiCK SIGMOD 2021 等,本系列不展开队列层)。
工程间隙:论文展示的是 Apple 规模多租户与 schema 共享;自建集群若只有少量 database,Record Layer 的收益主要在「事务性索引与 PB schema」,而不是自动获得 CloudKit 级运维经验。查询能力弱于完整 SQL 引擎——需要 SQL 时应评估其它 Layer 或外部查询引擎,而不是假定 Record Layer 等于 PostgreSQL。
开放问题:
- 高冲突二次索引下,应用侧如何切分记录与索引键,才能与 5 秒事务上限共存?
- 在线建索引与集群恢复(第 13 篇)交错时的运维契约,公开文档仍偏「工具用法」而非完整故障手册。
- 强事务 KV 是否应把更多数据模型收进核心,还是坚持 Layer——第 18 篇继续收束。
6.1 与内核系列其它篇的接口表
| 内核机制 | Record Layer 如何依赖 |
|---|---|
| 严格可串行化提交 | 主记录与索引同事务可见 |
| 冲突范围 / OCC | 索引键进入读写冲突集合 |
| 原子操作 | 聚合索引维护 |
| Subspace 有序扫描 | 索引前缀 range 查询 |
| 5 秒 / 大小限制 | 约束单次 save 与查询事务切分 |
| 恢复 epoch | Layer 无独立恢复;跟随 FDB 集群 |
Layer 不实现自己的 WAL:崩溃与世代切换语义全部是 FDB 的。这是「电池不附带」设计的直接推论——也是运维排障时必须回到第 13、17 篇,而不是只查 Java 层日志的原因。
七、常见误解
误解一:Record Layer 是 FDB 进程内置的 SQL
引擎。
它是客户端库(及周边组件);存储与事务仍在 FDB
集群。进程角色图里不会出现「Record Layer Server」。
误解二:二级索引由 Storage Server
自动维护。
索引是 Layer 写入的普通键;原子性来自 FDB
事务,不是存储引擎触发器。
误解三:用了 CloudKit 同款 Layer 就自动有
CloudKit 规模。
规模来自租户切分、schema 管理、硬件与运维;Layer
只提供机制。
八、小结
三句话小结:
- Record Layer 在 FDB KV 之上用 PB 定义记录,用 Tuple/Subspace 隔离逻辑库与索引前缀。
- 二级索引与主记录在同一 FDB 事务中维护,一致性不依赖存储侧触发器;聚合索引可借助原子操作降低冲突。
- CloudKit 案例以 SIGMOD 2019 与官方宣布文为界;本系列不把未核实产品数字写成通用容量结论。
参考资料
核心论文
- Chrysafis C., et al. FoundationDB Record Layer: A Multi-Tenant Structured Datastore. SIGMOD 2019.
- Zhou J., et al. FoundationDB…. SIGMOD 2021.(核心 KV 与原子操作、Layer 关系)
官方文档 / 源码
- Record Layer Overview
- Extending the Record Layer(索引 subspace 同事务更新)
- FoundationDB Tuple / Subspace
开发者文档;
fdb-record-layer源码。
辅助
- Announcing The FoundationDB Record Layer. foundationdb.org blog, 2019.(CloudKit 骨干关系,B 级宣布文 + 指向 A 级论文)
上一篇:确定性模拟:FDB
的工程落点
下一篇:运维与容量:角色资源画像
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【FoundationDB 内核】Unbundled 全景:事务、日志与存储为何拆开
以 SIGMOD 2021 为锚点,厘清 FoundationDB 控制面与数据面角色分工,说明一次事务穿过 Proxy、Sequencer、Resolver、TLog、Storage 的路径,并与站内 distributed/39、tikv-htap 划清边界。
【FoundationDB 内核】Client API 与事务模型:冲突范围、自动重试与 5 秒窗口
钉住 FoundationDB 客户端事务契约:读写缓冲、冲突范围、@transactional 自动重试,以及 5 秒上限如何约束应用设计——机制证明留给第 8–9 篇。
【FoundationDB 内核】Coordinator 与集群配置:Paxos 边界、招募与恢复入口
说明 Coordinators 如何用磁盘 Paxos 持久化事务系统配置、Cluster Controller 如何招募角色,并划清『配置共识』与『用户 mutation 共识』的边界。
【FoundationDB 内核】Proxy:Grv 与 Commit、批处理与 read version
拆解 FoundationDB 7.x 中 Grv Proxy 与 Commit Proxy 的分工:如何发放 read version、如何编排提交,以及批处理与 Ratekeeper 如何保护 Sequencer。