环境Ceph 14.2.18Nautilus双 NUMA 节点56 核42 OSD一、Page Cache 是否有 NUMA 归属很多人认为 page cache 是全局的没有 NUMA 归属。实际上page cache 的每一页都有 NUMA 归属只是这个归属是隐式决定的而不像进程那样可以显式设置。1.1 Page Cache 页的 NUMA 分配机制Linux 内核在将磁盘数据读入 page cache 时会在处理该 IO 完成中断IRQ的 CPU 所在的 NUMA node上分配物理内存页磁盘 IO 完成 ↓ Block layer 触发 IRQ 完成回调 ↓ 由当前处理该 IRQ 的 CPU所在的 NUMA node 分配 page ↓ page cache 中这一页的物理内存归属于该 NUMA node所以 page cache 页落在哪个 NUMA node取决于两个因素IRQ 亲和性smp_affinity磁盘中断绑定到哪些核上处理触发 IO 的线程部分场景如同步读是由触发 IO 的线程等待完成page 在该线程所在的 NUMA node 分配1.2 跨 NUMA 访问 page cache 的典型场景场景一OSD 线程在 NUMA0但 IRQ 被调度到 NUMA1 处理OSD 线程NUMA0 核 #4发起 pread ↓ 磁盘 IO 完成IRQ 在 NUMA1 核 #5 处理 ↓ page cache 页分配在 NUMA1 ↓ OSD 线程NUMA0读取该页 → 跨 NUMA 内存访问场景二OSD 线程不绑核在 NUMA0/NUMA1 之间漂移第一次 IOOSD 线程在 NUMA0 → page 分配在 NUMA0 ↓ 线程被调度器移到 NUMA1 核 ↓ 第二次读命中 page cache但 page 在 NUMA0 → 跨 NUMA场景三多个 OSD 共享同一 SST 文件BlueFSbluefs_buffered_iotrue时OSD_A 的 RocksDB 与 OSD_B 同盘时或共享 block.db 时 OSD_A 在 NUMA0 触发 IO → page 分配在 NUMA0 OSD_B 的线程在 NUMA1 → 读同一 page → 跨 NUMA二、如何判断是否发生跨 NUMA 访问2.1 方法一numastat -p pid查看进程内存分布# 获取 OSD 进程 PIDOSD_PID$(pgrep-fceph-osd.*id 1334)# 查看 NUMA 内存分布numastat-p$OSD_PID典型输出Per-node process memory usage (in MBs) for PID 12345 (ceph-osd) Node 0 Node 1 Total ----- ------ ----- Huge 0.00 0.00 0.00 Heap 412.30 389.50 801.80 ← 堆内存分散在两个 node Stack 0.06 0.02 0.08 Private 923.14 867.23 1790.37 ---------------- Total 1335.50 1256.75 2592.25判断标准如果 Node 0 的 OSD 进程在 Node 1 上有大量内存Private 100MB 且占比超过 30%说明存在显著的跨 NUMA 内存访问。理想情况下绑定到 NUMA0 的 OSD 几乎所有内存都应该在 Node 0。2.2 方法二查看线程的 CPU 分布# 查看 OSD 进程所有线程跑在哪些核上ps-eLopid,tid,psr,comm|grep$(pgrep-fceph-osd.*1334)|head-30输出中psr列是当前线程运行的 CPU 编号。对照本机的 NUMA 拓扑# 查看 NUMA 拓扑numactl--hardware# 或cat/sys/devices/system/node/node0/cpulist# NUMA0 的核列表cat/sys/devices/system/node/node1/cpulist# NUMA1 的核列表以本机为例偶数核 NUMA0奇数核 NUMA1PID TID PSR COMMAND 12345 12345 0 ceph-osd ← NUMA0 12345 12346 3 tp_osd_tp ← NUMA1 12345 12347 4 rocksdb:low ← NUMA0 12345 12348 15 msgr-worker ← NUMA1 同一个 OSD 进程的线程分散在两个 NUMA node则这些线程之间共享的内存RocksDB block cache、page cache、内存池都会产生跨 NUMA 访问。2.3 方法三perf 硬件性能计数器最精确# 监控远端 NUMA 内存访问占比perfstat-e\mem_load_retired.local_pmm,\mem_load_retired.remote_pmm,\mem_load_retired.local_dram,\mem_load_retired.remote_dram\-p$OSD_PIDsleep30输出示例1,234,567 mem_load_retired.local_dram # 本地 NUMA DRAM 命中 897,234 mem_load_retired.remote_dram # 远端 NUMA DRAM 命中 ← 这个高说明跨 NUMA 严重remote_dram/local_dram比值 30% 即说明跨 NUMA 访问严重。2.4 方法四/proc/pid/numa_maps详细页面分布cat/proc/$OSD_PID/numa_maps|grep-v^$输出格式地址 策略 N0页数 N1页数 ...7f1234000000 default N01024 N1987 ← 这段匿名内存在两个 node 上都有分配 7f2345000000 default N02048 N10 ← 这段完全在 NUMA0理想状态N0 和 N1 的页数接近说明内存分配没有 NUMA 亲和性大量跨 NUMA 访问几乎必然发生。2.5 方法五间接判断——查看 IRQ 亲和性# 找到磁盘的 IRQ 号cat/proc/interrupts|grep-Esdb|nvme# 查看该 IRQ 的 CPU 亲和性掩码cat/proc/irq/IRQ_NUM/smp_affinity_list如果磁盘 IRQ 绑定在 NUMA1 的核上而 OSD 线程主要跑在 NUMA0那么 page cache 页由 IRQ 处理时分配会在 NUMA1OSD 读取时必然跨 NUMA。三、IO 进程绑核的好处3.1 消除跨 NUMA 内存访问延迟这是绑核最核心的收益。NUMA 本地访问延迟~80ns NUMA 远端访问延迟~150ns约 1.9 倍 以 generic_file_buffered_read 中的 copy_page_to_iter 为例 操作从 page cache 复制 4KB 数据到用户 buffer 本地 NUMA4096 / 64 (cache line) × 80ns ~5μs 跨 NUMA 4096 / 64 × 150ns ~10μs × 每次 RocksDB GET 读 1~3 个 block × 高峰期 42 OSD 并发大量 GET → 内存延迟开销累加非常可观绑核按 NUMA node 绑定后OSD 线程、其堆内存、page cache 页都在同一 NUMA node内存访问延迟稳定在本地延迟。3.2 提高 CPU Cache 命中率L1/L2 Cache不绑核时线程可能在每次调度后跑到不同的 CPU 核上调度前线程在 Core #4L1/L2 cache 里有 OSD 的热数据onode cache、内存池指针 调度后线程被调度到 Core #12 → Core #12 的 L1/L2 cache 全是冷的原有缓存全部失效 → 需要重新从 L3 cache 或内存加载数据绑核后线程长期在固定的一组 CPU 核上运行线程始终在 Core #4 和 Core #6同一 NUMA node 的两个核之间调度 → L2 cache256KB/核和共享 L3 cache35MB/socket持续命中 → 减少内存访问次数降低 CPU 执行时间3.3 减少 TLB 和 Context Switch 开销线程跑到新核时不仅 L1/L2 cache 失效TLB页表缓存也需要刷新不绑核线程频繁在不同核间迁移 → 每次迁移TLB flush清空页表缓存 → 后续内存访问需要重新 page walk从 CR3 开始遍历页表 → 对于 OSD 这种内存访问密集的程序TLB miss 率直接影响性能 绑核线程在固定核上TLB 长期有效 → 内存访问直接命中 TLB → page walk 次数大幅减少3.4 配合 IRQ 亲和性实现端到端 NUMA 本地化绑核后可以进一步将磁盘 IRQ 绑定到与 OSD 线程相同的 NUMA node# 将 sdb 的 IO 完成中断绑定到 NUMA0 的核偶数核echo0,2,4,6,8,10,12,14/proc/irq/IRQ_NUM/smp_affinity_list实现完整的本地化链路OSD 线程NUMA0→ 发起 IO 磁盘 IRQNUMA0 核处理→ page cache 页分配在 NUMA0 OSD 线程NUMA0→ 读 page cache → 本地 NUMA 访问每个环节都在 NUMA0没有任何远端内存访问。3.5 降低尾延迟p99/p999对于存储系统尾延迟比平均延迟更重要。跨 NUMA 访问的延迟不稳定~150ns 是均值有时更高是尾延迟抖动的主要来源之一。绑核后内存访问延迟稳定p99/p999 延迟会明显收窄。3.6 防止 CPU 竞争多 OSD 共存场景你们的场景42 OSD × ~10 线程 约 420 个线程共享 56 个核。不绑核时Linux 调度器可能把多个 OSD 的线程都堆在热点核上不绑核 Core #0 同时有 OSD_1321 的 tp_osd_tp、OSD_1334 的 msgr-worker、OSD_1342 的 rocksdb 线程 → 这些线程互相竞争 Core #0 的时间片 → 高 IO 峰值时某个 OSD 的线程可能长时间等待调度 绑核每 OSD 划分 1~2 个核 OSD_1321~1341绑定 NUMA0 的 28 个核每 OSD 约 1.3 核 OSD_1342~1362绑定 NUMA1 的 28 个核每 OSD 约 1.3 核 → 不同 OSD 的线程不再互相竞争调度更可预期四、Ceph OSD 现网是否需要绑核4.1 你们当前场景分析硬件Intel E5-2680 v4双 NUMA56 逻辑核28 物理核 × 2 超线程 OSD42 个 内存95G 存储LVM 部署无独立 block.db核资源测算维度数据每 OSD 平均可用核数56 / 42 ≈1.33 个逻辑核每 OSD 典型线程数~10 个tp_osd_tp、messenger、rocksdb、finisher 等总线程数42 × 10 ≈ 420 个线程调度竞争比420 线程 / 56 核 7.5 个线程/核结论CPU 资源已经偏紧高 IO 压力下不绑核会导致严重的调度竞争。4.2 建议按 NUMA node 绑核而非精确绑单核不建议精确绑到每个核运维成本高且 1.33 核/OSD 的情况下没有足够余量建议按NUMA node 粒度绑核NUMA node 028 个逻辑核0,2,4,...,54→ OSD 1321~134121 个 OSD NUMA node 128 个逻辑核1,3,5,...,55→ OSD 1342~136221 个 OSD每个 NUMA node 的 28 核分给 21 个 OSD平均 1.33 核/OSD与不绑核时的总体比例相同但消除了跨 NUMA 访问。4.3 绑核实现方式systemd numactl# 创建 override 目录mkdir-p/etc/systemd/system/ceph-osd1321.service.d/# 写入绑核配置绑到 NUMA0cat/etc/systemd/system/ceph-osd1321.service.d/numa.confEOF [Service] ExecStart ExecStart/usr/bin/numactl --cpunodebind0 --membind0 \ /usr/bin/ceph-osd -f --cluster ceph --id %i \ --setuser ceph --setgroup ceph EOFsystemctl daemon-reload systemctl restart ceph-osd1321--cpunodebind0CPU 只跑在 NUMA node 0 的核上--membind0所有内存分配堆、栈、mmap优先从 NUMA node 0 分配配套的 IRQ 绑核找到对应磁盘的 IRQ# 查找磁盘对应的 IRQls/sys/block/sdb/device/cat/proc/interrupts|grepsdb\|nvme# 将 IRQ 绑到 NUMA0 的核偶数核echo0,2,4,6,8,10,12,14,16,18,20,22,24,26/proc/irq/IRQ/smp_affinity_list4.4 绑核的代价与注意事项代价说明缓解方式运维复杂度增加OSD 上下线需要维护绑核配置写统一的 ansible playbook 管理空闲 OSD 浪费 CPU某个 OSD 低负载时绑定给它的核不能被其他 OSD 用只做 NUMA node 绑不做精确核绑绑核方案不合理可能更差把多个高负载 OSD 绑在 NUMA0NUMA1 空闲按 OSD 数量均分到两个 NUMA node4.5 优先级建议绑核需要重启 OSD涉及数据重平衡操作成本较高。建议按以下顺序推进第一步立即无需重启 ceph config set osd bluefs_buffered_io false → 直接消除 copy_page_to_iter 的 CPU 开销 → 释放 page cache 重复缓存的内存 → 预期 CPU 使用率下降 10~30% 第二步规划窗口期需要重启 OSD 按 NUMA node 绑核 IRQ 亲和性调整 → 消除跨 NUMA 内存访问延迟 → 降低 p99/p999 尾延迟 → 长期运行稳定性提升 第三步可选配合第一步 适当调大 bluestore_cache_size / rocksdb_cache_size → 去掉 page cache 双缓存后确保 RocksDB block cache 覆盖热数据五、总结问题判断方法解决方案page cache 是否有 NUMA 归属有由处理 IO IRQ 的 CPU 所在 NUMA node 决定合理设置 IRQ affinity如何判断跨 NUMAnumastat -p、ps -eLo psr、perf stat remote_dram绑核使 OSD 线程和 IRQ 在同一 NUMA node不绑核的主要损耗L1/L2 cache 失效、TLB flush、跨 NUMA 内存延迟 ×2按 NUMA node 粒度绑核你们现网需要绑核吗是42 OSD 共 56 核调度竞争比 7.5:1绑核收益显著NUMA0 绑 OSD 1321~1341NUMA1 绑 OSD 1342~1362