作者:徐怀宇
导读
高质量的运行时监控指标对于问题定位和性能调优至关重要。在 TiDB 的运行时环境中,Go Runtime 层面的内存管理、垃圾回收(GC)行为以及锁竞争等问题会给系统的稳定性和性能带来一定影响,且这类问题通常难以直接复现和分析。
本文为 TiDB 可观测性解读系列文章的第三篇,将围绕 TiDB-Runtime Grafana 监控页面展开介绍,包括该监控页面适用于哪些类型的问题排查场景、各个常用面板的功能、如何借助这些面板进行问题分析与定位等。本系列文章将深入解读 TiDB 的关键参数,帮助大家更好地观测系统的状态,实现性能的优化提升,希望对 TiDBer 们有帮助。
注:本文基于 TiDB v8.5.0 版本 TiDB-Runtime Grafana 监控页面进行说明。
一、TiDB-Runtime Grafana 监控页面适用排查场景
内存使用异常
当 TiDB 实例出现 OOM(Out Of Memory)或内存使用率异常升高时,可以通过 Memory Usage 面板中的内存使用趋势、各类对象分配情况等,辅助判断是内存泄漏、小对象频繁分配,还是 GC 不及时等问题。
CPU 使用异常
对于 CPU 使用异常升高或波动剧烈的问题,可以借助 estimated portion of CPU time 分析面板,区分用户代码和 Runtime 层的消耗比例,识别是否是 GC、Scavenge 等系统层消耗占比过高,从而进一步定位 CPU 开销的具体来源。
GC 导致的延迟抖动
GC 行为不合理可能会引起明显的延迟抖动,特别是在短时间内频繁触发 stop-the-world(STW) 时。通过 golang gc 面板、 GC STW Latency 面板和 GOGC & GOMEMLIMIT 面板,可以全面了解 GC 的频率、耗时以及相关参数配置是否合理。
锁竞争
当 TiDB 出现高 CPU 占用或 Goroutine 堵塞时,可能与锁竞争或调度器资源紧张有关。通过 Goroutine Count 和 Sync Mutex Wait 等面板,可以帮助发现是否存在大量 Goroutine 堵塞、锁使用不当等问题,为进一步分析提供线索。
二、常用面板功能详述
Memory Usage
该面板用于展示 tidb-server
进程的整体内存使用情况。其中:
- RSS 曲线:表示当前进程的实际物理内存占用
- 叠加柱状图:展示了各类内存构成的详细拆分,帮助用户分析内存使用的具体来源
柱状图中的主要字段说明如下:
- heap_inuse:已经被 Go 应用代码实际使用的堆内存
- heap_unused:已经向操作系统申请但尚未被使用的堆内存(即保留但未分配)
- stack_inuse:Goroutine 栈的占用内存
- go_runtime_metadata:Go Runtime 用于管理内存、类型信息等的元数据开销
- free_mem_reserved_by_go:Go Runtime 向操作系统申请但暂未使用的空闲内存,保留用于未来分配,以减少频繁的系统调用
通过该面板,用户可以直观判断当前内存使用是否合理、是否存在过度分配或内存未及时释放的问题。
estimated portion of CPU time
该面板展示 TiDB 实例在不同系统任务上的 CPU 时间占比(估算值),用于帮助用户快速识别 CPU 资源的主要消耗方向。该面板尤其适用于排查高 CPU 使用率的问题,判断是业务层的开销,还是 Go Runtime 层(如 GC、内存清理等)的系统开销。
面板中的主要字段说明如下:
- user_total:用户代码执行所消耗的 CPU 时间,通常代表实际业务逻辑运行的成本
- gc_total:Golang GC(垃圾回收)执行所消耗的 CPU 时间,反映内存回收过程对 CPU 的影响
- idle_total:CPU 空闲时间(即调度器没有可运行任务的时间),可用于观察是否存在调度资源冗余或业务负载不足
- scavenge_total:Go Runtime 的 Scavenger 所消耗的 CPU 时间,主要用于后台将未使用的物理内存页归还给操作系统
通过该面板,用户可以快速回答以下几个常见问题:
- 当前 TiDB 实例是否存在 GC 压力?
- 是否有大量 CPU 被 Runtime 后台任务占用(如 Scavenger)?
- 用户代码 CPU 占用是否与实际业务负载匹配?
该面板也可与 Golang GC、GC STW Latency 等面板结合使用,构建完整的 GC 压力分析视角。
golang GC
该面板用于展示 每个监控上报周期内 Golang GC 的触发次数,帮助用户直观了解 GC 频率。
此外,该面板通常与 Estimated Portion of CPU Time 监控面板配合使用,以分析 GC 对 CPU 资源的影响。例如,若 GC 触发频率较高且 GC 的 CPU 占比显著增加,可能意味着系统存在 GC 压力,从而影响 TiDB 的整体性能。
GC STW Latency(>= go1.22.0)
该面板用于展示 GC Stop-The-World(STW)延迟,相比 GC STW Duration(last 256 GC cycles) 面板(如下左图所示),能够提供更高粒度、低延迟的观察视角。
此外,该面板进一步区分了 GC 触发的 STW 和非 GC 触发的 STW,便于用户更细粒度地分析 STW 对 TiDB 性能的影响。例如,若非 GC 相关的 STW 延迟较高,可能需要关注其它因素带来的开销,如频繁抓取 profile 等。
注: GC STW Duration(last 256 GC cycles) 面板展示了最近 256 次 GC 中每轮 GC 的 STW 耗时的分位数统计(如 min / p25 / p50 / p75 / max),粒度相对较粗。
sync mutex wait
该面板用于展示 Goroutine 在 sync.Mutex 或 sync.RWMutex 上阻塞的累计时间(近似值),帮助用户分析锁竞争情况。
通过该面板,用户可以直观观察同步锁的等待时间,从而判断是否存在严重的锁争用问题。例如,当 TiDB 并发性能下降,且该指标显著上升,可能意味着某些关键路径存在严重的锁竞争。此时,可以进一步查看 Mutex Profile 或 Goroutine 栈,以定位问题。
GOGC & GOMEMLIMIT
该面板用于展示 Golang 的垃圾回收(GC)相关的两个重要参数:GOGC
和 GOMEMLIMIT
。通过该面板,用户可以判断 TiDB 服务器的 enable_gogc_tuner
和 tidb_server_memory_limit
机制是否正常运行。
三、如何借助面板进行问题分析与定位(附案例说明)
问题描述
- 遇到的问题:golang gc 导致 tidb-server cpu 持续打满
- Issue 链接:https://github.com/pingcap/tidb/issues/48741
- 问题描述:这个 GitHub issue 复现了一个在真实客户环境中遇到的问题:在 TiDB 从低版本升级到 6.5.0 后,当
tidb-server
的内存使用量达到tidb_server_memory_limit
设定的阈值时,CPU 会频繁被占满,进而影响业务吞吐,而在升级前并不会出现这一现象。
现象分析
从 tidb-runtime -> CPU Usage 监控面板可以看到,从 16:48 开始,CPU 使用率出现大幅波动,并在 16:55 之后持续保持在满载状态。
由于 CPU 使用率明显异常,首先查看 tidb-runtime -> estimated portion of CPU time 监控面板,分析具体是哪些任务导致了高 CPU 负载。从监控数据来看,gc_total
的 CPU 占比在 16:48 之后显著增加,从 1.8% 迅速上升,并最终稳定在 32%。此外,gc_total
的 CPU 占比与 CPU 使用率的高负载呈现明显的正相关关系。
机制分析
TiDB 6.5.0 引入了两个新的配置项:
tidb_server_memory_limit
tidb_server_memory_limit_gc_trigger
当 tidb-server
进程的内存使用量达到 tidb_server_memory_limit * tidb_server_memory_limit_gc_trigger
时,会主动触发 Golang 的 GC。这个机制是通过动态调整 Golang 的 GOMEMLIMIT
变量实现的,其中 GOMEMLIMIT
的设定值为 tidb_server_memory_limit * tidb_server_memory_limit_gc_trigger
。
为了避免 GC 过于频繁,TiDB 采用了一个自适应调整策略:
- 初始状态:当
tidb-server
进程内存未达到阈值时,tidb_server_memory_limit_gc_trigger
维持默认值 0.7。 - 触发 GC 后调整:当
tidb-server
达到阈值并主动触发一次 GC 后,tidb_server_memory_limit_gc_trigger
会从 0.7 提升至 1.1,并在 1 分钟后回调至 0.7。
理论上,不同内存范围下的预期行为如下:
- 当进程内存 <
tidb_server_memory_limit * 0.7
:tidb_server_memory_limit_gc_trigger
应始终保持在 0.7,CPU 使用率应保持正常业务波动。 - 当进程内存处于
[tidb_server_memory_limit * 0.7, tidb_server_memory_limit * 1.1)
之间:每次tidb_server_memory_limit_gc_trigger
从 1.1 回调到 0.7 后,会立即被重新设置为 1.1,CPU 负载应仍维持正常业务波动。 - 当进程内存 ≥
tidb_server_memory_limit * 1.1
:tidb_server_memory_limit_gc_trigger
应长期保持在 1.1,但由于频繁 GC,CPU 使用率可能会被打满。
需要注意的是,由于 non-heap
内存的影响,实际的上下界数值会略高于理论计算值。
问题分析总结
在本次复现案例中:
-
tidb_server_memory_limit = 760MB
-
tidb_server_memory_limit_gc_trigger = 0.7
-
计算得到的阈值范围:
- 下界:
760MB * 0.7 = 532MB
- 上界:
760MB * 1.1 = 836MB
- 下界:
从 tidb-runtime -> GOGC & GOMEMLIMIT 监控面板来看,在 16:46 ~ 16:55 期间,GOMEMLIMIT
在上下界之间波动,但之后一直稳定在下界。
同时,从 tidb-runtime -> Memory Usage 监控面板来看,tidb-server
进程的 RSS 内存使用量在 16:46 ~ 16:55 期间从 580MB 增加至 712MB,并在之后持续上升。按照前述分析,在这个内存区间内,GOMEMLIMIT
应该始终保持在上界,但监控数据显示 GOMEMLIMIT
仍然维持在下界,不符合预期。因此,我们怀疑 tidb_server_memory_limit_gc_trigger
的自适应调整机制未生效。
在后续的代码分析中,确认 tidb_server_memory_limit_gc_trigger
的自动调整机制实现存在 bug,导致 GOMEMLIMIT
无法正确调整,从而引发高频 GC,最终导致 CPU 持续被占满。
修复后重新测试,从监控中可以看到,GOMEMLIMIT 的自适应调整符合预期,golang gc 的 cpu 占比稳定在 2% 左右,tidb 整体 cpu 使用率不再出现打满的情况。
四、结语
TiDB-Runtime Grafana 监控页面提供了丰富的 Go Runtime 层监控视角,涵盖内存使用、GC 行为、调度延迟、锁竞争等多个关键维度。通过对各个核心面板的理解与合理使用,用户可以更高效地定位 TiDB 实例运行中的潜在性能问题,尤其是在面对难以复现的问题时,Runtime 层的监控数据往往能提供关键线索。在实际运维和问题排查过程中,建议结合业务特征、监控趋势和 Profile 工具综合分析,提升排查效率。