记一次 FD 泄漏问题黑盒排查
记录一次纯黑盒的 fd 泄漏问题排查过程。该问题发生在云上 GCP 环境,无法在本地或可控测试环境稳定复现,只能通过系统观测、tcpdump、对照实验等方式逐步收敛问题范围,最终定位到高置信触发链路并通过参数绕过止血。
1. 结论摘要
TiFlash 在 Cloud GCP 环境中出现 fd 持续增长,最终触发 Too many open files 并导致进程 panic。现场确认泄漏 fd 主要由 socket、eventfd、eventpoll 构成,三类对象数量接近。
当前最高置信的触发 / 放大链路为:TiDB planner 在准备部分 MPP 查询时,会获取 TiFlash 节点硬件信息,并触发 TiDB -> TiFlash ServerInfo gRPC 调用。该调用会在 TiDB 侧创建短生命周期 gRPC 连接;现场观测到该链路存在频繁连接创建与关闭,且 TiFlash 进程内 fd 呈 socket、eventpoll、eventfd 成组增长。
关闭 MPP 后,fd 不再增长;设置参数绕过 TiFlash 节点硬件信息获取链路后,泄漏也不再出现。因此,目前可以高置信判断:fd 泄漏与 MPP 查询准备阶段的 TiDB -> TiFlash ServerInfo gRPC 短连接链路高度相关。
2. 影响范围
- Platform:GCP(其他平台例如 AWS 未发现该问题)
- TiDB / TiFlash version:
v7.5.3 - 影响:
- TiFlash fd 数持续增长
- 查询开始失败
- 最终触发
Too many open files - TiFlash panic / restart
3. 排查方法说明
本次问题排查主要基于黑盒 debug。排查过程中无法直接依赖 TiFlash / gRPC 内部代码逻辑确认资源生命周期,因此主要通过以下外部观测和对照实验逐步收敛问题范围:
- TiFlash 进程 fd 数量变化;
- fd 类型分布;
- socket、eventfd、eventpoll 的增长关系;
- tcpdump 观察到的连接创建与关闭行为;
- 设置参数绕开相关链路前后的 fd 变化对比;
- 相关 gRPC / TLS 连接异常日志;
- 已知 workaround 的效果验证。
因此,本文中的根因判断应理解为基于黑盒证据链的高置信推断,而不是基于代码路径的完全证明。
3.1 证据强度分级
| 判断 | 证据强度 | 依据 |
|---|---|---|
| TiFlash 存在 fd 持续增长 | 强 | /proc/1/fd / lsof 统计持续增长 |
| 泄漏 fd 主要是 socket、eventfd、eventpoll | 强 | fd 类型统计显示三类对象成组增长 |
| 泄漏与 MPP 查询链路相关 | 强 | 关闭 MPP 后 fd 增长停止 |
泄漏与 ServerInfo 节点硬件信息获取链路相关 |
较强 | 绕过该链路后 fd 增长停止,且代码路径确认该链路会发起 gRPC 调用 |
[RST, ACK] 与 fd 增长时间相关 |
中 | 时间窗口相关,但构造相同 TCP 模式未稳定复现 |
| TiFlash / gRPC 内部未释放 fd | 中 | 资源形态吻合,但缺少 syscall / 代码级证据 |
| 问题与 GCP 环境存在相关性 | 中 | AWS 未观察到,但缺少同 workload、同版本、同配置的系统性对照 |
3.2 关键实验摘要
按排查顺序,关键对照实验如下:
- 确认 TiFlash fd 持续增长,且增长对象主要是 socket、eventfd、eventpoll;
- 关闭 MPP 后,TiFlash fd 增长停止;
- 沿 MPP planner 路径定位到 TiDB -> TiFlash
ServerInfoRPC; - 设置参数让 planner 不再动态拉取 TiFlash logical cores 后,fd 增长停止;
- tcpdump 观察到部分连接关闭路径存在
[RST, ACK],但单独构造类似 TCP 关闭模式未稳定复现泄漏。
4. 排查过程
4.1 确认故障现象
首先从 TiFlash 日志中确认故障现象。TiFlash 节点在运行过程中开始持续出现 Too many open files 错误,随后查询链路受影响,最终进程因为系统调用失败触发 panic。
该现象说明问题不是单次查询报错,而是 TiFlash 进程内打开的 fd 数量持续增长,最终超过系统限制。
4.2 检查系统 fd 限制
确认容器 / 进程的 ulimit 配置。当前系统默认 fd 上限为 1048576,属于较高配置。
因此,本次故障不是因为 fd 上限设置过低,而是 TiFlash 进程存在 fd 持续增长甚至泄漏的问题。
4.3 统计 TiFlash 进程 fd 类型
通过检查 TiFlash 进程 /proc/1/fd,发现 fd 数量持续增长,且主要集中在以下三类对象:
socketanon_inode:[eventfd]anon_inode:[eventpoll]
进一步统计发现三类 fd 数量接近,说明泄漏对象并非随机分布,而是呈现出一组一组增长的特征。
该现象与 gRPC 同步服务处理连接时创建的资源形态一致:一次连接通常会关联 socket、eventfd 和 eventpoll 等对象。
被泄漏的 socket、eventpoll、eventfd 呈现出对应关系:
1 | TiFlashMa 1 root *617u sock 0,8 0t0 229895840 protocol: TCPv6 |
继续查看 TiFlash 进程的 fd 明细,发现泄漏的 socket、eventpoll、eventfd 基本呈现连续、成组出现的关系。
这说明每次泄漏很可能对应一次连接生命周期中创建的一组资源,而不是 TiFlash 内部普通文件、数据文件或单独网络连接的偶发泄漏。
因此,排查方向从“泛化 fd 泄漏”收敛到“网络连接相关 fd 泄漏”。
4.4 验证问题是否与 MPP 查询相关
为了判断泄漏是否与 TiFlash 查询路径相关,调整 TiDB 参数关闭 MPP:
1 | set global tidb_allow_fallback_to_tikv = "tiflash"; |
调整后,TiFlash socket 相关 fd 数量保持稳定,不再持续增长。
在该 workload 和参数组合下,关闭 MPP 后增长停止,因此主触发链路优先指向 MPP 准备/执行相关路径。
4.5 排查 TiFlash 连接已缩容 TiKV 的影响
排查过程中发现,TiFlash 存在持续尝试连接已缩容 TiKV 地址的日志。该问题会导致大量 gRPC reconnect 日志,并可能污染排查视线。
但该链路主要表现为连接失败和重复重试,并不能解释 TiFlash 进程内 socket、eventfd、eventpoll 成组持续泄漏的现象。
因此,该问题被归类为独立的连接清理 / 日志治理问题,不作为本次 fd 泄漏的主因。
4.6 排查 TiFlash 访问 TiDB status 端口的影响
随后排查 TiFlash 与 TiDB 10080 status 端口之间的连接。发现开启 TLS 后,TiFlash 访问 TiDB status 端口下的 gRPC 服务存在认证 / ALPN 相关错误。
该问题说明 TLS + gRPC 的连接兼容性确实存在异常,但该链路主要影响 TiFlash 上报或访问 TiDB status 服务,并不能直接解释关闭 MPP 或绕过 ServerInfo 节点硬件信息获取链路后 fd 泄漏停止的现象。
因此,该问题被作为衍生问题单独跟进,不作为本次 fd 泄漏的主链路。
4.7 定位 MPP 准备阶段的 ServerInfo gRPC 调用
继续沿 MPP 查询链路排查,发现 TiDB planner 在准备 MPP 查询时,会向 TiFlash 发起 ServerInfo RPC,用于获取 TiFlash 节点 CPU 逻辑核数等硬件信息。
该调用有几个关键特征:
- 发生在 MPP 查询准备阶段;
- TiDB 会为该调用新建 gRPC 连接;
- RPC 调用结束后会关闭连接;
- 高查询频率下,该链路会频繁创建和关闭短生命周期 gRPC 连接。
该行为与现场观察到的短连接、socket fd 持续增长、关闭 MPP 后泄漏停止等现象一致。
因此,排查方向进一步收敛到 TiDB ↔ TiFlash ServerInfo gRPC 短连接链路。
从 TiDB 代码路径看,该链路对应:
pkg/planner/core/optimizer.go中getTiFlashServerMinLogicalCores调用infoschema.FetchClusterServerInfoWithoutPrivilegeCheck(..., ServerInfoType_HardwareInfo, ...);pkg/infoschema/tables.go中FetchClusterServerInfoWithoutPrivilegeCheck对每个 TiFlash server 调用getServerInfoByGRPC;getServerInfoByGRPC内部每次执行grpc.Dial(address, opt),RPC 完成后通过defer conn.Close()关闭 TiDB 侧连接。
因此,文档中所说的 ServerInfo 短连接链路不是纯现场猜测,而是可以从 TiDB planner 到 infoschema diagnostic gRPC 调用路径对应到代码。
4.8 分析异常连接关闭路径
tcpdump 中可以观察到部分连接关闭过程中出现 [RST, ACK]。在相近时间窗口内,TiFlash 进程内 socket、eventfd、eventpoll 数量出现增长。
这个现象提示 TiFlash 服务端 gRPC 连接处理路径可能在某些异常关闭场景下未能完整回收连接相关 fd,但仍需 syscall 或代码级证据确认。
1 | sh-5.1# lsof -p1 -nP | grep sock | wc -l |
4.9 构造相同 TCP 交互模式进行验证
为了验证 [RST, ACK] 是否必然导致 fd 泄漏,构造了相同 TCP 交互模式的验证场景。
验证结果是:未稳定复现 fd 泄漏。
这说明 [RST, ACK] 可能是触发条件之一,但不是单独充分条件。
4.10 验证 workaround
最后通过设置 TiDB 参数 tidb_max_tiflash_threads,避免 planner 在 MPP 查询准备阶段动态获取 TiFlash 节点硬件信息,从而绕开频繁 ServerInfo gRPC 短连接。
设置后,TiDB 不再需要通过 TiFlash ServerInfo RPC 获取 cpu-logical-cores 来推导 stream count。现场观察到 TiFlash fd 数量停止持续增长。
该结果进一步确认:本次 fd 泄漏与 TiDB -> TiFlash ServerInfo gRPC 短连接链路高度相关。
该 workaround 的副作用是:MPP 查询的并行度不再根据 TiFlash 节点 logical cores 自动推导,而是使用固定值;可能影响部分 MPP 查询的计划形态或性能。因此该 workaround 适合作为止血手段,不应替代 TiFlash / gRPC 侧 root cause 修复。
5. 后续验证项
如果允许修改基础镜像或 TiFlash 代码,建议进一步验证以下方向:
- 修改 gRPC / TiFlash 代码,在连接生命周期中增加日志,确认连接创建、RPC 调用、连接关闭、fd 释放的对应关系;
- 在 TiFlash 进程上通过 eBPF 或 strace 记录
socket、accept4、eventfd2、epoll_create1、epoll_ctl、close,确认是否存在创建后未关闭,或close返回异常。 - 对比同 workload 在 GCP / AWS、不同 kernel / container image 下的 fd 增长曲线。
- 明确 tcpdump 中两端 IP、端口角色,例如哪个是 TiDB,哪个是 TiFlash,
3930是否为 TiFlash service 端口。 - 将 fd 数量、RST 数量、查询 QPS、TiFlash error 日志按时间线对齐,确认 fd 增长与连接异常关闭之间的时间相关性。
- 在 TiFlash / gRPC 侧构造最小复现,验证异常关闭路径是否能稳定产生 socket、eventfd、eventpoll 泄漏。