OceanBase 数据库架构图文档(源码视角)

基于 oceanbase/oceanbase master 源码梳理:整体架构 · SQL 引擎 · 存储引擎(LSM-Tree)· 事务(MVCC+2PC)· PaLF/Multi-Paxos · 索引体系 · RootService
所有路径为仓库相对路径,例如 src/logservice/palf/log_state_mgr.h。流程图由 Mermaid 渲染。

1. 整体架构与定位

OceanBase 是分布式 Shared-Nothing 关系型数据库。每个节点对等运行单个 observer 进程 (入口 src/observer/main.cppObServer::start()),节点内自带 SQL、存储、事务、分布式日志四大引擎, 基于 Multi-Paxos(在 PaLF 中实现)保证副本一致性与高可用(RPO=0,RTO<8s)。

关键特性(摘自 README.md / docs/docs/en/architecture.md): 透明水平扩展 HTAP 单引擎 向量检索 MySQL 兼容 RPO=0 / RTO<8s 多租户隔离

1.1 集群拓扑

flowchart LR A1["应用 / 客户端"] -->|MySQL 协议| P1["ObProxy
无状态路由"] P1 --> Z1 P1 --> Z2 P1 --> Z3 subgraph Z1["Zone 1(机房 A)"] O1A["OBServer A"] O1B["OBServer B"] end subgraph Z2["Zone 2(机房 B)"] O2A["OBServer C"] end subgraph Z3["Zone 3(机房 C 或仲裁)"] O3A["OBServer D / Arbiter"] end Z1 <-->|Paxos 复制| Z2 Z2 <-->|Paxos 复制| Z3 Z1 <-->|Paxos 复制| Z3 classDef zone fill:#eaf3ff,stroke:#2a6df4,stroke-width:1px; class Z1,Z2,Z3 zone;

1.2 数据组织:Tenant → LS → Tablet → SSTable

flowchart TB CL["Cluster"] --> T1["sys 租户(系统元数据)"] CL --> T2["Meta 租户(每用户租户一个)"] CL --> T3["User 租户(MySQL/Oracle)"] T3 --> LS0["LS 1 (SYS LS)"] T3 --> LS1["LS 1001"] T3 --> LS2["LS 1002"] LS1 --> TAB1["Tablet 200001"] LS1 --> TAB2["Tablet 200002"] TAB1 --> MT["MemTable (KeyBtree, MVCC)"] TAB1 --> MN["Mini SSTable*"] TAB1 --> MI["Minor SSTable*"] TAB1 --> MJ["Major SSTable"] LS1 -. 同步 .-> CLOG["CLOG 流(PaLF)"] CLOG -. Multi-Paxos .-> CLOG2["CLOG 流副本 x3"] classDef ls fill:#fff3df,stroke:#e08a00; class LS0,LS1,LS2 ls;
核心抽象:Log Stream(LS)。Tablet 不再像 OB 1.x/2.x 那样每分区一份 Paxos 组; 3.x/4.x 起一台机器一个租户内拥有少量 LS,多个 Tablet 共享同一个 LS 上的 Multi-Paxos 日志流(见 src/logservice/palf), 显著降低 Paxos 心跳/选主开销。Tablet 通过 LS 之间的「迁移」实现负载均衡。

2. 组件全景与源码布局

仓库根目录的关键模块(节选 src/):

目录角色说明
src/observerOBServer 进程框架 包含进程入口 main.cpp、网络层 net/、MySQL 协议 mysql/、 多租户运行容器 omt/、RPC 派发 ob_srv_deliver/xlator
src/sqlSQL 引擎 parser → resolver → rewrite → optimizer → code_generator → engine → executor,外加 PX 并行 engine/px 与数据访问层 das,计划缓存 plan_cache
src/storage存储引擎(基于 LSM-Tree) MemTable memtable/、SSTable / 微块 blocksstable/、合并 compaction/、 访问路径 access/、LS 管理 ls/、事务 tx/、tablet 元数据 meta_mem/、 列式 column_store/、检查点 checkpoint/
src/logservice分布式日志服务 PaLF(Multi-Paxos 实现)palf/、回放 replayservice/、应用回调 applyservice/、 归档 archiveservice/、CDC cdcservice/、选举 palf/election/
src/rootserverRootService(集群管控) DDL、负载均衡 balance/、容灾 ob_disaster_recovery_*、 冻结/合并调度 freeze/、备份 backup/。RootService 本身也是一个 LS 的 Leader。
src/share共享基础 Schema、location cache、partition table、object pool 等。
src/plPL/SQL 引擎Oracle/MySQL 兼容的存储过程。
src/objit表达式 JITLLVM 表达式编译。
src/plugin插件机制FTS、向量、JSON 插件挂载点。
deps/oblib基础库容器、内存、网络、RPC、协议、加密。

2.1 组件级关系图

flowchart LR CLI["客户端/ObProxy"] --> NET["MySQL 协议层
(observer/mysql, observer/net)"] NET --> OMT["多租户运行容器 OMT
(observer/omt)"] OMT --> SQL["SQL 引擎
(src/sql)"] SQL --> PXENG["PX 并行执行
(sql/engine/px)"] SQL --> DAS["DAS 数据访问
(sql/das)"] DAS --> STO["存储引擎
(src/storage)"] STO --> MT["MemTable"] STO --> SST["SSTable / 微块"] STO --> COMP["Compaction"] SQL --> TXN["事务引擎
(storage/tx)"] TXN --> LOG["分布式日志 PaLF
(src/logservice)"] STO --> LOG LOG --> APP["Apply / Replay
(applyservice/replayservice)"] RS["RootService
(src/rootserver)"] -.元数据/调度.-> OMT RS -.DDL/负载均衡.-> SQL RS -.LS 调度.-> LOG classDef core fill:#eaf3ff,stroke:#2a6df4; class SQL,STO,TXN,LOG core;

3. OBServer 进程内分层架构

一个 OBServer 进程内的整体分层(参考 src/observer/ob_server.hob_srv_network_frameob_srv_deliverob_srv_xlatoromt/):

flowchart TB subgraph N["网络与协议层"] NF["ObSrvNetworkFrame
(libeasy/RPC)"] MP["MySQL 协议
observer/mysql"] XL["ObSrvXlator
(把 packet 翻译为 Task)"] DV["ObSrvDeliver
(投递到租户队列)"] end subgraph M["多租户容器 OMT"] TENANT1["Tenant 1001 资源池
(CPU/MEM 配额)"] TENANT2["Tenant 1002 资源池"] SYS["sys Tenant"] end subgraph K["内核引擎"] SQL["SQL 引擎"] TX["事务引擎"] STO["存储引擎"] LOG["PaLF 日志服务"] PL["PL/SQL"] end subgraph SVC["进程级常驻服务"] HB["ObHeartbeat
(observer/ob_heartbeat)"] LSSVC["ObLSService"] LOC["LocationService"] SCH["Tablet/Compaction Scheduler"] GTS["Timestamp / GTS"] SCHEMA["Schema Updater"] end NF --> XL --> DV MP --> XL DV --> M M --> K K --> SVC classDef hi fill:#fff3df,stroke:#e08a00; class K hi;

启动流程(ObServer::start() 简化):

main() 
  ├─ ObServer::init()  // 解析 cfg、初始化 schema/location/log_block_mgr 等
  └─ ObServer::start()
     ├─ start ObSrvNetworkFrame  // 监听 2881/2882
     ├─ start OMT(多租户线程池)
     ├─ start ObLSService / ObTenantFreezer / ObCheckpointService
     ├─ start LogService (PaLF) + ApplyService + ReplayService
     ├─ register heartbeat to RootService
     └─ start RootService(若本机为 RS Leader 副本)

4. SQL 引擎流程

SQL 引擎入口 src/sql/ob_sql.h ObSql::stmt_query()。整条 pipeline:

flowchart LR IN["SQL 文本"] --> PC{"Plan Cache 命中?
(sql/plan_cache)"} PC -- 命中 --> EXEC PC -- 未命中 --> PAR["Parser
(sql/parser, Bison/Flex)"] PAR --> RES["Resolver
(sql/resolver)
语义/Schema 绑定"] RES --> RW["Rewrite
(sql/rewrite)
子查询展开/谓词下推/视图合并"] RW --> OPT["Optimizer
(sql/optimizer)
代价模型 + 动态采样"] OPT --> CG["Code Generator
(sql/code_generator)
生成 ObPhysicalPlan & 向量化表达式"] CG --> CACHE["写回 Plan Cache"] CACHE --> EXEC["Executor
(sql/executor + engine/*)
火山/向量化算子树"] EXEC --> DAS["DAS
(sql/das)"] EXEC --> PX["PX 并行调度
(sql/engine/px)"] DAS --> SE["存储引擎
(src/storage)"] PX --> DAS classDef hot fill:#eaf3ff,stroke:#2a6df4; class OPT,EXEC hot;

4.1 Plan Cache 与软硬解析

语种路径 src/sql/plan_cacheObPlanCache 用 SQL 文本(参数化后)+ schema_version + literval 模式为 key 缓存 ObPhysicalPlan

4.2 SPM:Manual vs Auto Baseline

SQL Plan Management(src/sql/spm)维护 evolved baseline。当优化器选择新计划且代价优于现有 baseline 阈值,会被加入 evolve 队列;DBA 或自动任务执行 verify 后确认升级为新 baseline。Outline(sql/ob_outline.h)和 UDR''udr` 提供人工 pin 计划能力。

4.3 优化器(基于代价的 CBO)

4.4 代码生成与向量化

4.5 执行引擎算子家族

算子工厂在 sql/engine/ob_operator_factory.{h,cpp}。生成两种模式:

4.6 谓词下推到存储与其他高级特性

· SQL 引擎
Q1:Plan Cache 怎么决定命中?key 是什么?
A1:key = (parameterized SQL fingerprint, schema_version, environment/charset) + 必要的 literal 模式。其中"参数化"使用 OB-SQL Parser 的 literal replacement,把 IN 列表等标准化为 ParamMap;schema 变更则使全部相关 plan 失效(ObPlanCache::flush_cache by `refine_task`)。
Q2:向量化执行一坨表达式里有一个 if 不走 vec,会不会降级?
A2:每个 ObExpr 都有 evaleval_batch 两种入口。如果某个表达式只支持 row 级(如 PL SPI 调用),CG 会保留变体并在 batch 角标 fallback 为逐行调用。代价是劣化但正确;不会全盘退化为火山式。
Q3:CBO 如何为分布式查询选 DOP?多 DFO 下选择算子分布容易产生网络内存打绑吗?
A3:DOP 基于可用 CPU quota(租户 parallel_max_servers + pool CPU)、数据量估算、小表 broadcasting 决定;Exchange 分布类型选择按 join 类型和表分布 hash/pkey/broadcast,并与 hash subpartition 的 redistribute cost 模型比较。Admission 在执行入口(PX Coord)拦截不合规 DOP。
Q4:JIT 实际跑不跑?什么场景触发?
A4:JIT 默认对超复杂表达式开(big expr depth、重复次数高、单 plan 累计 exec rows > 阈值),代价高得多 + 调通环境需要 ob_enable_jit + objit not stripped。OLTP 一般关闭,AP 大 query scene 才 enabled。LLVM IR compile 需要~几十 ms,因此 sample 入库 first/warm-up 时不受限于。

4.2 执行引擎

算子工厂在 sql/engine/ob_operator_factory.{h,cpp}。代码生成两种模式:

主要算子族:

家族代码位置代表算子
聚合engine/aggregateHashGroupBy / MergeGroupBy / HashDistinct(含向量化 vec 版本)
Joinengine/joinHashJoin / NestedLoop / MergeJoin / NLJ_Vec
排序/集合engine/sort, set外排序 + 落盘、UNION/INTERSECT/EXCEPT
窗口/CTEengine/window_function,recursive_cteWindow、递归 CTE
子查询engine/subquerySubPlan Filter、Subplan Scan
表/扫描engine/tableTable Scan、Direct Receive、Function Table、向量索引扫描
DMLengine/dmlInsert/Update/Delete/Merge/Replace
PDMLengine/pdml并行 DML
并行engine/pxPX Coord、DFO、Granule、Exchange

5. DAS 与 PX 并行执行

5.1 DAS(Data Access Service)

DAS 是 SQL 与存储之间的数据访问中间层(src/sql/das),把每个 Scan/DML 拆分成对各 Tablet 的 ObDasTask, 并按数据位置远程/本地下推。核心类:ObDataAccessServiceObDASRefObDASScanOpObDASInsert/Update/Delete/LockOp、定位 ObDASLocationRouter、重试 ObDASRetryCtrl

flowchart LR EX["Executor 算子
TableScan / DML"] --> REF["ObDASRef"] REF --> SCAN["ObDASScanOp / DmlOp"] SCAN --> LOC["ObDASLocationRouter
(tablet → server)"] LOC -->|local| LOCAL["本地存储 ObAccessService"] LOC -->|remote| RPC["DAS RPC
ob_das_rpc_proxy"] RPC --> REMOTE["远端 OBServer 上的 DAS handler"] REMOTE --> LOCAL2["远端 ObAccessService"] classDef k fill:#eaf3ff,stroke:#2a6df4; class SCAN,LOC k;

5.2 PX(Parallel eXecution)

PX 实现 MPP 风格多机并行。源码在 src/sql/engine/px。关键概念:

flowchart TB Q["QC: Query Coordinator
(根 OBServer)"] --> S1["SQC 1
(OBServer A)"] Q --> S2["SQC 2
(OBServer B)"] Q --> S3["SQC 3
(OBServer C)"] S1 --> W1["PX Worker x N
处理 Granule"] S2 --> W2["PX Worker x N"] S3 --> W3["PX Worker x N"] W1 -.DTL Exchange.-> W2 W2 -.DTL Exchange.-> W3 W1 --> R["Receive"] R --> Q

6. 存储引擎:LSM-Tree + Tablet

OceanBase 的存储引擎是准 LSM-Tree 架构,对应代码 src/storage

6.1 写入路径

flowchart LR SQL["SQL DML"] --> TS["ObTransService
(storage/tx)"] TS --> CTX["ObPartTransCtx (per LS)"] CTX --> RD["MemtableCtx + Redo Generator
(memtable/ob_redo_log_generator)"] RD --> WMT["写 MemTable
(memtable/mvcc/ob_keybtree + ob_mvcc_row)"] RD --> RLOG["生成 redo log entry"] RLOG --> PALF["PaLF append (Multi-Paxos)
logservice/palf"] PALF -- majority ack --> CB["on_success → 提交回调"] CB --> CTX WMT -. snapshot 可见性 .-> RD2["读路径 ObAccessService"]

6.2 读取路径(合并多版本 + 多 SSTable)

位于 src/storage/access。读需要把同一行在 MemTable 与多层 SSTable 的版本「融合」:

flowchart TB Q["TableScan / Get"] --> AS["ObAccessService"] AS --> ITER["ObMultipleMerge
(ObMultipleScanMerge / GetMerge)"] ITER --> M0["MemTable iterator
(memtable/ob_memtable_iterator)"] ITER --> M1["Mini SSTable iter"] ITER --> M2["Minor SSTable iter"] ITER --> M3["Major SSTable iter"] M1 -.索引树.-> IB["index_block
(blocksstable/index_block)"] M2 -.索引树.-> IB M3 -.索引树.-> IB ITER --> LOSER["LoserTree 多路归并
+ MVCC 可见性过滤 (snapshot version)"] LOSER --> ROWS["按 rowkey 输出最新有效版本"] AS --> CACHE["BlockCache / RowCache
BloomFilter (blocksstable/ob_bloom_filter_*)"]

6.3 微块格式与列式

6.4 缓存层

缓存路径用途
BlockCacheblocksstable/ob_block_cache_*缓存解码后的微块
RowCacheblocksstable/ob_row_cache_*点查 row 级缓存
FuseRowCacheblocksstable/ob_fuse_row_cache融合多 SSTable 的结果行
BloomFilterblocksstable/ob_bloom_filter_*SSTable 上 rowkey 是否存在

6.5 MemTable 内部结构(常点)

数据源码:src/storage/memtable。每行结构:

ObMemtable
 ├─ ObQueryEngine         // 用 KeyBtree + HashIndex 索引
 │     ├─ ob_mvcc/ob_keybtree.{h,cpp}    // B+树,叶节点为 ObMvccRow*
 │     └─ ob_mt_hash.h                   // JIT hash 索引适配点查
 ├─ ObMvccEngine          // 行级 latch + write handler
 │     ├─ ObMvccRow      // 同 rowkey 多版本链表 ObMvccTransNode
 │     ├─ ObMvccTransNode // 一个版本:trans_version + data
 │     └─ row_latch.h    // 行写锁
 ├─ ObRedoLogGenerator     // 与 clog 衔接
 ├─ ObRowCompactor         // 行级 flush
 └─ ObMemtableCtx          // TX 局部上下文:回调链 + undo 链
flowchart LR subgraph MT["ObMemtable"] KE["ObQueryEngine
(KeyBtree + 可选 HashIndex)"] KE --> R1["ObMvccRow
rowkey=k1"] KE --> R2["ObMvccRow
rowkey=k2"] R1 --> N11["TransNode v=100
committed"] R1 --> N12["TransNode v=150
uncommitted"] R2 --> N21["TransNode v=120
committed"] end subgraph CTX["ObMemtableCtx"] CB["txn callback 链表
支持回滚 + 提交顺序"] end MT -.存储.-> TXCB CB --> R1 CTX --> CB style MT fill:#fff3df,stroke:#e08a00;

写冲突处理

6.6 MVCC 可见性算法(必答)

读时刻保存 read_snapshot_scn(来自 ObTxReadCtx,可能取自 GTS / Standby Timestamp / Pure Local)。对每个版本 n

if n.trans_version <= read_snapshot_scn AND
   n.commit_version 已经通过 PA + TxData落盘稳定:
    可见 n
elif n.write_seq < = own_tx_seq (是自己写的):  // self-modification
    在 curr_stmt 内可见
else:
    skip n

读会按 rowkey 融合:MemTable(活跃 / frozen) + 各层 SSTable(Mini / Minor / Major) → ObMultipleMerge 通过 LoserTree 多路归并,结合 bloom filterindex filterpush down filter 提前裁剪。

6.7 隔离级别实现

隔离级别实现要点出入点
READ-COMMITTED每条 SQL 重新申请 snapshot_scn(来自 GTS)ObTransService::get_read_snapshot
REPEATABLE-READ事务开始拿一次 snapshot_scn,复用整个事务ObSqlCtx::isolation_
SERIALIZABLEsnapshot SCN + 写时检查 write-write 冲突(等于 SI + 首冲突回滚)ob_row_conflict_handler
弱一致读 / Standby Read用 standby_time_service 或某个 PAlog位点回放后的 SCNob_standby_timestamp_serviceob_tx_sby_read_define

6.8 文件分布与 IO 路径

OB 不再像一维文件系统(一行一文件),而是在指定目录(squarerouter/ob_file_system_router.h)下按 tenant/ls/tablet 分层;macro block 由 ob_block_manager 以一组大小为 2MB 的 PGE 文件统一划块回收,写入通过 libeasy / io_uring 提交 IO。

· 存储引擎
Q1:MemTable 是 B+树还是 SkipList?为什么选这个?
A1:OB MemTable 用 B+树(KeyBtree)memtable/mvcc/ob_keybtree),并可选 hash 索引(ob_mt_hash.h)作为点查加速。选 B+树的理由:1)多版本追加写入需要顺序扫描性能稳定;2)缓存的 chunk 集中便于复用;3)回放 / dump 时天然顺序读 rowkey。RocksDB 老牌 SkipList 是为了无锁插入,OB 用 latch + B+ 树 在长 scan 上占优。
Q2:MemTable 写入和持久化(clog)的关系是?clog 是否足够丢?
A2:写 MemTable 之前先写 redo log entry 到 PaLF(ObRedoLogGenerator::submit);redo log 多 Buff写后回调成功后再"提交到" MemTable 内存结构。即使 MemTable 还在内存中刷盘前进程崩溃,重启从 clog replay 即可恢复。Memtable 本身 flush 出的小 sstable 文件 + clog 双重持久化,clog的多数派可追保住 RPO=0。
Q3:MVCC 多版本去读时,如何避免读太多 sstable?
A3:Major 周期性举措把"老版本"提纯为一个大 SSTable:Testtable 中充斥的 multi-version(所有 <= snapshot_version 的)版本只在 Major 中保留一行;mini/minor 只保留仍可能被在读 snapshot 上追溯的版本。BloomFilter + RowCache + FuseRowCache + Index Filter 完整地减少块读取。
Q4:CompactionFilter 与 Major/Minor 的关系?谁清理行级 GC?
A4:CompactionFilter 只在合并时运行;只在 Major / Medium / Minor 上插入,Mini 几乎只用还原(无 GC)。它检查"事务 commit_version 早于 snapshot_version 且已 not-locked",则丢行;Mini 不做安全丢弃(因为只跨越一个 MemTable 固定范围,降不经过 commit-version решений)。
Q5:如果 RTO<8s 是真的,那么崩溃后必然要回放 clog,一般回放多久?
A5:OB 对崩溃分阶段:(1)进程拉起/分区恢复包含从 clog 拉日志并回放(ob_replay_handler);(2)只读 LS 上的恢复操作 + reconfirm会启动恢复。并行回放(多 LS 线程)、多 worker 加速;8s 对故障的短极限目标是指 leader 损失场景下 lease 过期 + 准连锁 reconfirm 后新 leader 接写。并非进程全量重启时间。

7. 三级合并(Mini / Minor / Major / Medium)

合并类型在 src/storage/compaction/ob_compaction_util.h 定义:

enum MergeType {
  INVALID_MERGE_TYPE,
  MINOR_MERGE,            // 多个 Mini -> 更大 Mini
  HISTORY_MINOR_MERGE,
  META_MAJOR_MERGE,
  MINI_MERGE,             // MemTable flush -> Mini SSTable
  MAJOR_MERGE,            // 全量基线合并(每日)
  MEDIUM_MERGE,           // 中型增量合并
  ...
  CONVERT_CO_MAJOR_MERGE, // 行存 Major -> 列存 CG SSTables
  INC_MAJOR_MERGE,        // 增量 Major
};

7.1 合并层级与触发

flowchart LR W["写入"] --> MT["MemTable"] MT -- freeze/flush --> M1["Mini SSTable"] M1 -- "Minor Merge" --> M2["Minor SSTable"] M2 -- "Medium / Major" --> M3["Major SSTable (基线)"] M3 -- 每日全量 --> M3 RS["RootService 调度
rootserver/freeze & compaction"] -.触发.-> MT RS -.触发.-> M3 classDef hot fill:#fff3df,stroke:#e08a00; class M3 hot;

7.2 合并的内部结构

Major/Medium 的「全局一致性」通过 snapshot_version + medium_list 实现,确保所有副本 Major 后产生相同基线版本(见 ob_medium_list_checkerob_extra_medium_infoob_medium_compaction_mgr)。

7.3 合并类型逐项详解(源码:ob_compaction_util.h / ob_schedule_tablet_func / ob_basic_schedule_tablet_func)

类型阶段输入输出触发条件是否停写关键代码
MINI_MERGE实时1 个冻结 MemTable1 个 Mini SSTable(增量基线 0) MemTable 冻结后立即触发;阈值受 ob_tenant_freezer 控制(memstore 内存 / 行数 / 时间) 否(后台) ob_tablet_memtable_mgr::schedule_mini_mergeob_mini_merge
MINOR_MERGE近实时N 个 Mini SSTable + 历史 minor1 个更大的 Minor SSTable Mini 数达到 minor_compact_trigger(默认 2~3) ob_schedule_tablet_func::schedule_minor_mergeob_partition_merge_iter
HISTORY_MINOR_MERGE后台历史 Minor + Minis归并历史 清理 stale minor、控制 minor 层数HISTORY_MINOR_MERGE 分支
MEDIUM_MERGE中型增量Major + Minor/Mini(range 内)新 Medium SSTable RootService 在 medium_list 为每个 tablet 颁发 MediumCompactionInfo;watermark 之上的 minor/mini 都是被合并对象 ob_medium_compaction_mgrob_medium_loopob_medium_compaction_func
MAJOR_MERGE全量基线当前 Major + 其上所有增量1 个新 Major SSTable(全表 flat) 全局冻结(ObTenantFreezer::do_major_freeze)后,RootService 下发到所有 tablet;所有副本按相同 snapshot_version 做否(不停写) ob_root_minor_freezeob_tenant_freezerob_basic_schedule_tablet_func::schedule_major_mergeob_partition_merge_fuser
META_MAJOR_MERGE全量meta tablet(如 LOB/aux)新 meta SSTable随 Major 联动META_MAJOR_MERGE 分支
CONVERT_CO_MAJOR_MERGE行列转换行存 Major列存 CG(Column Group)SSTables 用户切列存策略 / 列存化升级读路径切换column_store/cs_encoding/ob_dag_macro_block_writer
INC_MAJOR_MERGE增量基线Major + 增量在新 Major 中保留部分增量 大表渐进合并ob_progressive_merge_helper
MDS_MINI / MDS_MINOR多源数据multi_data_source 制数据MDS 模块自带 mini/minorob_mds_filter_infomulti_data_source/
BATCH_FREEZE_TABLETS批量冻结多小 tablet memstore同时冻结节省 clog 流量ob_batch_freeze_tablets_dag

7.4 MemTable 冻结的条件与流量控制

ObTenantFreezerstorage/tx_storage/ob_tenant_freezer.h)控制租户级写入门槛:

7.5 调度优先级与 DAG Ranker

所有合并统一进入 DAG 调度器share/scheduler/ + compaction/ob_compaction_dag_ranker):每个 tablet 合并抽象为 dag(含多 task)。ObDagRanker 根据如下维度打分:

flowchart LR subgraph SL["Schedule Loop (per tenant)"] IC["ObCompactionScheduleIterator
遍历 tablet"] IC --> RK["ObCompactionDagRanker
打分/排名"] RK --> SCH["ScheduleDag 系列函数
调度 winner tablet"] SCH --> DAGF["ObPartitionMergeDag
含 ParaMerge 进度"] DAGF --> TH["DAG Thread Pool
(merge thread)"] end FF["ObTenantFreezer
检测 memstore 阈值"] --> IC RS["RootService
medium_list / major_freeze"] --> IC TH --> WR["ob_data_macro_block_merge_writer
输出新 macro blocks"] WR --> NEW["新 SSTable 挂到 TabletMeta"] classDef hl fill:#fff3df,stroke:#e08a00; class RK,DAGF hl;

7.6 合并过程内部细节(常问)

(a) Iterator、Fuser、Merger 三件套

(b) Compaction Filter(行级 GC)

(c) Progressive / 渐进式合并

ObProgressiveMergeHelperob_progressive_merge_helper.h):当 Major 数据量巨大、一刀切会打垮 IO 时,按 macro block 「轮转合并」,由 progressive_merge_round 控制剩余未合并 range,逐 round 推进。INC_MAJOR_MERGE 类型即应用此机制。

(d) Medium List 与一致基线

sequenceDiagram participant RS as RootService participant L as ObLS (leader) participant FT as MediumListChecker participant MM as MediumCompactionMgr RS->>L: schedule_medium (medium_info v_k) L->>MM: append medium_info_k FT->>MM: check medium_list 单调&连续 FT->>L: 校验通过则 schedule MEDIUM_MERGE L->>FT: merge 完成 -> 推进 medium_watermark Note over RS,L: Major 时所有副本用同一个 snapshot_version
且 medium_list 完全相同 -> 全集群同一基线

7.7 失败与回滚

· 合并策略
Q1:为什么 OB 用「全局 Major」而不是 RocksDB 那种 tiered/leveled?如何保证查询不读多百个 Minor?
A1:因为 OB 优先 HTAP、查询性能要求 SSTable 层数尽量少。MemTable→Mini→Minor 进行局部 tiered;同时每日 Major 把基线重铺回收空间;再叠加 Medium 在中型增量下提前逼近 Major。Mini 数达到 minor_compact_trigger(2~3)就 minor;Minor 数不会无限堆叠。
Q2:Major 全量合并对在线写有影响吗?是否需要停写?
A2:不需要停写。MemTable 持续可写;Major 在后台 dag 上用 freeze 时刻的 snapshot_version 作为读起始点,对 Active MemTable 无影响。DAG 线程池资源 + schedule_status_cache 防资源打满。
Q3:为何称为「全局一致基线」?多副本 Major 后如何保证一致?
A3:RootService 在 medium_list 为每个 tablet 颁发 MediumCompactionInfo(snapshot_version + schema_version + medium_type 等)。所有副本 Major/Medium 不可超过 medium_watermark;完成后写相同的 extra_medium_info。因此多副本 Major 完成后产出的新 SSTable 的 rowkey 范围、版本谱、schema_version 三者一致;合并输出的字节流上方相等(同 macro block hash 又可验)。
Q4:Major 的输入为什么不止是 Major?会不会与 Minor 并发冲突?
A4:Major 的输入包括当前 Major SSTable + 所有其上的 Minor / Mini / MemTable。同一 tablet 上 Major 同一时间点不允许并发其他合并(见 DAG 状态机 ob_dag_macro_block_writer)。Scheduler 为了吞吐会在不同 tablet 间并发。
Q5:渐进合并(Progressive / INC_MAJOR)vs 普通 Major 区别?
A5:Progressive 不在单轮内合并全部 macro block,而是按 round 推进,每轮只换一部分宏块。INC_MAJOR 是为超大表在多次拿到 medium_info 后"增量收口"成新基线,利于减少一次性 IO 突发。

8. 事务、MVCC 与 2PC 状态机

8.1 事务引擎总览

代码位置:src/storage/txsrc/storage/tx_storage。核心类:

8.2 MVCC

8.3 两阶段提交(2PC)状态机

2PC 角色与状态见 src/storage/tx/ob_committer_define.h

enum class Ob2PCRole  { UNKNOWN=0, ROOT, INTERNAL, LEAF };    // 树形分布式事务
enum class ObTxState   : uint8_t {
  UNKNOWN=0, INIT=10, REDO_COMPLETE=20, PREPARE=30,
  PRE_COMMIT=40, COMMIT=50, ABORT=60, CLEAR=70, MAX=100
};
enum class ObTwoPhaseCommitLogType  { OB_LOG_TX_INIT, OB_LOG_TX_COMMIT_INFO,
  OB_LOG_TX_PREPARE, OB_LOG_TX_PRE_COMMIT, OB_LOG_TX_COMMIT, OB_LOG_TX_ABORT, OB_LOG_TX_CLEAR };
stateDiagram-v2 [*] --> INIT INIT --> REDO_COMPLETE : Drain REDO/LSN stop REDO_COMPLETE --> PREPARE : ROOT 发 PREPARE_REQ PREPARE --> PRE_COMMIT : 多数派应答 PREPARE_RESP PRE_COMMIT --> COMMIT : PRE_COMMIT_REQ 完成 COMMIT --> CLEAR : COMMIT 完成,清理 REDO_COMPLETE --> ABORT : 任一参与者失败 PREPARE --> ABORT : 任一参与者应答 abort PRE_COMMIT --> ABORT COMMIT --> ABORT ABORT --> CLEAR CLEAR --> [*]

2PC 协调器核心:ObTwoPhaseCommitterstorage/tx/ob_two_phase_committer.h),与上下游 commit 链 ob_two_phase_upstream_committer / ob_two_phase_downstream_committer 形成多级 commit tree, IDA 的「分布式事务」形态通过 ob_tx_2pc_ctx_implob_tx_2pc_msg_handler 消息驱动。

8.4 REDO 提交优化 + Multi-Source

8.5 Commit Tree(树形分布式事务)

跨多个 LS 的分布式协调不是直接的 N-方 2PC,而是按依赖关系组织成 commit treeOb2PCRole 分 ROOT / INTERNAL / LEAF,由 root 服务(通常事主参与 LS)作为 coordinator; 每个分发到的 LS 都是一个 downstream committer;上游者应答前驱 agnostic。ObTwoPhaseCommitter 同时 向上游发送 PRE_COMMIT / COMMIT / ABORT,在 ob_two_phase_upstream_committer/ob_two_phase_downstream_committer 上完成树形级联。这样可以樹形隐藏 single-coordinator bottleneck,时延近似 O(log L),避免某分片持有 participant 名称表。

flowchart TB RT["ROOT
coordinator LS"] --> A["INTERNAL LS A"] RT --> B["INTERNAL LS B"] A --> A1["LEAF LS A1"] A --> A2["LEAF LS A2"] B --> B1["LEAF LS B1"] RT -. upstream/dn.-> A A -. do/ack.-> A2 classDef rt fill:#fff3df,stroke:#e08a00; class RT rt;

8.6 TxFreeRoute(关键 OB 4.x 特性)

源码:ob_tx_free_route.*。允许同一逻辑事务在物理 多个 OBServer 节点之间路由(即便事务尚未提交, 客户端 SQL 也可被 ObProxy 路由到任意 server 执行 next part),而事务状态不再严格"绑在某一 server 上"。状态机 TxFreeRouteState:SAVEPOINT / COMMIT_INFO 等通过 commit_info log + 内存 session state snapshot 在 server 间传递。 这是 OLTP 负载均衡与秒级切关键。

8.7 Standby Read 与弱一致读

8.8 死锁检测

8.9 XA / GTI 全局唯一 ID

· 事务
Q1:OB 的"分布式事务"是哪种?2PC 多少阶段?关在哪?
A1:基于多个参与 LS 的树形 2PC,过程:REDO 完成 → PREPARE → PRE_COMMIT(优化阶段)→ COMMIT → CLEAR。状态机在 enum ObTxState(10~70)。PRE_COMMIT 是 OB 加的优化阶段,让 leader 端的多数派持久化"提 prepare"后告知下游,减少 COMMIT 阶段 RTT。
Q2:如果 commit 已经 PREPARE 后某参与者崩溃,恢复后会是什么行为?
A2:参与者启动后从 clog replay,看到 PREPARE log 但无 COMMIT/ABORT。它会向 ROOT/上游主动查询最终状态(retransmit_upstream_msg_);root 若已发布 COMMIT 则下发 CLAIM;若严格未发布、且 timeout,会按 PREPARE emission 时间决定 ABORT/COMMIT:通常 commit_info 已包含 redo/结果 → 不会丢;保护由 retain_ctx_mgr(ob_tx_retain_ctx_mgr)保留结构直到 commit/abort 完成。
Q3:如何预防一个事务"读半截"看到部分提交记录?
A3:快照 SCN(snapshot_version)+ commit_version(TxData 落定后)的 dual-check:在读时刻 ObTxDataTable::check_tx_status 验证目标 trans 是否已 commit 与是否 <= snapshot_version;同时脏读未被可见 access 限制(每次读到 ObMvccRow 时检查被事务是否在 commit_version 中"确定状态",使用 TxData 临时把 committed/unfinished 分类)。
Q4:TxFreeRoute 是怎么做到"任意节点继续事务"而不破坏 ACID?
A4:每个 part 上 ObPartTransCtx 持有事务态、回叙并旃;当客户端路由到新节点、从 commit_info log 中恢复了主要 participant 中的 snapshot。强一致性由 clog + 2PC + commit info-log 保证,不在 server 内存里维持"transactor host"。
Q5:没有死锁也会先持有的解决资源锁定怎么让脚法不全?
A5:MemTable 行 latch + tx callback 链:ObRowLatch 在被多事务并发更新同 row 时 serialize,影响仅限冲突同 row。deadlock_adapter 通过 "wait-for" graph(加锁链表)定期发现有向环;同时强超时(ob_tx_timeout)自毫 fault-injection 与高 trauma 又 印证。

9. 日志服务 PaLF 与 Multi-Paxos

9.1 PaLF 是什么

PaLF = Paxos Log Facilitysrc/logservice/palf),是 OceanBase 自研的、面向 LS 的、基于 Multi-Paxos + Lease-based 选举的日志复制引擎。每个 LS 自己有一组 PaLF 实例。它不是 Fast Paxos (Fast Paxos 需要 2F+1 全部参与 prepare 阶段且不需 Leader),OceanBase 使用 Leader-based Multi-Paxos: 只有 Leader 接读写,其余副本只持久化和回放。Leader 通过独立的选举模块(palf/election) lease 选出。

为什么不是 Fast Paxos:PaLF 用 Leader(独 Lease)归约掉 prepare 阶段,写路径为「Leader 提议→多数派持久化→回调用户」。 Fast Paxos 在 3F 副本场景中无法显著省 LiN,且不兼容 Lease 切主、日志连续性、可回放等数据库日志需求。

9.2 PaLF 内部模块

模块路径职责
PalfEnv / PalfHandlepalf_env_impl, palf_handle_impl管理一组 LS 的 PaLF 实例及句柄
LogStateMgrlog_state_mgr.{h,cpp}维护本副本的 (role, state) 状态机并驱动切换
LogConfigMgrlog_config_mgr.{h,cpp}成员变更(加/减副本、降级、仲裁、切主)
LogSlidingWindowlog_sliding_window.{h,cpp}提案编号、leader 上的 ack 滑窗、follower 接收点
LogEnginelog_engine.{h,cpp}阻塞 IO、checksum、落盘、回收、网络收发
LogModeMgrlog_mode_mgr访问模式切换(RAW_WRITE / APPEND)
LogReconfirmlog_reconfirm.{h,cpp}新主上线时把多数派未一致的日志拉全、补齐
Electionpalf/election/独立选举模块(非 Paxos 缺一不可的部分)
ApplyService / ReplayServicelogservice/applyservice, replayservice把已提交日志应用到存储引擎

9.3 角色与副本状态机(LogStateMgr)

角色 ObRole:LEADER / FOLLOWER;副本状态 ObReplicaStatelog_define.h):

enum ObReplicaState {
  INVALID_STATE = 0, INIT = 1, ACTIVE = 2, RECONFIRM = 3, PENDING = 4,
};

状态转移节选自 log_state_mgr.cpp

stateDiagram-v2 [*] --> INIT INIT --> FOLLOWER_ACTIVE : init_to_follower_active_ (启动) FOLLOWER_ACTIVE --> LEADER_RECONFIRM : follower_active_to_reconfirm_ (赢得选举) INIT --> FOLLOWER_PENDING : init 后收到 prepare FOLLOWER_PENDING --> FOLLOWER_ACTIVE : flush 完成 / 自动恢复
follower_pending_to_follower_active_ FOLLOWER_PENDING --> LEADER_RECONFIRM : follower_pending_to_reconfirm_ LEADER_RECONFIRM --> LEADER_ACTIVE : reconfirm_to_leader_active_
(Reconfirm 拉日志→START_WORKING) LEADER_ACTIVE --> FOLLOWER_PENDING : leader_active_to_follower_pending_
(Lease 过期 / 切主) FOLLOWER_ACTIVE --> [*] LEADER_ACTIVE --> [*]

9.4 Reconfirm 子状态机(新主上线修复不一致)

LogReconfirm::Statelog_reconfirm.h)。这是 OceanBase Multi-Paxos 重要扩展——确保新 Leader 在服务写之前, 把多数派未确认的日志全部补齐,这是不同 Paxos 组从「无主 / 切主 / 宕机」直奔稳定阶段的恢复路径:

stateDiagram-v2 [*] --> INITED INITED --> WAITING_LOG_FLUSHED : 等待本地日志持久化完成 WAITING_LOG_FLUSHED --> FETCH_MAX_LOG_LSN : 找出多数派中 max_lsn 的 newest_server_ FETCH_MAX_LOG_LSN --> RECONFIRM_MODE_META : 同步 mode_meta 到多数派 RECONFIRM_MODE_META --> RECONFIRM_FETCH_LOG : 从 newest_server_ 拉缺失日志 RECONFIRM_FETCH_LOG --> RECONFIRMING : 等待日志拉齐 RECONFIRMING --> START_WORKING : 提交 START_WORKING 日志
(之后才可对外服务) START_WORKING --> FINISHED FINISHED --> [*]

9.5 写日志路径

flowchart LR TS["ObTransService append redo"] --> APP["PalfHandleImpl::append"] APP --> SW["LogSlidingWindow: 预占 LSN/提案号"] SW --> GB["LogGroupBuffer"] GB --> W["LogIOWorker 落盘"] W --> RPC["Leader→Followers: push_log_to_paxos_follower_"] RPC --> ACK["各副本 ACK (持久化完成)"] ACK --> MAJ{"多数派持久化?"} MAJ -- yes --> CB["on_success 回调业务"] W --> LST["本地 apply 队列
ApplyService"] LST --> STO["应用回放到 MemTable/SSTable"] RPC --> FST["Follower ReplayService
replayservice/ob_log_replay_service"] FST --> STO

9.6 成员变更与降级

9.7 成员变更具体流程(reconfig)

源码:log_config_mgr.cppLogConfigChangeType 包括 ADD/REMOVE/CHANGE_NUM/DEGRADE/UPGRADE/ADD_ARB/REMOVE_ARB/CHANGE_LEADER/SETR_LOGONLY/...。一次变更分四步:

flowchart LR BR["用户/RotService 触发 reconfigure"] --> PR["阶段1: Prepare
多数派持久化 config_version + new_member_list"] PR --> ST["阶段2: 与目标副本同步
(必要日志推送 / 抓取至 latest_lsn)"] ST --> CM["阶段3: Commit
多数派持久化新的有效配置"] CM --> AP["阶段4: Apply
切换 LogConfigMgr inner_state"] classDef k fill:#fff3df,stroke:#e08a00; class PR,CM k;

9.8 Reconfirm 内部细节

LogReconfirmlog_reconfirm.{h,cpp})做新主补全多副本不一致日志。关键变量:

进程:选举胜选 → 本机 flush 完毕 → 向多数派发 prepare → 找出 lsn 最大者 → 拉 (max_lsn_local, majority_max_lsn] 区间日志 → 写 START_WORKING log(多 raft 唯一标志)→ 切 LEADER_ACTIVE。 START_WORKING commit 后才允许对该 LS append 新日志——这是 Multi-Paxos "断主重主不准漏日志" 的关键实现。

9.9 为什么 OB RTO < 8s?

  1. Leader 离职 → 租约过期 < 3s:选举租约短(默认 9s 内可续;租约检查原 cycle 1s),断主到选主即 1~2s。
  2. 选出后再做 Reconfirm:Reconfirm 只对不一致区间拉日志;正常情况下 majority_max_lsn == max_lsn 后 0 拉取即 START_WORKING。
  3. START_WORKING log 是 single-log:仅一次 append + 多数派即 leader 可服务。
  4. 磁盘 IO 复用 clog 多 Group batch:日志落盘单条很短,write 链路稳定 ms 级。
  5. Replay 多 LS 并行回放(ob_replay_handler),follower 一直 hot,不需要冷启动。

实例:3F normal 切主一般在 1~3s 完成 START_WORKING;整 RTO 阈值 8s 留 buffer 给网络抖动 + brick 恢复。

9.10 Group Buffer 与 Sliding Window

OB 把多条 redo log entry 合成 LogGroupEntrylog_group_entry)批量落盘,减少 IO 次数。Sliding Window(log_sliding_window)维护 ack 位图:leader 维护每 follower 的 acked lsn;当"多数派 ack lon >= entry 的 lsn" 则该 entry committed 走 ApplyService。

10. 选举(Lease-based,非 Fast Paxos)

10.1 为什么独立一个选举模块

Multi-Paxos 本身只是协议,需要「谁来做 Leader」这一前提。OceanBase 把选举与共识拆开:选举模块 (src/logservice/palf/election)用 Lease 选举每 LS 独立维护一个稳定 Leader, Paxos 只在 Lease 失效或切主时被重新启动(见 LogStateMgr::switch_state)。

角色变化原因(election/interface/election.h): DevoteToBeLeader(无主选举登基)、ChangeLeaderToBeLeader(切主登基)、LeaseExpiredToRevoke(租约过期退位)、 ChangeLeaderToRevoke(切主退位)、StopToRevoke(停服退位)。

10.2 选举算法:基于 Prepare/Accept 两阶段 + Lease

sequenceDiagram participant F1 as Follower A participant F2 as Follower B participant L as Old Leader / 空缺 F1->>F2: ElectionPrepareRequest (proposal_id++) F2-->>F1: ElectionPrepareResponse (ack highest log, priority) F1->>F2: ElectionAcceptRequest (leader=A, lease=[t0,t1]) F2-->>F1: ElectionAcceptResponse (grant lease) Note over F1: 多数派应答 + 自身优先级最高
当选 Leader F1->>F1: RoleChangeReason = DevoteToBeLeader F1->>L: 通告 ChangeLeader (optional) F1->>F1: 启动 LogReconfirm → 切换到 Leader Active

关键代码:

10.3 Lease 与领导者连任

Leader 持有严格租约,租约期内不开新一轮选举;租约即将到期时由 Leader 自我续约(lease renew), 减少选举震荡。租约过期后由 Follower 触发新一轮。RCHandleRequestChecker 处理过期与消息时序。

10.4 选举优先级与避免脑裂 / 反复选举

ElectionPrioritypalf/election/interface/election_priority.h)的比较维度(从高到低):

  1. 本地 log_lsn / proposal_id(日志更全者优先)
  2. 成员变更版本(更晚变更优先,避免老 member 抢占)
  3. server_id(兜底确定性)

因此在切主时原则上由日志最全者当选,避免后续 leader 还要回拉 follower 日志的代价。temporarily_downgrade_protocol_priority 提供外部介入:刚恢复的 server 短时下调自己优先级,避免它当选后立刻被更全者"切回来"。

10.5 切主(ChangeLeader)路径

sequenceDiagram participant CLI as 用户/RotService participant L as 旧 Leader participant F as 目标 Follower B participant M as 多数派副本 CLI->>L: CHANGE_LEADER TO B L->>F: ChangeLeaderMsg (lease transfer) F->>L: 同意接管 L->>M: 广播原主停止写 (revoke_RChangeReason=ChangeLeaderToRevoke) L->>F: 当本地 lsn 同步完成授权揽 F->>M: ElectionAcceptRequest (proposal_id++) M-->>F: 接受新 lease F->>F: 赢得选举 → LogReconfirm → 切 Leader Active Note over L,F: 全程双 Leader 共存窗口通过 lease 边界 + config_version 严格互斥,
不会写同一 proposal_id 的两条日志
· Paxos / 选举
Q1:OB Paxos 是否是 Fast Paxos?为什么不用?(高频追问)
A1:不是。Fast Paxos 需要 quorum 大小 = 2F+1(即所有副本),且 client 直接 propose,不预先选 leader,本质不能 3F 多数派持久化即可提交;亦不兼容 lease 切主、日志连续性、回放优先级。OB 用 Leader-based Multi-Paxos:Leader 由独立 lease 选举选出,正常写请求归约掉 prepare 阶段(接单条 append → 多数派持久 → ack),RPO=0 / RTO<8s。
Q2:Reconfirm 在补不必要的日志吗?多数已经OK 时仍要拉?
A2:不必。Reconfirm 在 FETCH_MAX_LOG_LSN 阶段就先比 lsn:若本机 max_lsn == majority_max_lsn 则跳过 RECONFIRM_FETCH_LOG 直入 START_WORKING。即使需要拉,也只补充 (max_lsn_local, majority_max_lsn] 这一段,并且缓冲区预占最大 ObGroupBuffer 容量;所以"缺多少 拉 多少"。
Q3:选举优先级里 "日志最全者当选",那日志同样多怎么决?
A3:备选梯次:log_lsn == 时 → 看成员变更版本(member_version 越新越优先,保证不在重配期间脑裂);同样时 → server_id 比较(deterministic)。结合 lease 续约:旧 Leader 续约若成功 lease 仍持有,无新一轮选举,自然消除抖动。
Q4:少数派副本故障怎么办?会触发选举吗?
A4:少数派故障 Leader 仍可写(多数派仍 forming),不会选举。可通过 reconfig 进 DEGRADE 把它从多数派计数中摘除;等它恢复后 UPGRADE 回来。两可用区 + Arbiter 仲裁模式下,单少数可用区挂也能保证多数派 alive。
Q5:会不会出现两个 Leader 同时写?
A5:不会。新 Leader 必须先 commit START_WORKING 日志(一份 lease epoch 标志)才能 append 业务日志;旧 Leader 退出由 ChangeLeaderToRevoke / LeaseExpiredToRevoke 触发;config_version 严格单调使得旧 leader 的旧 proposal_id 不被其它大多数接受。即使 lease 重叠也只能"旧 leader 晚到 ACK 多数派"提交受阻,绝不会双 commit。

11. 索引体系(B+ / LSM / 全文 / 向量)

11.1 索引类型矩阵

索引定义见 src/share/schema/ob_schema_struct.hObIndexType)。OceanBase 的二级索引并不直接坐落 B+Tree 数据结构上,而是「索引表」——每张索引表本身就是一个 Tablet,存储布局与数据表一致(LSM-Tree)。 用户表与索引表之间通过 rowkey 维持对应关系。

大类典型 index_type存储形态用途
本地索引(local)IS_LOCAL/UNIQUE_LOCAL主表 Tablet 内的辅助 SSTable同一 Tablet 范围的快速定位
全局索引(global)IS_GLOBAL/UNIQUE_GLOBAL独立 Tablet,跨分区分布跨分区查询、不绑定主表分区
向量索引 IVFINDEX_TYPE_VEC_IVFFLAT_CENTROID_LOCAL / CID_VECTOR_LOCAL / ROWKEY_CID_LOCAL多张辅助 Tablet:质心表 + 倒排IVF_FLAT / IVF_SQ8 向量 ANN
向量索引 HNSWvec_hnsw_*(见 schema_struct.h:429)列存 HNSW 图数据近似向量检索(高召回)
全文检索 FTSis_local/global/global_local_fts_index*、fts_doc_word_auxdoc-word 倒排辅助表全文 MATCH AGAINST
多值索引is_multivalue_index*多值列倒排表JSON 数组成员查询
Domain 索引domain_index 相关插件可扩展插件化自定义索引

11.2 索引存储:Index Block 树

每张 SSTable 由 Index Block 树(多级 + 微块)组织,源码在 src/storage/blocksstable/index_block

flowchart TB RT["Root Index Block
(常驻内存)"] --> M1["Macro Index Block 1"] RT --> M2["Macro Index Block 2"] M1 --> B1["Micro Index Block
(rowkey→data block offset)"] B1 --> D1["Data Micro Block (实际行)"] M2 --> B2["Micro Index Block"] B2 --> D2["Data Micro Block"] style RT fill:#fff3df,stroke:#e08a00

索引扫描核心组件(src/storage/access):

11.3 向量索引

位于 src/sql/das/ob_das_vec_defineob_vector_index_lookup_opob_das_search_index_utilssrc/sql/optimizer/ob_access_path_estimation。IVF 系列: Centroid Local(质心)+ CID-Vector Local(每个质心所属向量)+ Rowkey-CID Local(反向映射),3 张辅助 Tablet 联合检索; HNSW:单独的图结构辅助表。SQL 层支持 VECTOR 数据类型、距离函数(L2/IP/Cosine)与混合检索 (sql/code_generator/ob_hybrid_search_cg_service_*)。

11.4 全文索引 FTS

位于 src/storage/ftssql/code_generator/ob_hybrid_search_cg_service_fulltext。结构:分词后的 doc-word 倒排辅助表(is_fts_doc_word_aux),查 MATCH AGAINST 时复用 parser 分词结果做倒排合并。支持 local/global/global-local 三种形态。

11.5 多值索引

用于 JSON / ARRAY 列:把数组每个元素展开到一张倒排辅助表,使 MEMBER OFJSON_CONTAINS 等可以走索引。

11.6 索引选择与代价(常问)

11.7 复合表 / Clustered Index / RowStore-vs-ColumnStore

11.8 向量检索执行链路(IVF + HNSW)

flowchart LR Q["query vector"] --> CEN["Centroid Local
(质心搜索 top-k)"] CEN --> CID["CID-Vector Local
在选中质心下属做 ANN"] CID --> RK["Rowkey-CID Local
回到 rowkey 取主表 row"] RK --> T["主表 Tablet"] HNSW["HNSW 图辅助表"] -. approx kNN .-> CID HYBRID["Hybrid Search
(vec + fts + 关系)"] --> T classDef k fill:#fff3df,stroke:#e08a00; class CEN,HYBRID k;
· 索引
Q1:OB 的「全局索引」和「本地索引」本质区别?走全局索引时为什么不写 clog?
A1:本地索引 tablet 数据跟主表 tablet 一一绑定(同 LS/Paxos 组),写主表时就一起写本地索引 tablet 行,不再有跨 LS 一致性问题。全局索引是独立分布的 tablet 集,可能落在不同 LS 上,因此 DML 维护全局索引要单独走 2PC,性能慢但全局可分布。两种索引都写各自 LS 的 clog。
Q2:Skip Scan 和与索引扫描比起来什么时候赚?
A2:当索引前缀列基数小且非 ranger/Ranger 区间集中(如 flag IN 0,1),而剩余谓词选择度高时收益大。CBO 估算时按「前缀块数 × 谓词选择率」与「普通全范围 Scan × 谓词选择率」对比;同样考虑 covering 后者代价;Skip Scan 通常省去 80% IO 开销。
Q3:列存(CG SSTable)和行存同一张表 / 同一 Tablet 内怎么共存?
A3:通过 CONVERT_CO_MAJOR_MERGE 把 Major 转出 CG SSTable,此时同时存在 Major CG 和 Minor row sst(中间版增量仍是行存)。读路径在 ob_multiple_merge 中按列需求拼接:AP 查询列打开 CG,要小行 rowkey lookup 仍取 minor 行存。Major 切换保证 schema 一致。
Q4:FTS/向量索引/多值索引都不是普通 B+,为什么不全都接通用 SQL 引擎?
A4:DAS 通过 ObDASScanOp 抽象 + ObDASExtraData 适配不同索引类型:FTS 用 hybrid_search_cg、向量用 ob_vector_index_lookup_op,多值用倒排 task。SQL 算子侧对外仍是 TableScan / SubPlan Scan,专门索引插.min scan_descriptors(ob_das_def_reg)注册以避免上层重新发明算子。

12. RootService 与多租户

12.1 RootService 职责

RootService(src/rootserver)是一个集群级单点服务(实际上也是根 LS 的 Leader 副本承担),负责:

flowchart LR RS["RootService (根 LS 的 Leader)"] RS --> HBT["heartbeat / all_server_checker"] RS --> DDL["DDL Service"] RS --> BAL["balancer: Unit/LS 级"] RS --> DR["disaster_recovery"] RS --> FZ["freeze/compaction scheduler"] RS --> BU["backup/restore"] RS --> MV["mview builder"] DDL --> OB["所有 OBServer 上的 ddl 任务执行器"] BAL --> OB DR --> OB FZ --> OB classDef hi fill:#fff3df,stroke:#e08a00; class RS hi;

12.2 多租户与资源隔离

flowchart TB CL["Cluster"] --> Z1["Zone 1"] & Z2["Zone 2"] & Z3["Zone 3"] Z1 --> S1["OBServer A"] Z1 --> S2["OBServer B"] Z2 --> S3["OBServer C"] Z3 --> S4["OBServer D"] S1 --> U1A["Unit (tenant T1)"] S1 --> U2A["Unit (tenant T2)"] S2 --> U1B["Unit (tenant T1)"] S2 --> U2B["Unit (tenant T2)"] U1A ---|资源池 pool_T1| U1B subgraph T1["Tenant T1"] POOL1["Pool T1 (3 单元, 跨 zone)"] POOL1 --> LST1["LS-x1"] POOL1 --> LST2["LS-x2"] end classDef tn fill:#eaf3ff,stroke:#2a6df4; class T1 tn;

13. 关键源码索引(导航)

主题关键文件 / 目录
进程入口src/observer/main.cppob_server.{h,cpp}ob_srv_network_frameob_srv_deliver/xlator
SQL 入口src/sql/ob_sql.{h,cpp}ob_result_set
Parsersrc/sql/parser/(ob_sql_parser.l/y、raw_token.h
Resolversrc/sql/resolver/ob_resolver.h、dml/ddl/cmd/tcl/dcl 子目录)
Rewritesrc/sql/rewrite/ob_transform_rule.*udr/
Optimizersrc/sql/optimizer/ob_optimizer.h、ob_log_plan.h、ob_join_order_enum_idp
Code Generatorsrc/sql/code_generator/ob_static_engine_cg.{h,cpp}ob_expr_generator_impl
执行算子src/sql/engine/*ob_operator.h、ob_operator_factory、basic/join/sort/aggregate/px/dml)
DASsrc/sql/das/ob_data_access_service.h、ob_das_scan_op.h、ob_das_location_router.h
PXsrc/sql/engine/px/ob_dfo.h、ob_dfo_scheduler、ob_granule_pump、ob_px_coord
存储访问src/storage/access/ob_multiple_merge、ob_index_block_tree_traverser
MemTable / MVCCsrc/storage/memtable/ob_memtable.h、mvcc/ob_keybtree.h、mvcc/ob_mvcc_engine.h
SSTablesrc/storage/blocksstable/ob_block_manager、ob_micro_block_*、index_block/ob_index_block_builder
列式编码src/storage/blocksstable/encoding/*cs_encoding/*
Compactionsrc/storage/compaction/ob_compaction_util.h、ob_partition_merger、ob_partition_merge_fuser、ob_medium_compaction_mgr
Tablet / LSsrc/storage/ls/src/storage/tx_storage/ob_ls_service/ob_ls_mapsrc/storage/meta_mem/
事务src/storage/tx/ob_trans_service.h、ob_trans_part_ctx.h、ob_two_phase_committer.h、ob_committer_define.h
PaLFsrc/logservice/palf/palf_env_impl、palf_handle_impl、log_state_mgr、log_config_mgr、log_sliding_window、log_reconfirm、log_engine
Apply / Replaysrc/logservice/applyservice/ob_log_apply_servicereplayservice/ob_log_replay_service、ob_replay_handler
选举src/logservice/palf/election/algorithm/election_impl、election_proposer、election_acceptorinterface/election.h
归档 / CDCsrc/logservice/archiveservice/cdcservice/libobcdc/
RootServicesrc/rootserver/ob_root_service.h、ob_ddl_service.h、ob_root_balancer、ob_bootstrap
Schemasrc/share/schema/ob_schema_struct.hObIndexType 定义)
FTSsrc/storage/fts/sql/code_generator/ob_hybrid_search_cg_service_fulltext.cpp
向量索引src/sql/das/ob_das_vec_define、ob_vector_index_lookup_opsql/code_generator/ob_hybrid_search_cg_service_vec.cpp
建议结合本图动手追代码:从 ObSql::stmt_query() 跟到 ObPhysicalPlan,再进入 ObTableScanOpObDASScanOpObMultipleScanMerge → MemTable/SSTable 算子, 同时从 ObTransService::start_tx 跟到 ObPartTransCtx::submit_logPalfHandleImpl::append, 即可贯通 SQL / Storage / Tx / Paxos 四层。