Skip to content

Bound Gateway cache lifetime and capacity - #516

Open
GOLDKUN wants to merge 2 commits into
chaitin:mainfrom
GOLDKUN:perf/bound-gateway-cache-lifetime
Open

Bound Gateway cache lifetime and capacity#516
GOLDKUN wants to merge 2 commits into
chaitin:mainfrom
GOLDKUN:perf/bound-gateway-cache-lifetime

Conversation

@GOLDKUN

@GOLDKUN GOLDKUN commented Sep 2, 2026

Copy link
Copy Markdown

问题

Gateway 的 MCP Tool、Connect Handler 缓存按版本 key 累积,缺少容量和生命周期限制;实例失效时也没有清除 schema/handler 缓存。

影响

服务反复导入、descriptor 变化或暴露配置变化时,旧 handler、schema 和 descriptor 关联对象会长期驻留,造成可持续的内存增长。

修复内容

  • MCP Tool 和 Connect Handler 缓存增加 1024 条容量上限。
  • 增加 10 分钟生命周期,到期后自动淘汰。
  • 实例失效时同步清理 MCP/Connect schema 缓存。
  • Gateway 关闭时清理全部缓存。
  • 增加过期和实例失效测试。

验证

  • go test ./internal/protocol -run 'TestGatewayCacheEntriesExpireAndInvalidateTogether|TestGatewayInstanceInvalidationClearsSchemaCaches' 通过。

@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Bound Gateway cache lifetime and capacity

Commit: c17eace

本变更在 Gateway 中为 mcpToolsCacheconnectCache 引入了缓存生命周期管理:新增 mcpCacheAt/connectCacheAt 时间戳映射、gatewayCacheMaxEntries=1024 容量上限与 gatewayCacheTTL=10min 过期时间,并新增 evictExpiredCachesLocked(每次访问时全量扫描过期项)、pruneGatewayCachesLocked+deleteOldestCache(写入时按最旧淘汰到上限)。同时 InvalidateInstanceClose 现在会一并清空这些 schema 缓存,修复了此前实例失效后工具列表可能长期陈旧的缺陷,并新增了两个针对过期与失效清理的测试。

整体方向正确(此前缓存无界且实例失效不清缓存)。但实现存在几个值得关注的点:(1) 每次 mcpTools/connectHandler 访问都在全局 g.mu 下对整张缓存做 O(n) 全扫描,热路径由 O(1) 退化为 O(n);(2) pruneGatewayCachesLocked 的无界循环依赖缓存与时间戳两个 map 严格同步,一旦失配会在持锁下死循环卡死整个网关,且该路径无测试覆盖;(3) InvalidateInstance 无条件清空全部缓存,多实例场景会引发跨实例缓存雪崩。测试覆盖了过期与失效清理,但未覆盖 prune 上限与命中路径。

Comment thread internal/protocol/gateway.go Outdated
}
cacheKey := mcpToolsCacheKey(capsetID, items)
g.mu.Lock()
g.evictExpiredCachesLocked(time.Now())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

每次缓存访问都在全局锁下全量扫描缓存做过期淘汰,热路径由 O(1) 退化为 O(n)

mcpTools(887 行)与 connectHandler(1268 行)在每次调用、持有全局 g.mu 时都执行 evictExpiredCachesLocked,该函数会完整遍历 mcpCacheAt 与 connectCacheAt 两张 map(每张最多 gatewayCacheMaxEntries=1024 项)。即使缓存全部命中、没有任何过期项,每次请求也要做最多约 2048 次时间比较并串行化在全局互斥锁内。改动前热路径只是 O(1) 的 map 读取;改动后所有网关请求在全局锁下承担 O(cache) 的清扫成本,高 QPS 下会放大锁竞争与请求延迟,且该成本与缓存命中与否无关。

Problem code:

Changed code at internal/protocol/gateway.go:887

Recommendation:
将过期淘汰从每次访问的全量 O(n) 扫描改为摊销/懒淘汰:读取时只检查目标 cacheKey 自身的过期时间,命中即返回;仅在写入新条目或周期性(每 N 次访问/后台定时器)时才做全量清扫。若保留全量扫描,至少将其移出读路径。

Comment thread internal/protocol/gateway.go
@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Bound Gateway cache lifetime and capacity

Commit: edece71

本次变更优化了 Gateway 的 schema/handler 缓存淘汰策略,并修复了此前 prune 路径可能死循环的缺陷。

改动要点:

  1. mcpTools 与 connectHandler 的缓存读路径:将原先每次持全局锁全量扫描的 evictExpiredCachesLocked(O(n))替换为只针对当前 cacheKey 的 evictExpiredCacheEntryLocked(O(1) 定向 TTL 淘汰)。由于读路径在返回缓存值之前总是先对本 key 做过期检查,过期条目不会被命中返回,正确性保持不变,命中热路径成本从 O(n) 降为 O(1)。
  2. 全量 evictExpiredCachesLocked 被移到写路径(miss 后重建入库时),此时 O(n) 成本被昂贵的重建所吸收,属合理取舍。
  3. deleteOldestCache 改为返回 bool:当对应时间戳 map 为空/缺失时,从 cache 中兜底删除任意一个条目并返回 true,仅在 cache 本身为空时返回 false;pruneGatewayCachesLocked 据此增加 break,确保即使两张 map 失配也能终止循环,修复了此前在持全局锁下无界自旋卡死网关的隐患。
  4. 新增两个测试覆盖 prune 路径(正常按最旧淘汰、时间戳元数据缺失时仍能推进),方向正确。

总体评估:本次改动针对性地解决了两个历史高风险问题(热路径 O(n) 扫描、prune 死循环)。经核查,gateway.go 内 mcpToolsCache/connectCache 的全部读写点均在 mcpTools/connectHandler 内且于同一把 g.mu 下成对维护,读路径定向淘汰保证了任何 key 都不会被命中返回过期值,未发现新的高置信正确性/并发/安全/回归缺陷。历史问题 InvalidateInstance 清空全量缓存导致的跨实例缓存雪崩不在本次 diff 范围内,维持原样。无新增可操作发现。

@GOLDKUN

GOLDKUN commented Sep 2, 2026

Copy link
Copy Markdown
Author

Follow-up fixes after performance/reliability review:

  • Cache reads now check only the requested key instead of scanning every timestamp under the global lock.
  • Full expiry sweeping remains on cache writes.
  • Cache pruning now always makes progress, including when timestamp metadata is missing, preventing a lock-held infinite loop.
  • Added capacity, oldest-entry, and metadata-mismatch tests.

Verification: targeted Gateway cache tests pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant