解释 Erlang span 的“kind=internal”与“kind=server”区别。
解读
在 CouchDB 的分布式链路追踪实现里,每个 span 都必须声明自己的“角色”,kind=server 与 kind=internal 是最常见的两种。国内面试官问这道题,并不是想听你背 OpenTelemetry 规范,而是想看你是否能把“谁触发、谁负责、谁耗时”这三件事在 Erlang 进程模型里说清楚;同时考察你对 CouchDB 内部 chttpd、mochiweb、fabric、mem3 等组件调用链路的熟悉程度。答不出“server 代表跨网络边界、internal 代表纯进程内函数级调用”这一层,基本会被判定为“只写过业务代码,没调过 CouchDB 源码”。
知识点
- 语义规范:OpenTelemetry 把 span 分为 internal、server、client、producer、consumer 五类;CouchDB 3.x 内置的 otel 采集器只用到 internal 与 server。
- server span:
- 由外部 HTTP 请求触发,入口函数通常是 chttpd:handle_request/2;
- 生命周期与一次 mochiweb 连接对齐,耗时包含 JSON 解析、权限验证、视图索引合并等;
- 在追踪树里永远位于根或子树根位置,trace-id 由外部携带或新建。
- internal span:
- 由Erlang 进程内函数调用产生,例如 fabric:update_docs/3 内部做一致性写时,为“写副本”生成子 span;
- 不跨网络,也不对应任何 socket,仅用于拆分耗时、定位热点函数;
- parent 必须是同一个 VM 里的另一个 span,不能独立存在。
- 实现细节:CouchDB 在 otel_span:start_span/3 时把 kind 写进 #span{} 记录;server span 会额外把 peer.ip 与 peer.port 标签带上,internal span 则不带任何网络标签。
- 面试陷阱:
- 把 internal 说成“就是本地函数调用”不够,要补充“不创建新的 trace-id,只共享上下文”;
- 把 server 说成“HTTP 请求”也不够,要指出“由 mochiweb 触发、会写 http.method / http.status_code 属性”。
答案
在 CouchDB 的 Erlang 实现里,kind=server 的 span 表示一次跨网络边界的入口请求,通常由 mochiweb 收到 HTTP 报文后创建,它负责记录整个外部请求的耗时与状态码;而 kind=internal 的 span 仅标记同一 VM 内的函数级子调用,用于细化热点路径,不对应任何 socket,也不会产生新的 trace-id。简单说:server 对外,internal 对内;前者是“分布式入口”,后者是“进程内拆分”。
拓展思考
- 如果你在 K8s sidecar 里把 CouchDB 的 server span 采样率调到 1%,但 internal span 保持 100%,会发现高并发写时 internal span 数量远大于 server,借此可快速定位到 fabric 层的“写副本”瓶颈。
- 国内不少公司把 CouchDB 当作边缘节点数据库,再同步到中心 Elasticsearch;此时若把 CouchDB 的 server span 与下游 ES 的 client span 做 trace-id 透传,就能在 Jaeger 里拼出“边缘写 -> 同步 -> 中心索引”的完整火焰图,排障效率提升 3 倍以上。