Skip to content

Commit fb02386

Browse files
committed
[manager] harden bounded host cache query compatibility
Preserve the existing API and wire contracts while adding explicit per-key spec filtering. Replace request-wide P2P and Mamba projections with ordered bounded reducers, retain exact fetch semantics, harden metadata and metrics error handling, and add differential, sanitizer, and million-key coverage.
1 parent 334a991 commit fb02386

22 files changed

Lines changed: 2549 additions & 560 deletions

docs/api/report_event.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,9 @@ HTTP 接口为 `POST /api/getCacheLocation`:
619619
`ST_EVENT_REPORT_L1P5``ST_EVENT_REPORT_L2` 等 backend,适合验证两种 EventReport storage 的
620620
隔离状态。
621621

622-
`location_spec_names` 不只是返回结果的投影条件,也是 backend/peer 选择前按 query key 生效的候选条件:
622+
`location_spec_names` 保持原有语义:它是应用到每个 query key 的全局返回投影;为空时返回
623+
选中 location 的全部 spec。需要在 backend/peer 选择前按 query key 限制候选时,使用新增的
624+
`per_key_location_spec_names`
623625

624626
- 为空时,location 中任意合法 spec 都可使该 location 成为候选;
625627
- 非空时,数组长度必须等于 query key 数量,且每个 name 都不能为空;第 i 个 name 只过滤第 i 个 key;
@@ -631,7 +633,7 @@ HTTP 接口为 `POST /api/getCacheLocation`:
631633

632634
因此 spec name 是 reporter 与查询方之间的稳定协议字段,不能用 object size 代替:不同 cache
633635
group 即使 byte size 相同,也必须使用不同且稳定的 spec name。调用方必须让
634-
`location_spec_names``block_keys`(或由 token 生成的 query keys)同序对齐。同一个 block key
636+
`per_key_location_spec_names``block_keys`(或由 token 生成的 query keys)同序对齐。同一个 block key
635637
可以在不同位置重复并请求不同 spec,用于 mixed-attention/Mamba groups。长度不匹配或包含空
636638
name 会返回 `INVALID_ARGUMENT`。确定性 tie-break 只消除无序遍历造成的抖动;各 key 经过
637639
spec 过滤后的候选 peer 集合仍可能不同。
@@ -662,7 +664,7 @@ HTTP 接口为 `POST /api/getHostCacheState`:
662664
"hosts": [
663665
{
664666
"host_ip_port": "10.0.0.8:8080",
665-
"local": "3",
667+
"prefix_match_blocks": "3",
666668
"p2p_1_fetch": "0",
667669
"p2p_1_total_match": "3"
668670
}
@@ -680,17 +682,17 @@ HTTP 接口为 `POST /api/getHostCacheState`:
680682
- `QT_UNSPECIFIED` 使用 RegisterInstance 时配置的 `default_query_type`
681683
- 支持 `QT_PREFIX_MATCH``QT_PREFIX_MATCH_WITH_MAMBA`,其他类型返回参数错误;
682684
- 同一个 host 在多个 backend 的有效 cache 会按 host 汇总参与匹配;
683-
- `local` 包含同一 host 的 subscriber 与 Vineyard 上报;
684-
- 非混合注意力对 full local-miss 使用 Prefix 选择远端 Vineyard;混合注意力先对
685+
- `prefix_match_blocks` 包含同一 host 的 subscriber 与 Vineyard 上报;
686+
- 非混合注意力对 full local miss 使用 Prefix 选择远端 Vineyard;混合注意力先对
685687
FullAttention group 使用 Prefix,再对 Mamba local-miss spec 使用 Coverage;
686688
- `p2p_1_fetch` 表示各 P2P 阶段实际选中并拉取的 spec 所属的去重 block key 数;
687689
- `p2p_1_total_match` 表示本地 cache 与实际选中的远端 spec 合并后的最终前缀;
688-
- 远端 P2P 候选只使用 `ST_EVENT_REPORT_L2`,且不会让 `local` 为 0 的 host 出现在响应中;
690+
- 远端 P2P 候选只使用 `ST_EVENT_REPORT_L2`,且不会让 `prefix_match_blocks` 为 0 的 host 出现在响应中;
689691
- reporter unavailable 时,该 host 对应的 event-report location 不参与匹配。
690692

691693
成功完整 snapshot 后,`GetHostCacheState` 会立即忽略完全属于旧 generation 的 location。
692694
snapshot 失败、KVCM 重启恢复或 realtime-only reporter 仍使用 soft metadata,因此
693-
`local` 在这些模式下仍可能是 false positive。
695+
`prefix_match_blocks` 在这些模式下仍可能是 false positive。
694696

695697
## 12. 节点生命周期与查询
696698

docs/design/report_event_performance.md

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ instance group、data-storage backend 和 reporter node lock。当前实现做
7676
4. 首个有界 metadata range 读取完成后,第一次处理 event-report location 时用 `std::call_once` 为该请求
7777
抓取 reporter liveness 与 committed-version 快照。每个 backend 只持有一次 `nodes_mutex_` shared lock,
7878
后续 range 的 `(block, location)` 只读不可变快照;
79-
5. host/spec 投影和候选 host 前缀归约复用同一个有界 executor,输出仍按 host 字典序构造,普通 prefix、
80-
Mamba、Eagle pop 和 medium filter 的结果语义不变;
81-
6. 普通 prefix 只为首 key 建立排序后的候选 host,后续 key 直接写入按候选编号组织的 packed bitset,
82-
不再为每个 key 构造 `map<string, set<string>>`,也不保存普通 prefix 根本不需要的 spec name。
83-
Mamba 仍需 spec 完整性信息,但改用排序的小 vector,避免每个 key 的红黑树 node 分配;
79+
5. 无 P2P 的普通 prefix 保留并发 callback 快路径;需要跨 key 状态的普通 P2P/Mamba 使用 ordered visitor。
80+
metadata suffix 仍按不超过 worker 数的 chunk window 并行读取,但 callback 严格按 key 顺序消费,窗口
81+
释放后才读取下一窗口,因此不需要为并发乱序 callback 保留整请求投影;
82+
6. 普通 prefix 只为首 key 建立排序后的候选 host,后续 key 直接写入按候选编号组织的 packed bitset。
83+
P2P/Mamba 把 host 字符串请求内 intern 成稠密 ID,单 key 复用 generation-index vector 和多 word spec
84+
bitmask,不再按 key 分配 `map<string, set<string>>` 或 hash node。普通 P2P 只保留最终 local top-N 的
85+
peer 交集;Mamba local 每 host 只保留最后两个 state checkpoint,P2P 再用一次 full-prefix/coverage
86+
合并扫描和一次最终验证扫描。各 FullAttention group 可独立选择 peer,其 group spec 会按原 API 语义
87+
合并后再判断 Mamba state;state coverage 仍只选择一个 peer。Eagle POP 延迟一 key 提交 coverage,
88+
pending 状态只记录本 key 实际触达的 peer,不再每 key 清零/遍历全部 host。精确
89+
`p2p_1_fetch` 只顺序记录实际 fetch key,末尾排序去重;
8490
7. GetHostCacheState 专用可见性 checker 在校验 EventReport reporter 状态和 URI 时一并返回已经解析的
8591
medium/host。host 投影复用该结果,不再对同一 location id 做第二次 split。EventReport URI 的查询侧
8692
校验直接在不可变字符串上单次扫描,用 `string_view` 比较 generation;不再为每个 spec 拆分
@@ -103,6 +109,11 @@ instance group、data-storage backend 和 reporter node lock。当前实现做
103109
或再次扫描 metadata;因此这里明确把快照线性化点放在首 range 与后续并行 range 之间。由于 `available`
104110
是逐 reporter atomic,多个 reporter 与并发 liveness 变化之间不承诺一个全局事务时间点;保证的是
105111
request-stable 结果,避免同一 reporter 在一次长 projection 中前半段 up、后半段 down。
112+
可选的 Mamba P2P 为避免 `key × host × spec` 常驻表,会在同一个 request-stable reporter 快照下执行三次
113+
有序归约(local、full+coverage、final validation);纯 local backend 对应三次有界 metadata range scan。
114+
Redis/cached 等 batch backend 只发起一次原有 batch read,随后在请求内复用其紧凑结果执行后两次归约,
115+
不把远端请求放大三倍。local backend 的多次 range read 本身不是跨扫描事务快照,和原有分 chunk 并发读取
116+
一样,不承诺与并发 ReportEvent 形成全局原子视图。
106117

107118
这不是用“原子变量 + 双重检查”替换 node map。`available` 本身可以是 atomic,但 reporter 的存在性、
108119
lifecycle generation、strict flag 与 committed token 必须在同一个受保护快照中一致;只原子化一个布尔值
@@ -237,8 +248,10 @@ GetHostCacheState 的新增分段指标:
237248

238249
- `meta_searcher.indexer_get_time_us`:整个 MetaIndexer 读取;
239250
- `meta_indexer.get_io_time_us`:其内部 backend 调用墙钟时间;
240-
- `meta_searcher.host_projection_time_us`:location 可见性、URI/host/spec 投影;
241-
- `meta_searcher.host_prefix_reduce_time_us`:按 host 计算普通或 Mamba 前缀;
251+
- `meta_searcher.host_projection_time_us`:location 可见性、URI/host/spec 投影;ordered streaming 路径还包含
252+
与单 key 投影融合执行的在线前缀归约 CPU;
253+
- `meta_searcher.host_prefix_reduce_time_us`:投影结束后的独立 host 前缀归约/选择;完全融合的 Mamba 路径可为
254+
0,需与上一项一起判断;
242255
- `manager.prefix_match_time_us`:上述阶段及少量管理层开销的外层总时间。
243256

244257
UT 覆盖单线程/4-worker 结果对照、缺失 key 和重复 key、普通/Mamba 大于阈值、medium filter、Eagle pop、
@@ -597,7 +610,8 @@ local LRU 上逐 key 加锁的固定成本。实现要点如下:
597610
后续范围才提交共享有界 executor。普通 prefix 或 Mamba 的所有候选已经终止时,通过原子 stop index
598611
阻止尚未领取的后缀工作;
599612
3. metadata 读取和 host/spec 投影在 visitor 内融合,不再先保存百万 key 的 location 集合、随后做第二次
600-
全量遍历。普通查询只保留每 host 的 prefix stop;Mamba 只保留后续 Eagle pop 所需的状态位矩阵;
613+
全量遍历。普通查询只保留每 host 的 prefix stop;当前 Mamba local 进一步只保留每 host 最后两个
614+
state checkpoint,已不再保留早期版本的 `key × host` 状态位矩阵;
601615
4. 只有 prefix 内的 `EC_NOENT` 是正常 miss。答案终止位置之前的硬错误仍返回;终止位置之后已经开始的
602616
speculative 读取即使失败,也不能推翻已经确定的短 prefix;
603617
5. EventReport location id 与 URI 使用只读 `string_view` 解析,并通过透明比较直接查询 snapshot,避免
@@ -907,3 +921,57 @@ cleanup 的 generation lease 或 conditional-delete 语义。
907921

908922
本节没有启动 Redis,也没有把 local 结果外推到 cached/Redis backend。后续 AI 至少应保留:36 项 HTTP
909923
功能套件、固定 blocks/s 的写读混合 A/B、TSAN 关闭期复现,以及 sanitizer 对第三方 ODR/stub 的精确处理。
924+
925+
### 5.16 2026-08-11 P2P/Mamba 请求内存上界复核
926+
927+
对百万 key 路径继续审计后,早期“紧凑投影”虽然已经去掉 `map<string, set<string>>`,P2P 仍会为整请求
928+
保留 CSR host/spec 表,无 P2P 的 Mamba 也仍有 `key × host` byte matrix;这两者的常数下降不改变最坏
929+
情况下随 key 数和 host 数相乘的内存增长。本轮改成 ordered bounded reduction:
930+
931+
1. `MetaIndexer::PrefixVisitOrder::ORDERED` 首个 4096-key chunk 同步读取;suffix 每次最多并行读取
932+
`worker_count` 个 chunk,callback 按绝对 key 顺序消费后释放窗口。visitor 跨窗口返回 stop 时,下一
933+
chunk 只能看到 stop 之前的有效数量,不能误消费 speculative suffix;
934+
2. 普通 P2P 在一次扫描中维护 local stop,并只保留最终 local top-N 的 peer intersection。较短 prefix
935+
的计划会被后出现的较长 prefix 单向淘汰,已淘汰计划不可能重新进入最终 top-N;
936+
3. Mamba local 每 host 只保留 full stop 和最后两个 state checkpoint。选出 top-N 后,第二次扫描同时
937+
完成各 FullAttention group 的 prefix peer 交集与 state coverage 计票;Eagle POP 用一个 pending-key
938+
计数延迟提交被 pop 的尾 block,pending 只遍历本 key 触达的 peer。第三次扫描用最终 peer 计划验证
939+
合并结果;各 full-group peer 的 group spec 先合并,再叠加单个 coverage peer,保持原 API 语义;
940+
4. host 字符串只 intern 一次并转换为稠密 ID;单 key 的 host/spec 视图复用 generation-index vector 和
941+
multiword bitmask。热循环不再按 key 创建 host hash node。精确 `p2p_1_fetch` 使用连续 vector 记录
942+
实际 fetch,末尾 sort/unique;这部分下界随需要精确去重的 fetch key 数增长,但不再乘以全部 host 和
943+
spec,也没有 `unordered_set` 的逐元素 node 开销。
944+
945+
`K` 为 query key 数、`H` 为请求中出现的 host 数、`P` 为启用 P2P 的 local top host 数、`G`
946+
FullAttention group 数、`W` 为 spec mask word 数、`F` 为实际 fetch 位置数,则普通 P2P 的常驻 reducer
947+
状态为 `O(H + P×H + F)`;Mamba 为 `O(H×W + P×G×H + P×H + P×W + F)`,metadata window 另为
948+
`O(worker_count × 4096)` key 的 compact location 引用。已不存在 `O(K×H)``O(K×H×W)` 请求表。
949+
非 local backend 仍使用原有整 batch fallback,并在首次 visitor 中保存一份紧凑 offsets/locations 供后续
950+
归约回放,因此一次 Mamba 请求只触发一次 backend batch read;但该 batch 本身仍随 `K` 增长,本节的
951+
固定 metadata-window 内存上界不能外推到 Redis/cached backend。
952+
953+
三次有序扫描不是无条件的重复工作:第一次结束后才知道按 local 排序的 top-N host,第二次结束后才知道
954+
每个 full group 的 prefix peer 和唯一 coverage peer,第三次才能对这些最终 peer 精确计算 state checkpoint
955+
及去重后的 fetch key 数。把第一、二次合并需要为尚未入选的 host 保留 peer coverage;把第二、三次合并则
956+
需要为所有候选 peer 保留逐 key/fetch 集合,都会重新引入随 `K×H` 增长的状态。若未来允许放宽精确 fetch
957+
计数或改变 API 语义,可以重新评估两扫描方案;在当前契约下保留三扫描是显式的内存/CPU 取舍。
958+
959+
为避免只靠定向 example 证明复杂状态机,本轮另写了不复用生产 reducer 的朴素 `map/set` 参考模型。
960+
固定随机种子生成 256 组 host/spec 拓扑,每组组合 Eagle on/off、P2P host count 0/1/2,并逐 host 对比普通
961+
和 Mamba 的 `local/p2p_1_fetch/p2p_1_total_match`。模型显式覆盖跨 group 边界:多个 FullAttention group
962+
分别由不同 peer 补齐时,其 group spec 会先 union,再参与 Mamba state 判断;定向回归锁定这一兼容语义。
963+
964+
同机 Release/O2 手工 benchmark 使用 1M 个纯 local key:目标 L1P5 host 本地命中 1024 block,由一个
965+
全量 L2 host 延伸到 1M,结果严格校验为 `fetch=998976,total=1000000`。连续独立运行三次(每次先 warmup,
966+
再取三次样本的 p50),下表报告三轮 p50 与进程 peak RSS 的中位数:
967+
968+
| phase/case | p50 | 进程 peak RSS |
969+
| --- | ---: | ---: |
970+
| local-only all-hit | 111.862ms | 571156KB |
971+
| 普通 P2P prefix | 190.527ms | 587300KB |
972+
| Mamba P2P prefix | 632.583ms | 587304KB |
973+
974+
普通 P2P 相对已经装载 1M metadata 的进程增加约 15.8MiB peak RSS;随后三扫描 Mamba 的中位数只继续
975+
抬高 4KiB high-water mark。Mamba 数字体现了“用有界内存换确定的重复扫描”,不是延迟 SLA。回归还覆盖 ordered
976+
window 跨 chunk stop、在线 top-N 淘汰、peer intersection 从非空变空时保留最后有效 peer、重复 block
977+
key 去重、多 word spec mask、Eagle POP、coverage tie-break 和 metadata 硬错误传播。

docs/prometheus-en_US.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ every `kvcm.metrics.report_interval_ms`, default 20s).
134134
| `manager.prefix_match_time_us` | gauge | Outer total latency for GetHostCacheState-style prefix matching (us) |
135135
| `meta_searcher.indexer_get_time_us` | gauge | Wall time spent reading metadata through MetaIndexer (us) |
136136
| `meta_indexer.get_io_time_us` | gauge | Metadata-backend wall time; local mode includes LRU/locks/copies and does not imply Redis I/O |
137-
| `meta_searcher.host_projection_time_us` | gauge | GetHostCacheState visibility checks and host/spec projection time (us) |
138-
| `meta_searcher.host_prefix_reduce_time_us` | gauge | GetHostCacheState normal/Mamba host-prefix reduction time (us) |
137+
| `meta_searcher.host_projection_time_us` | gauge | CPU time for GetHostCacheState visibility/host/spec projection plus fused online reduction on streaming paths (us) |
138+
| `meta_searcher.host_prefix_reduce_time_us` | gauge | Independent post-projection host-prefix reduction/selection; may be 0 on fully fused paths (us) |
139139
| `meta_indexer.search_cache_hit_ratio` | gauge | Search cache hit ratio |
140140
| `data_storage.create_keys_counter` | counter | Total created keys |
141141

docs/prometheus-zh_CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ kvcm_data_storage_storage_usage_ratio{type="nfs",unique_name="store_02"} 0.3
131131
| `manager.prefix_match_time_us` | gauge | GetHostCacheState 等前缀匹配的外层总延迟(微秒) |
132132
| `meta_searcher.indexer_get_time_us` | gauge | MetaSearcher 调用 MetaIndexer 读取 metadata 的墙钟时间(微秒) |
133133
| `meta_indexer.get_io_time_us` | gauge | metadata backend 调用墙钟时间;local 模式也包含 LRU/锁/复制,并不表示 Redis I/O |
134-
| `meta_searcher.host_projection_time_us` | gauge | GetHostCacheState location 可见性检查及 host/spec 投影时间(微秒) |
135-
| `meta_searcher.host_prefix_reduce_time_us` | gauge | GetHostCacheState 普通/Mamba host 前缀归约时间(微秒) |
134+
| `meta_searcher.host_projection_time_us` | gauge | GetHostCacheState location 可见性、host/spec 投影及流式路径融合在线归约的 CPU 时间(微秒) |
135+
| `meta_searcher.host_prefix_reduce_time_us` | gauge | GetHostCacheState 投影后的独立 host 前缀归约/选择时间;完全融合路径可为 0(微秒) |
136136
| `meta_indexer.search_cache_hit_ratio` | gauge | 搜索缓存命中率 |
137137
| `data_storage.create_keys_counter` | counter | 已创建 key 总数 |
138138

integration_test/meta_service/http_interface_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def test_event_report_requested_spec_filters_before_peer_selection(self):
243243
"query_type": "QT_BATCH_GET",
244244
"block_keys": block_keys,
245245
"block_mask": {"offset": 0},
246-
"location_spec_names": ["linear_1"] * len(block_keys),
246+
"per_key_location_spec_names": ["linear_1"] * len(block_keys),
247247
"backend_selectors": [{
248248
"backend_type": "ST_EVENT_REPORT_L2",
249249
"strategy": strategy,
@@ -264,7 +264,7 @@ def test_event_report_requested_spec_filters_before_peer_selection(self):
264264
"query_type": "QT_BATCH_GET",
265265
"block_keys": block_keys,
266266
"block_mask": {"offset": 0},
267-
"location_spec_names": ["unknown_spec"] * len(block_keys),
267+
"per_key_location_spec_names": ["unknown_spec"] * len(block_keys),
268268
"backend_selectors": [{
269269
"backend_type": "ST_EVENT_REPORT_L2",
270270
"strategy": "LSS_V6D_PREFIX",

integration_test/meta_service/test_report_event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ def test_18_get_host_cache_state_dual_type_prefix_match(self):
10141014
"10.0.0.3:8080": 1,
10151015
}
10161016
actual = {
1017-
h["host_ip_port"]: int(h["local"])
1017+
h["host_ip_port"]: int(h["prefix_match_blocks"])
10181018
for h in resp.get("hosts", [])
10191019
}
10201020
self.assertNotIn("p2p_1_hit_count", resp)

0 commit comments

Comments
 (0)