物皆可KV(5)Nubela存儲(chǔ)布局分析)
1. 結(jié)論NebulaGraph 的當(dāng)前持久化后端是 RocksDB。每個(gè) Graph Space 在每個(gè)數(shù)據(jù)目錄下有獨(dú)立數(shù)據(jù)庫(kù)路徑語(yǔ)義為nebula/spaceId/data使用默認(rèn) Column Family因此 Graph Data Key 不再重復(fù)編碼spaceId。Meta 也使用 RocksDB但固定在 Meta 進(jìn)程的space0, part0邏輯域。HBase 相關(guān)實(shí)現(xiàn)仍在代碼樹(shù)中但當(dāng)前存儲(chǔ)工廠不選擇它不屬于本文的現(xiàn)行落盤格式。Schema 與 Data 都是同一個(gè)簡(jiǎn)單模型Key - Value。Schema 不是獨(dú)立文件而是 Meta RocksDB 中帶 ASCII 命名空間前綴的 KV圖記錄則是 Space RocksDB 中帶二進(jìn)制類型前綴的 KV。Tag 和 Edge 的身份全部進(jìn)入 Key屬性行、Schema 版本、TTL 配置等進(jìn)入 Value。普通屬性更新覆蓋同一 Key沒(méi)有應(yīng)用層 MVCC 版本后綴。二級(jí)索引把“可排序的屬性編碼 原記錄定位符”放入 KeyValue 通常為空只有啟用 Schema TTL 時(shí)索引 Value 才保存緊湊編碼的 TTL 屬性副本。刪除直接調(diào)用 RocksDBDelete或批量Delete范圍清理使用DeleteRange。應(yīng)用層沒(méi)有普通行墓碑格式。當(dāng)前格式混合了原生整數(shù)編碼與顯式可排序大端編碼身份、ID、Meta 字段大量依賴本機(jī)字節(jié)序和size_t索引范圍字段與 Edge rank 才使用跨符號(hào)有序編碼。本文的精確樣本來(lái)自當(dāng)前x86_64環(huán)境即小端、size_t8。屬性 Value 使用 Row V2Schema 決定字段類型Value 不逐字段攜帶類型標(biāo)簽?zāi)┪脖4?8 字節(jié)寫入微秒時(shí)間但它不是 MVCC 版本也不是 TTL 到期時(shí)間。全文索引的定義在 Meta RocksDB倒排內(nèi)容在外部 Elasticsearch當(dāng)前沒(méi)有向量索引 KV 格式。2. 總體存儲(chǔ)布局RocksDB ├── Meta 服務(wù)數(shù)據(jù)庫(kù)邏輯 space0 / part0 │ ├── SchemaSpace、Tag、Edge、Index、名稱映射 │ ├── 鑒權(quán)與拓?fù)銾ser、Role、Host、Zone、Part、Leader │ └── 運(yùn)維狀態(tài)Job、Balance、Snapshot、Session、Config、Service └── Storage 數(shù)據(jù)庫(kù)每個(gè) Graph Space、每個(gè) data path 一庫(kù) ├── Tag / Vertex / Edge 主記錄 ├── Property Index ├── Raw KV ├── Raft/分區(qū)系統(tǒng)記錄 └── TOSS 事務(wù)恢復(fù)記錄本文用以下記號(hào)描述字節(jié)布局LET當(dāng)前機(jī)器原生小端整數(shù)BET顯式大端整數(shù)。NATIVET實(shí)現(xiàn)直接復(fù)制 C 對(duì)象字節(jié)當(dāng)前環(huán)境表現(xiàn)為小端但格式本身依賴平臺(tái)。STR[n]恰好 n 字節(jié)不含終止零PAD[n]截?cái)嗷蜓a(bǔ)零到固定長(zhǎng)度。CompactTApache Thrift Compact Protocol 序列化。SORT_I64(x)x的 64 位二進(jìn)制先翻轉(zhuǎn)符號(hào)位再轉(zhuǎn)大端使字節(jié)序與有符號(hào)數(shù)序一致。item(part,type)LEuint32((part 8) | type)。在小端機(jī)器上首字節(jié)就是類型隨后三字節(jié)承載分區(qū)號(hào)。2.1 Graph Data 頂層 Key 類型類型值Key 的一側(cè)Value 的一側(cè)當(dāng)前用途1 Tagitem | vid | tagIdRow V2頂點(diǎn)上的 Tag 屬性2 Edgeitem | src | edgeType | rank | dst | suffixRow V2有向邊屬性3 Indexitem | indexId | indexedValues | locator空或 TTL 副本屬性二級(jí)索引4 Systemitem | systemSubType日志位置、Peer 等分區(qū)系統(tǒng)記錄5 Operationitem | timestamp | opType [| indexKey]index Value 或舊 index Key索引重建期間的補(bǔ)償操作6 KeyValueitem | userKey不透明用戶字節(jié)Storage Raw KV API7 Vertexitem | vid空可選的顯式頂點(diǎn)存在記錄8 Primeitem | normalEdgeKeyCompact 請(qǐng)求 操作字符TOSS 第一階段恢復(fù)記錄9 DoublePrimeitem | normalEdgeKeyCompact 請(qǐng)求 操作字符TOSS 第二階段恢復(fù)記錄spaceId不在上述 Key 中因?yàn)閿?shù)據(jù)庫(kù)目錄已經(jīng)隔離 SpacepartId位于每個(gè)item中使同一分區(qū)與同一對(duì)象類型形成連續(xù)前綴。2.2 RocksDB 層行為單條寫入是 RocksDBPut原子多對(duì)象更新使用WriteBatch。單 Key 刪除是Delete區(qū)間清理是默認(rèn) Column Family 上的DeleteRange。SST 壓縮和可選 BlobDB 是 RocksDB 物理層選擇不改變本文描述的應(yīng)用層 Key/Value。非零 Space 存在數(shù)據(jù)格式標(biāo)記Key 為ff ff ff ffValue 為 ASCII3.0。應(yīng)用編碼沒(méi)有統(tǒng)一 checksum、壓縮 envelope 或行級(jí)加密頭。3. 當(dāng)前編碼器生成的真實(shí) KV 樣本以下四組樣本由當(dāng)前版本生產(chǎn) Key 工具、Row V2、Meta Key 工具和 Thrift Compact 編碼器實(shí)際生成并立即按生產(chǎn)解碼路徑反解不是手工拼接的示意值。VID 長(zhǎng)度取 8。各樣本是分別驗(yàn)證單個(gè)編碼族的獨(dú)立 fixture其中 3.2 為覆蓋 Row V2 變長(zhǎng)字段而使用四列臨時(shí) Schema不應(yīng)誤認(rèn)為它與 3.1 的兩列 Meta Schema 是同一批可聯(lián)合寫入的數(shù)據(jù)。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寫入時(shí)創(chuàng)建 Tag 同批寫入“名稱到 Tag ID”映射、版本 0 Schema 和 Meta 最后更新時(shí)間。讀取時(shí)按spaceId tagId前綴掃描所有 Schema 版本選擇最大邏輯版本再用 Value 開(kāi)頭的名稱長(zhǎng)度與 Compact Schema 還原字段。Alter 會(huì)新增版本 KV舊版本保留Drop 批量刪除全部版本和名稱映射。3.2 Tag 主記錄頂點(diǎn)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 字節(jié) 80 nullable bitmapage 為 NULL 4164610000000000 FIXED_STRING(8) Ada 0000000000000000 age 的 8 字節(jié)固定槽NULL 時(shí)內(nèi)容不參與語(yǔ)義 01 BOOL true 1b000000 05000000 STRING 描述符tail offset27length5 6772617068 tail graph 598452cfb9570600 NATIVEint64(write_ts_us1785305419121753)寫入時(shí)根據(jù) Space 的 VID 長(zhǎng)度構(gòu)造 Key根據(jù) Tag Schema 構(gòu)造 Row V2 Value。點(diǎn)查詢以完整 Key 讀取前綴掃描可枚舉一個(gè)分區(qū)或 VID 的 TagValue 交給與 header 版本匹配的 Schema 解碼。最終返回業(yè)務(wù)字段時(shí)不返回末尾寫時(shí)間。3.3 頂點(diǎn)屬性索引(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占位區(qū)分因此 Key 另帶原生 16 位 NULL bitmap第二列對(duì)應(yīng)0x4000當(dāng)前小端字節(jié)為00 40。非 TTL Schema 的索引 Value 為空。掃描(nameAda, age IS NULL)時(shí)用屬性前綴構(gòu)造范圍再?gòu)?Key 尾部還原 VID如果請(qǐng)求字段僅由安全可解碼的索引列覆蓋可以不回表否則用 VID 和 Tag ID 重建主 Key 回表。3.4 Edge 主記錄v42-[301-7]-v99邏輯輸入Part ID2源 VIDv42Edge Type301Rank-7目標(biāo) 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 字節(jié) 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。插入雙向圖語(yǔ)義時(shí)源分區(qū)保存正 Edge Type 的出邊目標(biāo)分區(qū)保存負(fù) Edge Type 的入邊屬性索引只針對(duì)正類型出邊。讀取后以 Edge Schema 解碼 Value并從 Key 返回src/type/rank/dst。4. Schema 如何保存4.1 核心 Schema 對(duì)象Meta Key 的命名空間是可讀 ASCII后續(xù) ID 多為原生整數(shù)。表中“Key”與“Value”分別描述 KV 的兩側(cè)。對(duì)象KeyValue生命周期Space__spaces__ | NATIVEspaceIdCompactSpaceDesc含名稱、分區(qū)數(shù)、副本數(shù)、字符集、排序規(guī)則、VID 類型與長(zhǎng)度、Zone、隔離級(jí)別、注釋Create 寫入Alter 覆蓋Drop 刪除并觸發(fā)數(shù)據(jù)清理Space 名稱映射__index__ | SPACE | nameNATIVEspaceId與 Space 同批維護(hù)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創(chuàng)建與刪除時(shí)維護(hù)Property Index 定義__indexes__ | spaceId | indexIdCompactIndexItem含目標(biāo) Schema、索引名、列名、列類型/長(zhǎng)度、可選 Schema IDCreate/Drop實(shí)際索引數(shù)據(jù)在 Storage RocksDBIndex 名稱映射__index__ | INDEX | spaceId | nameNATIVEindexId與 Index 定義同批維護(hù)Fulltext Index 定義__ft_index__ | nameCompactFTIndex這里只保存定義倒排內(nèi)容位于外部搜索服務(wù)全局 ID精確 Key__id__NATIVEint32分配全局 Meta IDSpace 局部 ID__local_id__ | spaceIdNATIVEint32Tag、Edge、Index ID 分配Meta 最后更新時(shí)間精確 Key__last_update_time__NATIVEint64毫秒時(shí)間Schema/拓?fù)湫薷臅r(shí)更新Schema 的storageVersion INT64_MAX - logicalVersion。它以原生小端字節(jié)進(jìn)入 Key所以 RocksDB 字節(jié)順序并不可靠地等同于“最新邏輯版本優(yōu)先”當(dāng)前讀取實(shí)現(xiàn)掃描版本并計(jì)算最大邏輯版本。CompactSchema中列保持定義順序。每個(gè) ColumnDef 保存列名、PropertyType、可選長(zhǎng)度或地理形狀、可選默認(rèn)表達(dá)式二進(jìn)制、nullable 和注釋。SchemaProp 保存 TTL duration、TTL column 和 Schema 注釋。列不是獨(dú)立 KV默認(rèn)值、NULL 約束也嵌在 Schema Value。當(dāng)前對(duì)象模型沒(méi)有獨(dú)立的關(guān)系表/列 KV也沒(méi)有外鍵、CHECK、UNIQUE、生成列、用戶 Sequence、View、Materialized View、Function 或 Procedure 對(duì)象。Tag/Edge 是數(shù)據(jù)容器記錄身份由 Tag/Edge Key 隱式保證。Property Index 均為非唯一索引。Alter Schema 只增加新的 Meta Schema 版本不同步重寫已有 Row Value。舊行憑 Row header 中的版本選擇舊 Schema 讀取已經(jīng)被刪除或無(wú)法匹配有效 Schema 的行可在后續(xù) compaction 中清理。Drop Tag/Edge 前會(huì)檢查關(guān)聯(lián)索引成功后刪除 Meta 定義但不是在同一個(gè) Meta 操作中同步掃描刪除所有 Storage 主記錄。4.2 分區(qū)、拓?fù)?、鑒權(quán)與運(yùn)維 Meta KV下表覆蓋當(dāng)前版本實(shí)際存在的其余 Meta 編碼器。host的通用序列化為NATIVEsize_t(hostnameLen) | hostname | NATIVEint32(port)。命名空間/對(duì)象Key 的一側(cè)Value 的一側(cè)Part__parts__ | spaceId | partId當(dāng)前 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狀態(tài)字節(jié)、結(jié)果字節(jié)、開(kāi)始/結(jié)束時(shí)間Job__job_mgr__ | spaceId | jobIdJobType、長(zhǎng)度化參數(shù)數(shù)組、狀態(tài)、開(kāi)始/結(jié)束時(shí)間、ErrorCode均為原生字段Job Task__job_mgr__ | spaceId | jobId | taskIdserializedHost、狀態(tài)、時(shí)間和 ErrorCodeMeta 格式版本精確 Key__meta_version__NATIVEint32(V3_44)Cluster ID精確 Key__meta_cluster_id_key__NATIVEint64Segment ID原始 Keysegment_id十進(jìn)制 ASCIISnowflake Worker ID原始 Keysnowflake_worker_id十進(jìn)制 ASCIIWorker Host 映射原始 IP 字節(jié)作為 Key十進(jìn)制 ASCII worker ID__versions__ | serializedHost的當(dāng)前 Value 編碼值得特別警惕寫端把std::string對(duì)象內(nèi)存的前 8 字節(jié)復(fù)制到 Value再附加字符串內(nèi)容而讀端把前size_t當(dāng)作字符串長(zhǎng)度。這不是穩(wěn)定的長(zhǎng)度前綴格式是當(dāng)前源碼中寫讀約定不一致的缺陷不能把樣本機(jī)器上偶然出現(xiàn)的對(duì)象內(nèi)部字節(jié)當(dāng)成協(xié)議。以下前綴仍有兼容或輔助定義但當(dāng)前正常生產(chǎn)寫路徑不再創(chuàng)建對(duì)應(yīng)記錄舊 Leader__leaders__、舊 Index Status__index_status__、Balance Plan__balance_plan__、舊 Group__groups__以及 Zone 名稱映射。它們只應(yīng)在升級(jí)/兼容分析中解釋不應(yīng)混入當(dāng)前新建數(shù)據(jù)格式。Storage 進(jìn)程另有一個(gè) Admin RocksDB。其任務(wù) Key 固定 16 字節(jié)seqId | spaceId | jobId | taskId四個(gè)原生int32Value 為NATIVEErrorCode | CompactStatsItem。seqId-1且其余為 0 的特殊 Key 保存當(dāng)前int32序列號(hào)。5. Data 如何保存5.1 主記錄 KeyTagKey item(partId, Tag) | PAD[vIdLen](vid) | NATIVEint32(tagId) Value RowV2(tag properties)同一個(gè) VID 可以擁有多個(gè) Tag它們以tagId區(qū)分。整型 VID 先變成原生 8 字節(jié)int64固定字符串 VID 截?cái)嗷蜓a(bǔ)零到 Space 定義的vIdLen。當(dāng)前 Space 只允許INT64或FIXED_STRINGVID??蛇x Vertex 存在記錄Key item(partId, Vertex) | PAD[vIdLen](vid) Value empty默認(rèn)use_vertex_keyfalse因此不能假設(shè)每個(gè)邏輯頂點(diǎn)都有這條 KVTag/Edge 本身仍可表達(dá)頂點(diǎn)參與的數(shù)據(jù)。EdgeKey item(partId, Edge) | PAD[vIdLen](src) | NATIVEint32(edgeType) | SORT_I64(rank) | PAD[vIdLen](dst) | uint8(suffix1) Value RowV2(edge properties)edgeType的正負(fù)區(qū)分出邊與入邊rank 使用可排序編碼。代碼能夠識(shí)別 suffix0 的鎖 Key 兼容形式但當(dāng)前 TOSS 寫路徑使用內(nèi)存鎖以及 Prime/DoublePrime 恢復(fù)記錄未發(fā)現(xiàn)生產(chǎn)寫端生成這種持久鎖 Key故不把它作為當(dāng)前正常記錄。Raw KVKey item(partId, KeyValue) | rawUserKey Value rawUserValue用戶 Key 占據(jù)剩余全部字節(jié)沒(méi)有長(zhǎng)度字段或轉(zhuǎn)義Value 對(duì) Storage 完全不透明。Put/Get/Remove 與圖 Row 共享 RocksDB但依靠類型前綴隔離。分區(qū)系統(tǒng)記錄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 高位標(biāo)識(shí) V2低 3 位表示 Schema version 使用多少字節(jié)。版本 0 的 header 是08版本 1 是09 01。當(dāng)前 RowReader 入口識(shí)別版本標(biāo)志后只接受 V2舊格式主要由升級(jí)工具處理。NULL bitmap 只為 nullable 列分配位按高位優(yōu)先。固定區(qū)仍為 NULL 列保留槽位但槽內(nèi)字節(jié)沒(méi)有業(yè)務(wù)語(yǔ)義。變長(zhǎng)字段的固定槽通常是NATIVEint32(tailOffset) | NATIVEint32(length)。末尾寫時(shí)間是寫入輔助信息不參與 Key、快照隔離或版本選擇。5.3 全部 PropertyType 的 Value 編碼與索引能力PropertyTypeRow Value 固定槽/尾部Index Key 編碼當(dāng)前狀態(tài)UNKNOWN無(wú)合法持久化定義不支持DDL 拒絕BOOL1 字節(jié)1 字節(jié)支持INT64原生 8 字節(jié)SORT_I64支持VID歷史 v1 類型不支持當(dāng)前 DDL 拒絕VID 身份由 Space 類型和 Key 決定FLOAT原生 IEEE-754 4 字節(jié)提升為 sortable double 8 字節(jié)支持DOUBLE原生 IEEE-754 8 字節(jié)sortable double 8 字節(jié)支持STRING固定槽offset:int32 len:int32尾部原字節(jié)必須指定前綴長(zhǎng)度索引定義落為 FIXED_STRING截?cái)?補(bǔ)零支持FIXED_STRINGSchema 長(zhǎng)度的原位字節(jié)UTF-8 安全截?cái)?、補(bǔ)零Schema 長(zhǎng)度的原字節(jié)支持INT8原生 1 字節(jié)歸一為SORT_I64支持INT16原生 2 字節(jié)歸一為SORT_I64支持INT32原生 4 字節(jié)歸一為SORT_I64支持TIMESTAMP原生int64SORT_I64支持也可作為 TTL 列DURATIONint64 seconds int32 micros int32 months共 16 字節(jié)不支持Row 支持索引 DDL 拒絕DATEint16 year int8 month int8 day共 4 字節(jié)各分量大端有序編碼支持DATETIMEint16 year 5*int8 int32 micros共 11 字節(jié)各分量大端有序編碼支持以實(shí)現(xiàn)的 11 字節(jié)為準(zhǔn)TIME3*int8 int32 micros共 7 字節(jié)各分量大端有序編碼支持GEOGRAPHY固定槽 offsetlen尾部 WKB每個(gè) S2 covering cell 一個(gè) 8 字節(jié)大端 cell id支持當(dāng)前只允許單列地理索引LIST_STRING固定槽 8 字節(jié)只寫前 4 字節(jié) offset尾部int32 count [int32 lenbytes]*不支持Row 支持LIST_INT同上尾部 count 后每項(xiàng)存原生int32不支持Row 支持列表元素在當(dāng)前編碼中收窄為 int32LIST_FLOAT同上尾部 count 后每項(xiàng)原生 float不支持Row 支持SET_STRING與 LIST_STRING 相同的元素格式不支持Row 支持無(wú)序迭代導(dǎo)致字節(jié)序不保證穩(wěn)定SET_INT與 LIST_INT 相同的元素格式不支持Row 支持元素唯一但順序不保證SET_FLOAT與 LIST_FLOAT 相同的元素格式不支持Row 支持元素唯一但順序不保證索引 sortable double 的規(guī)則是NaN 編成 8 個(gè)ff非負(fù)數(shù)的 IEEE 位轉(zhuǎn)大端后設(shè)置最高位負(fù)數(shù)先逐位取反再轉(zhuǎn)大端。這樣 RocksDB 字節(jié)序與數(shù)值順序一致。當(dāng)前 PropertyType 枚舉之外沒(méi)有 Decimal、JSON、Map、Struct 或 Vector 的行編碼本文不臆造這些格式。5.4 Property Index頂點(diǎn)索引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 索引列時(shí)才附加 16 位 bitmap因此最多 16 個(gè)索引列。NULL 的屬性區(qū)域用該類型全ff的固定寬度占位bitmap 再區(qū)分“真實(shí)最大值”與 NULL。索引都是非唯一索引原記錄定位符位于 Key 末尾使相同屬性值可對(duì)應(yīng)多條記錄。沒(méi)有 included column、predicate、expression 或 partial-index 字段。Value empty是常態(tài)。啟用有效 TTL 時(shí)Value NATIVEsize_t(compactPayloadLength) | CompactValue(ttlColumnValue)這樣 compaction filter 無(wú)需回主記錄即可判斷索引是否過(guò)期。寫主記錄時(shí)處理器先讀取舊 Row刪除由舊屬性生成的 index Key再把新 index Key 和新主 Key 放入同一批寫。Edge 只給正edgeType的出邊建索引。等值掃描使用索引列前綴范圍掃描構(gòu)造[start,end)邊界有序整數(shù)、浮點(diǎn)和時(shí)間編碼確保 RocksDB lexicographic order 可用。掃描可從 Key 解碼固定索引列和定位符。以下情況必須回主記錄請(qǐng)求了非索引列FIXED_STRING 或由 STRING 前綴形成的索引列因截?cái)?補(bǔ)零可能丟失原值GEOGRAPHY需要原幾何精確判斷并對(duì)多個(gè) cell 命中去重。5.5 TTL、版本、刪除與恢復(fù)內(nèi)部記錄TTL 配置位于 Schema Value包含ttl_duration與ttl_colTTL 列只能按 INT64/TIMESTAMP 語(yǔ)義計(jì)算。默認(rèn)單位為秒可通過(guò)配置切為毫秒。主 Row 保留完整 TTL 屬性索引 Value 只復(fù)制 TTL 列。Compaction filter 解碼主 Row 或索引 Value 后判斷過(guò)期NULL 或非整數(shù)語(yǔ)義不會(huì)過(guò)期。到期時(shí)間不進(jìn)入 Key也不使用 Row 尾部寫時(shí)間替代 TTL 列。普通 Tag/Edge 更新覆蓋相同 Key歷史值只可能暫時(shí)存在于 RocksDB 內(nèi)部版本中不是 Nebula 可查詢的 MVCC。Nebula 應(yīng)用 Key 沒(méi)有 commit timestamp。普通刪除不寫業(yè)務(wù) tombstone Value。索引重建期間主寫路徑額外寫 Operation 記錄以彌補(bǔ)掃描與并發(fā)寫之間的窗口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 的持久恢復(fù)記錄為Prime Key item(part,Prime) | completeNormalEdgeKey DoublePrime Key item(part,DoublePrime) | completeNormalEdgeKey Value Compactsingle edge request | ASCII operation operation a | u | d請(qǐng)求類型分別是單條 AddEdgesRequest、UpdateEdgeRequest 或 DeleteEdgesRequest。Leader 切換時(shí) Transaction Manager 掃描 Prime/DoublePrime 前綴恢復(fù)內(nèi)存鎖并重試未完成事務(wù)。分區(qū)快照包含 Vertex、Tag、Edge、Index 與 Raw KV 前綴System 和 Operation 不按普通用戶數(shù)據(jù)快照處理。分區(qū)范圍清理覆蓋主要數(shù)據(jù)命名空間當(dāng)前清理例程對(duì) Prime/DoublePrime 仍有待完善項(xiàng)這是實(shí)現(xiàn)邊界而不是額外的數(shù)據(jù)格式。6. 數(shù)據(jù)庫(kù)設(shè)計(jì)的啟示設(shè)計(jì)點(diǎn)NebulaGraph 的做法與收益代價(jià)/可改進(jìn)點(diǎn)按 Space 物理分庫(kù)Data Key 省去 spaceId備份與隔離直觀多 Space 帶來(lái)更多 RocksDB 實(shí)例與后臺(tái)線程part type公共前綴分區(qū)遷移、前綴掃描、范圍刪除簡(jiǎn)單4 字節(jié)原生編碼存在平臺(tái)依賴類型放低字節(jié)是小端假設(shè)身份全部在 KeyTag/Edge 點(diǎn)查無(wú)需解析 ValueEdge 自然按 rank 排序長(zhǎng)字符串 VID 會(huì)放大主 Key 和每個(gè)索引 KeySchema 版本不重寫舊行Alter 快舊數(shù)據(jù)惰性兼容Reader 必須長(zhǎng)期持有歷史 Schema回收舊版本需謹(jǐn)慎Row 固定區(qū) tail固定字段 O(1) 定位變長(zhǎng)字段緊湊offset/長(zhǎng)度和多數(shù)標(biāo)量為原生格式跨架構(gòu)遷移困難索引 Value 通常為空覆蓋查詢與定位成本低重復(fù)值靠 locator 自然區(qū)分每次更新要讀舊行并重建索引截?cái)嘧址P杌乇鞱ULL 占位 bitmap保持索引列固定寬度和范圍構(gòu)造能力bitmap 原生端序且最多 16 列TTL 副本僅放索引 Value索引 compaction 不回表主值與索引副本必須原子維護(hù)正負(fù) Edge Type出入邊共享一種 Key 布局方向掃描直接一條邏輯邊通常寫兩份跨分區(qū)一致性需要 TOSS外部全文檢索利用成熟倒排引擎Meta 只保存定義備份/一致性邊界跨系統(tǒng)原生整數(shù)與size_t編碼簡(jiǎn)單、CPU 成本低ABI、端序、32/64 位兼容性弱新系統(tǒng)宜統(tǒng)一固定寬度大端/小端協(xié)議直接 Rocks Delete應(yīng)用層簡(jiǎn)單無(wú)自定義墓碑解析時(shí)間旅行、審計(jì)和邏輯 CDC 需另建版本/日志層如果為新的底層 KV 數(shù)據(jù)庫(kù)復(fù)用這些思路建議保留“物理租戶隔離、類型前綴、locator 后綴、Schema 版本化、TTL 索引副本”五點(diǎn)同時(shí)把所有協(xié)議字段改為固定寬度、明確端序給 Key 段建立統(tǒng)一編碼庫(kù)在格式頭加入版本與校驗(yàn)將 List/Set 元素寬度、Meta host 編碼和 Version Value 這類隱式 ABI 行為消除。