大功率,高延迟:NUMA 调优的"蜘蛛感应"
2026年2月5日 · 1072 字 · 6 分钟阅读
摘要: 本文探讨了现代"Titan 级"基础设施上的 NUMA(非统一内存访问)调优,这些基础设施配备了 AMD EPYC Venice 等高核心数 CPU。文章解释了本地内存访问与远程内存访问的关键性能影响,介绍了 PostgreSQL 18 新增的 pg_shmem_allocations_numa 视图用于调试 NUMA 不平衡,并提供了一套诊断 PostgreSQL 性能问题的系统化层级,在深入底层硬件调优之前先进行问题排查。
理论:理解硬件架构
在深入了解实际感受之前,让我们先看看 CPU 与 RAM 通信的技术演进。
- UMA(统一内存访问):在较简单的系统中,只有一个中央内存池。每个核心访问任何字节的路径都是"统一"的。这种方式很一致。然而,随着核心数激增,这条单一路径成为了严重的瓶颈。
- NUMA(非统一内存访问):为避免通过单一路径传输过多数据导致芯片过热,工程师将内存拆分并物理放置在特定核心组附近。访问核心"本地"的内存非常快速。访问"远程"内存?速度会慢 2 到 3 倍。
现代 CPU 如 AMD Venice 由多个 die 组成。通过 Infinity Fabric(I/O Die)访问数据会产生"远程访问"延迟。
- CXL(计算 express 链接):下一个前沿技术。它专为大规模虚拟化设计,允许 RAM 在不同物理服务器之间共享以避免"孤立内存"。虽然很灵活,但引入了最高的延迟。数据基本上"位于"本地主板边界之外。
正如 NUMA 进化是为了解决 UMA 的瓶颈,CXL 并非替代品:它是一个新的架构层,位于 NUMA 之上,弥合本地速度与全局规模之间的差距。
实际案例: toddlers 和玩具箱
现在,让我们把这个变得具体些。为了理解为什么你的数据库在 896 线程巨兽上可能很慢,我们可以把 CPU 核心想象成蹒跚学步的孩子,把 RAM 想象成玩具箱。
- UMA 时代(单一玩具箱):所有 toddlers 都去房间中间的一个大玩具箱。问题?他们开始撞头。去玩具箱的路变成了哭闹孩子的交通堵塞。
- NUMA 时代(私人玩具箱):我们给每组 toddlers 分配了紧邻他们的私人玩具箱。
- 本地访问:从自己的玩具箱拿玩具是瞬间完成的。太棒了!
- 远程访问:如果一个 toddler 需要从邻居的玩具箱拿玩具,他们需要穿过房间。这更慢,更重要的是会产生冲突。当来自不同 sockets 的两个 toddlers 争夺同一个玩具(一页内存)时,他们停止玩耍开始争吵。这些"chamailleries"(争用)正是杀死你的 TPS 的原因。
- CXL 时代(社区花园):现在花园里有一个巨大的社区玩具箱。这对共享很好,但路程很长。想象 896 个 toddlers 试图协调谁从花园拿到哪个玩具……混乱的潜力是巨大的。
虚拟化陷阱
在这些"Titan"环境中,你的 Postgres 实例很可能在 VM 或容器中。如果"精神分裂"调度器将你的 Postgres 进程(toddler)移动到不同的 socket,但将你的 shared_buffers(玩具)留在原始 socket 或花园中,每次内存访问都会变成缓慢的"远程访问"。相信我,你的性能会暴跌。
这里我们看到了"陷阱":Socket 1 上的 Postgres 进程被迫跨越 Infinity Fabric 访问存储在 Node 0 中的缓冲区。这就是"NUMA 税"在起作用。
PostgreSQL 18:培养你的"蜘蛛感应"
识别这些隐藏的"走路"和主板上的争吵一直完全是猜测。PostgreSQL 18 终于给了我们透过管理程序和硅芯片观察的能力。
pg_shmem_allocations_numa:这个新的系统视图是我们一直在等待的突破。它允许你精确审计共享内存如何在物理 NUMA 节点上分布。
通过使用这个视图,你终于可以执行真正的硬件和虚拟化审计:
- 发现"社区花园"(CXL):CXL 内存通常表现为"无 CPU"的 NUMA 节点。如果你看到"热"缓冲区位于没有附加核心的节点上,你就发现了一个延迟陷阱。
- 验证管理程序:你现在可以验证 VM 的虚拟拓扑是否与物理硬件匹配。如果操作系统认为它在两个节点上分布数据,但管理程序将所有内容固定在一个 socket 上,你就识别出了一个巨大的争用源。
目标:结合使用 PostgreSQL 18 的可视化和 OS 工具如 numastat 或 lscpu,我们可以确保我们的"toddlers"和"toys"留在同一个房子里,最大限度地减少那些代价高昂的远程跳转。
结论:在修复逻辑之前不要调优物理
你应该立即深入 NUMA 调优吗?视情况而定。
虽然 NUMA 和 CXL 调优是性能的"前沿",但它们不是灵丹妙药。相信我,我见过足够多的"交火",知道你不应该基于假设做决定。在开始摆弄 numactl 或 CPU 固定之前,你必须确保基础牢固。
你的审计应该遵循这个层级:
- 界定问题:这甚至是 Postgres 吗?检查应用端是否有缓慢,或者"繁忙邻居"在同一主机上抢走你的周期。
- 数据量:你的查询是否获取了比需要更多的数据?(未使用的列、不必要的行或无用的连接)。
- SQL 编写:你的 SQL 是否写成帮助 Postgres 找到最佳执行计划?
- 统计信息:Postgres 实际上知道你的数据是什么样子吗?确保你的统计信息是最新的,这样规划器就不会盲目飞行。
- 索引:是否缺失索引迫使 toddler 扫描整个玩具箱?
- I/O 瓶颈和恶性循环:你的存储是否慢到 CPU 只是空闲地坐着?或者更糟的是,你是否陷入了大量写入的恶性循环?
- 连接风暴:你是否被活跃进程淹没?896 线程很多,但管理数百个直接连接会产生"争吵"税。使用连接池。
- 缓存策略:你的
shared_buffers是否针对你的特定工作负载进行了调优?
只有在这些优化完成后,你才应该深入 NUMA 的物理层面。硬件是物理,但软件是逻辑。你需要两者才能获胜。
如果你遇到了性能墙,我在这里帮助你。我最近推出了自己的独立咨询公司,帮助你驾驭这些架构水域。
测试、验证,让你的 Postgres 飞起来!
标签:PostgreSQL、硬件、性能