万物皆可KV(5)Nubela存储布局分析
1. 结论NebulaGraph 的当前持久化后端是 RocksDB。每个 Graph Space 在每个数据目录下有独立数据库路径语义为nebula/spaceId/data使用默认 Column Family因此 Graph Data Key 不再重复编码spaceId。Meta 也使用 RocksDB但固定在 Meta 进程的space0, part0逻辑域。HBase 相关实现仍在代码树中但当前存储工厂不选择它不属于本文的现行落盘格式。Schema 与 Data 都是同一个简单模型Key - Value。Schema 不是独立文件而是 Meta RocksDB 中带 ASCII 命名空间前缀的 KV图记录则是 Space RocksDB 中带二进制类型前缀的 KV。Tag 和 Edge 的身份全部进入 Key属性行、Schema 版本、TTL 配置等进入 Value。普通属性更新覆盖同一 Key没有应用层 MVCC 版本后缀。二级索引把“可排序的属性编码 原记录定位符”放入 KeyValue 通常为空只有启用 Schema TTL 时索引 Value 才保存紧凑编码的 TTL 属性副本。删除直接调用 RocksDBDelete或批量Delete范围清理使用DeleteRange。应用层没有普通行墓碑格式。当前格式混合了原生整数编码与显式可排序大端编码身份、ID、Meta 字段大量依赖本机字节序和size_t索引范围字段与 Edge rank 才使用跨符号有序编码。本文的精确样本来自当前x86_64环境即小端、size_t8。属性 Value 使用 Row V2Schema 决定字段类型Value 不逐字段携带类型标签末尾保存 8 字节写入微秒时间但它不是 MVCC 版本也不是 TTL 到期时间。全文索引的定义在 Meta RocksDB倒排内容在外部 Elasticsearch当前没有向量索引 KV 格式。2. 总体存储布局RocksDB ├── Meta 服务数据库逻辑 space0 / part0 │ ├── SchemaSpace、Tag、Edge、Index、名称映射 │ ├── 鉴权与拓扑User、Role、Host、Zone、Part、Leader │ └── 运维状态Job、Balance、Snapshot、Session、Config、Service └── Storage 数据库每个 Graph Space、每个 data path 一库 ├── Tag / Vertex / Edge 主记录 ├── Property Index ├── Raw KV ├── Raft/分区系统记录 └── TOSS 事务恢复记录本文用以下记号描述字节布局LET当前机器原生小端整数BET显式大端整数。NATIVET实现直接复制 C 对象字节当前环境表现为小端但格式本身依赖平台。STR[n]恰好 n 字节不含终止零PAD[n]截断或补零到固定长度。CompactTApache Thrift Compact Protocol 序列化。SORT_I64(x)x的 64 位二进制先翻转符号位再转大端使字节序与有符号数序一致。item(part,type)LEuint32((part 8) | type)。在小端机器上首字节就是类型随后三字节承载分区号。2.1 Graph Data 顶层 Key 类型类型值Key 的一侧Value 的一侧当前用途1 Tagitem | vid | tagIdRow V2顶点上的 Tag 属性2 Edgeitem | src | edgeType | rank | dst | suffixRow V2有向边属性3 Indexitem | indexId | indexedValues | locator空或 TTL 副本属性二级索引4 Systemitem | systemSubType日志位置、Peer 等分区系统记录5 Operationitem | timestamp | opType [| indexKey]index Value 或旧 index Key索引重建期间的补偿操作6 KeyValueitem | userKey不透明用户字节Storage Raw KV API7 Vertexitem | vid空可选的显式顶点存在记录8 Primeitem | normalEdgeKeyCompact 请求 操作字符TOSS 第一阶段恢复记录9 DoublePrimeitem | normalEdgeKeyCompact 请求 操作字符TOSS 第二阶段恢复记录spaceId不在上述 Key 中因为数据库目录已经隔离 SpacepartId位于每个item中使同一分区与同一对象类型形成连续前缀。2.2 RocksDB 层行为单条写入是 RocksDBPut原子多对象更新使用WriteBatch。单 Key 删除是Delete区间清理是默认 Column Family 上的DeleteRange。SST 压缩和可选 BlobDB 是 RocksDB 物理层选择不改变本文描述的应用层 Key/Value。非零 Space 存在数据格式标记Key 为ff ff ff ffValue 为 ASCII3.0。应用编码没有统一 checksum、压缩 envelope 或行级加密头。3. 当前编码器生成的真实 KV 样本以下四组样本由当前版本生产 Key 工具、Row V2、Meta Key 工具和 Thrift Compact 编码器实际生成并立即按生产解码路径反解不是手工拼接的示意值。VID 长度取 8。各样本是分别验证单个编码族的独立 fixture其中 3.2 为覆盖 Row V2 变长字段而使用四列临时 Schema不应误认为它与 3.1 的两列 Meta Schema 是同一批可联合写入的数据。3.1 Tag Schemaperson逻辑输入Space ID1Tag ID101Schema 逻辑版本0名称person字段name FIXED_STRING(8) NOT NULL、age INT64 NULLTTLage3600 秒Keyescaped: __tags__\x01\x00\x00\x00\x65\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x7f hex: 5f5f746167735f5f0100000065000000ffffffffffffff7f length: 24Key 分段5f5f746167735f5f __tags__ 01000000 LEint32(spaceId1) 65000000 LEint32(tagId101) ffffffffffffff7f LEint64(INT64_MAX - logicalVersion(0))Valuehex: 06000000706572736f6e192c18046e616d651c150e141000220018036167651c15040021001c16a03818036167650000 length: 48Value 分段与反解06000000 LEint32(nameLength6) 706572736f6e person 192c...650000 CompactSchema columns[ name: FIXED_STRING(8), nullablefalse, age: INT64, nullabletrue ] ttl_duration3600, ttl_colage写入时创建 Tag 同批写入“名称到 Tag ID”映射、版本 0 Schema 和 Meta 最后更新时间。读取时按spaceId tagId前缀扫描所有 Schema 版本选择最大逻辑版本再用 Value 开头的名称长度与 Compact Schema 还原字段。Alter 会新增版本 KV旧版本保留Drop 批量删除全部版本和名称映射。3.2 Tag 主记录顶点v42的person逻辑输入Part ID2VIDv42Tag ID101Schema 版本0nameAdaageNULLactivetruenotegraphKeyescaped: \x01\x02\x00\x00v42\x00\x00\x00\x00\x00\x65\x00\x00\x00 hex: 01020000763432000000000065000000 length: 16Key 分段01020000 item(part2,typeTag) 7634320000000000 PAD[8](v42) 65000000 NATIVEint32(tagId101)Valuehex: 088041646100000000000000000000000000011b000000050000006772617068598452cfb9570600 length: 40Value 分段08 Row V2 headerSchema 版本占 0 字节 80 nullable bitmapage 为 NULL 4164610000000000 FIXED_STRING(8) Ada 0000000000000000 age 的 8 字节固定槽NULL 时内容不参与语义 01 BOOL true 1b000000 05000000 STRING 描述符tail offset27length5 6772617068 tail graph 598452cfb9570600 NATIVEint64(write_ts_us1785305419121753)写入时根据 Space 的 VID 长度构造 Key根据 Tag Schema 构造 Row V2 Value。点查询以完整 Key 读取前缀扫描可枚举一个分区或 VID 的 TagValue 交给与 header 版本匹配的 Schema 解码。最终返回业务字段时不返回末尾写时间。3.3 顶点属性索引(name, age)定位v42逻辑输入Part ID2Index ID201第一列nameFIXED_STRING(8,Ada)第二列ageNULL原记录定位符vidPAD[8](v42)Keyescaped: \x03\x02\x00\x00\xc9\x00\x00\x00Ada\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00v42\x00\x00\x00\x00\x00 hex: 03020000c90000004164610000000000ffffffffffffffff00407634320000000000 length: 34Key 分段03020000 item(part2,typeIndex) c9000000 NATIVEint32(indexId201) 4164610000000000 FIXED_STRING(8) Ada ffffffffffffffff NULL 的 INT64 占位 0040 NATIVEuint16(nullableBits0x4000) 7634320000000000 PAD[8](v42)Valuehex: empty length: 0ageNULL不能仅靠全ff占位区分因此 Key 另带原生 16 位 NULL bitmap第二列对应0x4000当前小端字节为00 40。非 TTL Schema 的索引 Value 为空。扫描(nameAda, age IS NULL)时用属性前缀构造范围再从 Key 尾部还原 VID如果请求字段仅由安全可解码的索引列覆盖可以不回表否则用 VID 和 Tag ID 重建主 Key 回表。3.4 Edge 主记录v42-[301-7]-v99逻辑输入Part ID2源 VIDv42Edge Type301Rank-7目标 VIDv99Schema 版本1weight1.5labelknowsKeyescaped: \x02\x02\x00\x00v42\x00\x00\x00\x00\x00-\x01\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xf9v99\x00\x00\x00\x00\x00\x01 hex: 0202000076343200000000002d0100007ffffffffffffff9763939000000000001 length: 33Key 分段02020000 item(part2,typeEdge) 7634320000000000 PAD[8](v42) 2d010000 NATIVEint32(edgeType301) 7ffffffffffffff9 SORT_I64(rank-7) 7639390000000000 PAD[8](v99) 01 normal edge suffixValuehex: 0901000000000000f83f12000000050000006b6e6f77737f8452cfb9570600 length: 31Value 分段09 01 Row V2 header1 字节 Schema version1 000000000000f83f NATIVEdouble(1.5) 12000000 05000000 STRING 描述符tail offset18length5 6b6e6f7773 tail knows 7f8452cfb9570600 NATIVEint64(write_ts_us1785305419121791)Edge rank 使用可排序大端编码因此同一src edgeType下能按 rank 做范围扫描。正常有向边 suffix 为 1。插入双向图语义时源分区保存正 Edge Type 的出边目标分区保存负 Edge Type 的入边属性索引只针对正类型出边。读取后以 Edge Schema 解码 Value并从 Key 返回src/type/rank/dst。4. Schema 如何保存4.1 核心 Schema 对象Meta Key 的命名空间是可读 ASCII后续 ID 多为原生整数。表中“Key”与“Value”分别描述 KV 的两侧。对象KeyValue生命周期Space__spaces__ | NATIVEspaceIdCompactSpaceDesc含名称、分区数、副本数、字符集、排序规则、VID 类型与长度、Zone、隔离级别、注释Create 写入Alter 覆盖Drop 删除并触发数据清理Space 名称映射__index__ | SPACE | nameNATIVEspaceId与 Space 同批维护Tag Schema__tags__ | spaceId | tagId | storageVersionNATIVEint32(nameLen) | name | CompactSchemaCreate 版本 0Alter 新增版本Drop 删除全部版本Edge Schema__edges__ | spaceId | edgeType | storageVersion与 Tag 相同同 TagTag/Edge 名称映射__index__ | entryType | spaceId | nameNATIVEtagId/edgeType创建与删除时维护Property Index 定义__indexes__ | spaceId | indexIdCompactIndexItem含目标 Schema、索引名、列名、列类型/长度、可选 Schema IDCreate/Drop实际索引数据在 Storage RocksDBIndex 名称映射__index__ | INDEX | spaceId | nameNATIVEindexId与 Index 定义同批维护Fulltext Index 定义__ft_index__ | nameCompactFTIndex这里只保存定义倒排内容位于外部搜索服务全局 ID精确 Key__id__NATIVEint32分配全局 Meta IDSpace 局部 ID__local_id__ | spaceIdNATIVEint32Tag、Edge、Index ID 分配Meta 最后更新时间精确 Key__last_update_time__NATIVEint64毫秒时间Schema/拓扑修改时更新Schema 的storageVersion INT64_MAX - logicalVersion。它以原生小端字节进入 Key所以 RocksDB 字节顺序并不可靠地等同于“最新逻辑版本优先”当前读取实现扫描版本并计算最大逻辑版本。CompactSchema中列保持定义顺序。每个 ColumnDef 保存列名、PropertyType、可选长度或地理形状、可选默认表达式二进制、nullable 和注释。SchemaProp 保存 TTL duration、TTL column 和 Schema 注释。列不是独立 KV默认值、NULL 约束也嵌在 Schema Value。当前对象模型没有独立的关系表/列 KV也没有外键、CHECK、UNIQUE、生成列、用户 Sequence、View、Materialized View、Function 或 Procedure 对象。Tag/Edge 是数据容器记录身份由 Tag/Edge Key 隐式保证。Property Index 均为非唯一索引。Alter Schema 只增加新的 Meta Schema 版本不同步重写已有 Row Value。旧行凭 Row header 中的版本选择旧 Schema 读取已经被删除或无法匹配有效 Schema 的行可在后续 compaction 中清理。Drop Tag/Edge 前会检查关联索引成功后删除 Meta 定义但不是在同一个 Meta 操作中同步扫描删除所有 Storage 主记录。4.2 分区、拓扑、鉴权与运维 Meta KV下表覆盖当前版本实际存在的其余 Meta 编码器。host的通用序列化为NATIVEsize_t(hostnameLen) | hostname | NATIVEint32(port)。命名空间/对象Key 的一侧Value 的一侧Part__parts__ | spaceId | partId当前 v2NATIVEint32(2) | host:port, host:portUser__users__ | accountNATIVEsize_t(passwordLen) | passwordRole__roles__ | spaceId | accountNATIVERoleTypeZone__zones__ | zoneName文本 Host 列表Machine__machines__ | serializedHost空Host__hosts__ | serializedHostv2int8(2) | heartbeatMs | HostRole | size_t(gitShaLen) | gitShaHost Directories__host_dirs__ | hostname | portCompactDirInfoLeader Term__leader_terms__ | spaceId | partIdv3int32(3) | size_t(hostLen) | serializedHost | int64(term)Listener__listener__ | spaceId | ListenerType | partIdserializedHostStatistics__stats__ | spaceIdCompactStatsItemConfig__configs__ | Module | int32(nameLen) | nameNATIVEConfigMode | CompactValueSnapshot__snapshots__ | nameNATIVESnapshotStatus | hostsTextExternal Service__services__ | ExternalServiceTypeCompactvectorServiceClientSession__sessions__ | sessionIdCompactSessionDisk Parts__disk_parts__ | size_t(hostLen) | serializedHost | spaceId | pathCompactPartitionListBalance Task__balance_task__ | jobId | spaceId | partId | srcHost | dstHost状态字节、结果字节、开始/结束时间Job__job_mgr__ | spaceId | jobIdJobType、长度化参数数组、状态、开始/结束时间、ErrorCode均为原生字段Job Task__job_mgr__ | spaceId | jobId | taskIdserializedHost、状态、时间和 ErrorCodeMeta 格式版本精确 Key__meta_version__NATIVEint32(V3_44)Cluster ID精确 Key__meta_cluster_id_key__NATIVEint64Segment ID原始 Keysegment_id十进制 ASCIISnowflake Worker ID原始 Keysnowflake_worker_id十进制 ASCIIWorker Host 映射原始 IP 字节作为 Key十进制 ASCII worker ID__versions__ | serializedHost的当前 Value 编码值得特别警惕写端把std::string对象内存的前 8 字节复制到 Value再附加字符串内容而读端把前size_t当作字符串长度。这不是稳定的长度前缀格式是当前源码中写读约定不一致的缺陷不能把样本机器上偶然出现的对象内部字节当成协议。以下前缀仍有兼容或辅助定义但当前正常生产写路径不再创建对应记录旧 Leader__leaders__、旧 Index Status__index_status__、Balance Plan__balance_plan__、旧 Group__groups__以及 Zone 名称映射。它们只应在升级/兼容分析中解释不应混入当前新建数据格式。Storage 进程另有一个 Admin RocksDB。其任务 Key 固定 16 字节seqId | spaceId | jobId | taskId四个原生int32Value 为NATIVEErrorCode | CompactStatsItem。seqId-1且其余为 0 的特殊 Key 保存当前int32序列号。5. Data 如何保存5.1 主记录 KeyTagKey item(partId, Tag) | PAD[vIdLen](vid) | NATIVEint32(tagId) Value RowV2(tag properties)同一个 VID 可以拥有多个 Tag它们以tagId区分。整型 VID 先变成原生 8 字节int64固定字符串 VID 截断或补零到 Space 定义的vIdLen。当前 Space 只允许INT64或FIXED_STRINGVID。可选 Vertex 存在记录Key item(partId, Vertex) | PAD[vIdLen](vid) Value empty默认use_vertex_keyfalse因此不能假设每个逻辑顶点都有这条 KVTag/Edge 本身仍可表达顶点参与的数据。EdgeKey item(partId, Edge) | PAD[vIdLen](src) | NATIVEint32(edgeType) | SORT_I64(rank) | PAD[vIdLen](dst) | uint8(suffix1) Value RowV2(edge properties)edgeType的正负区分出边与入边rank 使用可排序编码。代码能够识别 suffix0 的锁 Key 兼容形式但当前 TOSS 写路径使用内存锁以及 Prime/DoublePrime 恢复记录未发现生产写端生成这种持久锁 Key故不把它作为当前正常记录。Raw KVKey item(partId, KeyValue) | rawUserKey Value rawUserValue用户 Key 占据剩余全部字节没有长度字段或转义Value 对 Storage 完全不透明。Put/Get/Remove 与图 Row 共享 RocksDB但依靠类型前缀隔离。分区系统记录Commit Key item(part,System) | NATIVEuint32(1) Commit Value NATIVEint64(lastLogId) | NATIVEint64(lastTermId) Part Key item(part,System) | NATIVEuint32(2) Part Value empty Balance Key item(part,System) | NATIVEuint32(3) Balance Value textual peers5.2 Row V2 ValueRowV2 1-byte header | 0..7-byte little-endian Schema version | nullable bitmap | fixed region in Schema column order | variable tails | NATIVEint64(writeTimestampMicros)Header 高位标识 V2低 3 位表示 Schema version 使用多少字节。版本 0 的 header 是08版本 1 是09 01。当前 RowReader 入口识别版本标志后只接受 V2旧格式主要由升级工具处理。NULL bitmap 只为 nullable 列分配位按高位优先。固定区仍为 NULL 列保留槽位但槽内字节没有业务语义。变长字段的固定槽通常是NATIVEint32(tailOffset) | NATIVEint32(length)。末尾写时间是写入辅助信息不参与 Key、快照隔离或版本选择。5.3 全部 PropertyType 的 Value 编码与索引能力PropertyTypeRow Value 固定槽/尾部Index Key 编码当前状态UNKNOWN无合法持久化定义不支持DDL 拒绝BOOL1 字节1 字节支持INT64原生 8 字节SORT_I64支持VID历史 v1 类型不支持当前 DDL 拒绝VID 身份由 Space 类型和 Key 决定FLOAT原生 IEEE-754 4 字节提升为 sortable double 8 字节支持DOUBLE原生 IEEE-754 8 字节sortable double 8 字节支持STRING固定槽offset:int32 len:int32尾部原字节必须指定前缀长度索引定义落为 FIXED_STRING截断/补零支持FIXED_STRINGSchema 长度的原位字节UTF-8 安全截断、补零Schema 长度的原字节支持INT8原生 1 字节归一为SORT_I64支持INT16原生 2 字节归一为SORT_I64支持INT32原生 4 字节归一为SORT_I64支持TIMESTAMP原生int64SORT_I64支持也可作为 TTL 列DURATIONint64 seconds int32 micros int32 months共 16 字节不支持Row 支持索引 DDL 拒绝DATEint16 year int8 month int8 day共 4 字节各分量大端有序编码支持DATETIMEint16 year 5*int8 int32 micros共 11 字节各分量大端有序编码支持以实现的 11 字节为准TIME3*int8 int32 micros共 7 字节各分量大端有序编码支持GEOGRAPHY固定槽 offsetlen尾部 WKB每个 S2 covering cell 一个 8 字节大端 cell id支持当前只允许单列地理索引LIST_STRING固定槽 8 字节只写前 4 字节 offset尾部int32 count [int32 lenbytes]*不支持Row 支持LIST_INT同上尾部 count 后每项存原生int32不支持Row 支持列表元素在当前编码中收窄为 int32LIST_FLOAT同上尾部 count 后每项原生 float不支持Row 支持SET_STRING与 LIST_STRING 相同的元素格式不支持Row 支持无序迭代导致字节序不保证稳定SET_INT与 LIST_INT 相同的元素格式不支持Row 支持元素唯一但顺序不保证SET_FLOAT与 LIST_FLOAT 相同的元素格式不支持Row 支持元素唯一但顺序不保证索引 sortable double 的规则是NaN 编成 8 个ff非负数的 IEEE 位转大端后设置最高位负数先逐位取反再转大端。这样 RocksDB 字节序与数值顺序一致。当前 PropertyType 枚举之外没有 Decimal、JSON、Map、Struct 或 Vector 的行编码本文不臆造这些格式。5.4 Property Index顶点索引Key item(part,Index) | NATIVEint32(indexId) | encodedColumn1 ... encodedColumnN | [NATIVEuint16(nullableBits)] | PAD[vIdLen](vid)Edge 索引Key item(part,Index) | NATIVEint32(indexId) | encodedColumn1 ... encodedColumnN | [NATIVEuint16(nullableBits)] | PAD[vIdLen](src) | SORT_I64(rank) | PAD[vIdLen](dst)只有 Schema 含 nullable 索引列时才附加 16 位 bitmap因此最多 16 个索引列。NULL 的属性区域用该类型全ff的固定宽度占位bitmap 再区分“真实最大值”与 NULL。索引都是非唯一索引原记录定位符位于 Key 末尾使相同属性值可对应多条记录。没有 included column、predicate、expression 或 partial-index 字段。Value empty是常态。启用有效 TTL 时Value NATIVEsize_t(compactPayloadLength) | CompactValue(ttlColumnValue)这样 compaction filter 无需回主记录即可判断索引是否过期。写主记录时处理器先读取旧 Row删除由旧属性生成的 index Key再把新 index Key 和新主 Key 放入同一批写。Edge 只给正edgeType的出边建索引。等值扫描使用索引列前缀范围扫描构造[start,end)边界有序整数、浮点和时间编码确保 RocksDB lexicographic order 可用。扫描可从 Key 解码固定索引列和定位符。以下情况必须回主记录请求了非索引列FIXED_STRING 或由 STRING 前缀形成的索引列因截断/补零可能丢失原值GEOGRAPHY需要原几何精确判断并对多个 cell 命中去重。5.5 TTL、版本、删除与恢复内部记录TTL 配置位于 Schema Value包含ttl_duration与ttl_colTTL 列只能按 INT64/TIMESTAMP 语义计算。默认单位为秒可通过配置切为毫秒。主 Row 保留完整 TTL 属性索引 Value 只复制 TTL 列。Compaction filter 解码主 Row 或索引 Value 后判断过期NULL 或非整数语义不会过期。到期时间不进入 Key也不使用 Row 尾部写时间替代 TTL 列。普通 Tag/Edge 更新覆盖相同 Key历史值只可能暂时存在于 RocksDB 内部版本中不是 Nebula 可查询的 MVCC。Nebula 应用 Key 没有 commit timestamp。普通删除不写业务 tombstone Value。索引重建期间主写路径额外写 Operation 记录以弥补扫描与并发写之间的窗口Modify Key item(part,Operation) | BEint64(timestampMicros) | NATIVEuint32(1) | fullIndexKey Modify Value indexValue Delete Key item(part,Operation) | BEint64(timestampMicros) | NATIVEuint32(2) Delete Value oldFullIndexKeyTOSS 的持久恢复记录为Prime Key item(part,Prime) | completeNormalEdgeKey DoublePrime Key item(part,DoublePrime) | completeNormalEdgeKey Value Compactsingle edge request | ASCII operation operation a | u | d请求类型分别是单条 AddEdgesRequest、UpdateEdgeRequest 或 DeleteEdgesRequest。Leader 切换时 Transaction Manager 扫描 Prime/DoublePrime 前缀恢复内存锁并重试未完成事务。分区快照包含 Vertex、Tag、Edge、Index 与 Raw KV 前缀System 和 Operation 不按普通用户数据快照处理。分区范围清理覆盖主要数据命名空间当前清理例程对 Prime/DoublePrime 仍有待完善项这是实现边界而不是额外的数据格式。6. 数据库设计的启示设计点NebulaGraph 的做法与收益代价/可改进点按 Space 物理分库Data Key 省去 spaceId备份与隔离直观多 Space 带来更多 RocksDB 实例与后台线程part type公共前缀分区迁移、前缀扫描、范围删除简单4 字节原生编码存在平台依赖类型放低字节是小端假设身份全部在 KeyTag/Edge 点查无需解析 ValueEdge 自然按 rank 排序长字符串 VID 会放大主 Key 和每个索引 KeySchema 版本不重写旧行Alter 快旧数据惰性兼容Reader 必须长期持有历史 Schema回收旧版本需谨慎Row 固定区 tail固定字段 O(1) 定位变长字段紧凑offset/长度和多数标量为原生格式跨架构迁移困难索引 Value 通常为空覆盖查询与定位成本低重复值靠 locator 自然区分每次更新要读旧行并重建索引截断字符串常需回表NULL 占位 bitmap保持索引列固定宽度和范围构造能力bitmap 原生端序且最多 16 列TTL 副本仅放索引 Value索引 compaction 不回表主值与索引副本必须原子维护正负 Edge Type出入边共享一种 Key 布局方向扫描直接一条逻辑边通常写两份跨分区一致性需要 TOSS外部全文检索利用成熟倒排引擎Meta 只保存定义备份/一致性边界跨系统原生整数与size_t编码简单、CPU 成本低ABI、端序、32/64 位兼容性弱新系统宜统一固定宽度大端/小端协议直接 Rocks Delete应用层简单无自定义墓碑解析时间旅行、审计和逻辑 CDC 需另建版本/日志层如果为新的底层 KV 数据库复用这些思路建议保留“物理租户隔离、类型前缀、locator 后缀、Schema 版本化、TTL 索引副本”五点同时把所有协议字段改为固定宽度、明确端序给 Key 段建立统一编码库在格式头加入版本与校验将 List/Set 元素宽度、Meta host 编码和 Version Value 这类隐式 ABI 行为消除。