使用 源码 agent 核对了下上面内容:
@王军 独立跑了 pprof 并核对了源码,@ticket_new 的大方向对(瓶颈在 sorter 读路径,不是下游 sink),但参数名、机制细节、根因定位有 4 处明显偏差。
一、数据/数学核对(
全对)
| 项 | @ticket_new | 实测 | 核对 |
|---|---|---|---|
| 总采样 | 1254.17s / 4158.60% | 同 | |
| sinkWorker.handleTask cum | 548.23s/43.71% | 同 | |
| 其中 FetchByTable 占比 | 96.78% | 530.59/548.23 = 96.78% | |
| FetchByTable 总量 | 717.90s/57.24% | 同 | |
| pebble 版本 | v1.1.4-0.20250120151818-5dd133a1e6fb | 同(go.mod 核对) |
二、参数名错误(
)
sorter.max-memory-per-table 这个参数在 TiFlow 里不存在。
源码核对(pkg/config/sorter.go:23-40、server_config.go:114):
type SorterConfig struct {
SortDir string // sort-dir
CacheSizeInMB uint64 // cache-size-in-mb ← 这个才是真的
MaxMemoryPercentage int // Deprecated
MaxMemoryConsumption uint64 // Deprecated
NumWorkerPoolGoroutine int // Deprecated
NumConcurrentWorker int // Deprecated
ChunkSizeLimit uint64 // Deprecated
}
// 默认值: CacheSizeInMB: 128
并且 CacheSizeInMB 是 per-CDC-server 的全局 sorter block cache 大小,不是"per-table"。“per-table” 在源码里完全找不到对应字段。给客户的话术里写 sorter.max-memory-per-table=128MB 客户改了不生效。
三、机制描述偏差(
部分错)
1. 真正的瓶颈不是"L0 sstable 多",是 range deletion 元数据读放大
@ticket_new 说"每次 Iterator.First 需要合并更多 L0 文件"。实测 pprof focus=readRangeDel:
| 路径 | cum | cum% |
|---|---|---|
sstable.Reader.NewRawRangeDelIter |
651.66s | 51.96% |
sstable.Reader.readRangeDel |
651.66s | 51.96% |
sstable.Reader.readBlock(被 readRangeDel 调用) |
651.65s | 51.96% |
Iterator.First |
651.49s | 51.95% |
每条 Iterator.First() 都会对范围内所有 sstable 调 NewRawRangeDelIter → readRangeDel → readBlock。是 range-del 块吃掉了 52% CPU,不是普通 data block。RangeDel 块在 pebble 里独立于 data block,通常不在 block cache 命中,所以每次 First 都重新 alloc + CRC + pread。
→ RangeDel 积压的成因是 CleanByTable 没跟上(sorter 的废弃 segment 没被清理),通常对应 sink checkpoint-ts 卡住。这与 TICKET-8755 是同一类现象的不同表现。
2. “readBlock 内 53.93% 是 makeslice(377s)” 标错入口
实测:
pebble/internal/cache.Alloc.newValue:378.71s / 30.20%(这是 Pebble 自己的 block cache 分配,不是 Go 运行时makeslice)runtime.makeslice:422.22s / 33.67%(分散在多个 caller,不全是 readBlock)
cache.Alloc 是 pebble 用 mmap 风格自己管的内存池,调大 Go runtime GOGC/GOMEMLIMIT 不能直接改善它。
3. “syscall.Pread 148s/11.83%” 命名略偏
实际:internal/runtime/syscall.Syscall6 157.27s/12.54%(Pread 在 Linux 走 preadv64 → Syscall6,归属到通用 syscall 入口)。数字差不多,函数名说成 Pread 不严谨。
4. CRC 函数名补一个字
hash/crc32.castagnoliSSE42Triple 159.42s/12.71%(三表变体,不是基础版)。数字对。
四、@ticket_new 漏掉的关键事实(
)
redoWorker 也在独立读 sorter,吃 187.31s/14.93% 的 FetchByTable(profile focus=redoWorker 实测)。
完整拆分:
| 入口 | FetchByTable cum | 占比 |
|---|---|---|
| sinkWorker(实际下沉用) | 530.59s | 74.13% |
| redoWorker(写 redo log 用) | 187.31s | 26.16% |
| 合计 | 717.90s | 100% |
两者独立 iterate 同一份 pebble DB,是设计层双重读放大。当 readRangeDel 单价变高(积压时),双份成本翻倍。
五、建议修正
| @ticket_new 给的 | 问题 | 更准确版本 |
|---|---|---|
调大 sorter.max-memory-per-table(默认 128MB) |
参数名错,机制也错(增大 cache 不能消 range-del 读放大) | 1)先看 cdc changefeed list 的 checkpoint-ts-lag;2)若 lag 大 → 推进 ckpt → 触发 CleanByTable → range-del 自然掉;3)真要调 cache,参数叫 sorter.cache-size-in-mb |
| 暂停 changefeed → 等 compaction → 重启 | 方向对 | 但根因不是 L0 compaction 慢,是 range-del 没清。重启前应该看下 /tmp/sorter-dir 的 LSM 直方图(pebble 内部 cockroach-pebble dump),确认 range-del 文件数 |
| 升级到最新 patch | 太笼统 | v8.x 在 iterTable 路径加了 UseL6Filters: true(已在 db.go:84 看到)+ tableFilter,但 range-del 路径未根本改造。更直接相关的是 CleanByTable 调度(replication_manager.go v3 调度)的回填 PR |
| 拆 changefeed | 方向对 | 但机制是减少单 sorter 跨表 First 范围 → 减 range-del 块扫描量,不是因为"小表"本身有问题 |
六、最该问客户的下一句话
cdc changefeed list 显示这个 changefeed 的 checkpoint-ts-lag 是多少?
如果 lag 大(>10min),那这是"puller/sink ckpt 卡 → sorter clean 不掉 → range-del 积压 → CPU 飙"的连锁反应,根因还是要回到 puller/sink 端(参见 TICKET-8755 的 puller-egress 卡死)。如果 lag 很小(<1min),那才是独立的 sorter 读放大问题,建议升级 + 调 cache。