如何基于“/_dbs_info”批量获取 50 个库状态并限制 RTT < 100 ms?
解读
国内生产环境常见三类痛点:
- 集群跨可用区,RTT 天然 30-50 ms,留给单次 HTTP 的预算只剩 50 ms;
- 库数量大,/_dbs_info 默认会返回全量信息,body 体积膨胀导致解析耗时;
- 面试官想确认你对 CouchDB 内部 epmd/beam 调度、HTTP 连接池、Erlang 垃圾回收 是否有体感,而不是只会调 curl。
因此题目表面考“接口”,实则考“全链路 100 ms 稳定性”。
知识点
- /_dbs_info 语义:POST 体给出 {"keys":["db1",…,"db50"]},返回数组,单个库对象含 sizes、doc_count、doc_del_count、cluster、update_seq;
- Erlang 端耗时构成:
- 内部 gen_server:call 到 mem3 做 ring 定位
- 读取 .couch 文件头 4 kB 做签名验证
- 构造 JSON 时调用 jiffy:encode,大对象触发 fullsweep GC;
- HTTP 层:
- CouchDB 默认 keep-alive timeout=5 s,max=8192,但国内云厂商 LB 会静默断开 60 s 无流量连接;
- 单次 TCP+TLS 握手≈25 ms,复用连接才能把 RTT 压到 100 ms 以内;
- 客户端侧:
- 使用 HTTP/2 多路复用 或 连接池(maxIdle=8, maxPerRoute=8);
- 设置 TCP_NODELAY=true 禁用 Nagle;
- 超时分层:connectionTimeout=50 ms, socketTimeout=80 ms;
- 裁剪返回字段:在 3.2+ 可用 query param ?fields=sizes,doc_count,body 缩小 70 %,解析耗时线性下降;
- 并发模型:CouchDB 单节点仍受 async_thread_pool=16 限制,keys 超过 64 会触发内部拆分,但 50 个库刚好在阈值内,可一次性发;
- SLA 验证:用 tcprtt+httpstat 双采样,P99 超过 100 ms 直接触发熔断回退到本地缓存。
答案
“我会用三步保证 P99 RTT<100 ms:
- 连接预热:服务启动时批量建 8 条长连接,HTTP/1.1 keep-alive,TLS 会话复用,把握手成本均摊到 0;
- 请求裁剪:POST /_dbs_info?fields=sizes,doc_count,body 只传 50 个库名,返回体积控制在 12 kB 以内,jiffy 解析 < 5 ms;
- 超时阶梯:
- 客户端设 50 ms 连接超时、80 ms 读超时;
- 若触发超时立即降级到本地缓存的秒级快照,保证用户侧无错误。
上线前用 tcprtt 抓包+P99 监控,持续压测 30 min,确认 99 分位 92 ms,满足 SLA。”
拓展思考
- 如果库量从 50 涨到 500,/_dbs_info 会拆包,Erlang 端 gen_server 排队,P99 可能飙升到 300 ms。此时可改用 /_dbs_info 流式接口(3.3 实验特性),或把 500 拆成 10 组、在客户端做 goroutine 并发+滑动窗口,总耗时仍压到 100 ms;
- 国内金融合规要求**“本城双活、异地冷备”,跨城 RTT 40 ms。可把请求打到同城主节点**(_node/_local 中 region 标签),避开跨城;
- 进一步做到极致,可写 Erlang 插件把 50 个库的 meta 一次性读进 ETS 缓存,暴露新的 /_dbs_info_cached 接口,RTT 直接降到 5 ms,但需处理缓存失效与集群一致性问题。