AWS工程师报告PostgreSQL性能在Linux 7.0下降50%到底是怎么一回事?
phoronix最近两天发文报道:《AWS Engineer Reports PostgreSQL Performance Halved By Linux 7.0, But A Fix May Not Be Easy》:
https://www.phoronix.com/news/Linux-7.0-AWS-PostgreSQL-Drop
这到底是么回事呢?让我们看看Salvatore Dipietro <dipiets@amazon.it>的原始邮件:
We are reporting a throughput and latency regression on PostgreSQL pgbench (simple-update) on arm64 caused by commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") introduced in v7.0-rc1. The regression manifests as a 0.51x throughput drop on a pgbench simple-update workload with 1024 clients on a 96-vCPU (AWS EC2 m8g.24xlarge) Graviton4 arm64 system. Perf profiling shows 55% of CPU time is consumed spinning in PostgreSQL's userspace spinlock (s_lock()) under PREEMPT_LAZY:

它指出7.0内核使用PREEMPT_LAZY默认替代服务器领域的PREEMPT_NONE后,用户态的一个spinlock存在疯狂自旋。
关于PREEMPT_LAZY和PREEMPT_NONE的区别,笔者之前在《2024年Linux内核十大技术革新盘点|年终盘点》一文中已有描述,LAZY介于PREEMPT和NONE之间,允许抢占在时间片到来的时刻发生。
假设没有PREEMPT_LAZY的情况下(PREEMPT情况),fair调度类的task b本身可以在时刻1抢占task a,而随着PREEMPT_LAZY的引入,这个抢占需要延迟到下一个tick的到来。

如果是PREEMPT_NONE呢,则上述时刻1和tick到来时候的抢占都不会发生。所以理论上,PREEMPT_LAZY比PREEMPT_NONE多了个时间片到来时候的延迟抢占。
这一抢占可能就抢出了事情,在特定配置下,用户态的spinlock持有后,陷入内核后,原先PREEMPT_NONE是不抢的,现在却可以抢了,这样任务A的spinlock区间被任务B抢跑了,另外一个task C若等同一个spinlock则可能就等很久:

于是Salvatore童鞋请求恢复PREEMPT_NONE(这样B抢不了A):

而scheduler维护者Peter Zijlstra给出的良方则是使用rseq来抑制抢占:

关于rseq和时间片扩展,笔者在《2025 年 Linux 内核十大技术创新|年终盘点》一文中已经有描述。它允许用户态的spinlock等进入critical section的时候,标注一个状态给内核:
如果线程通过 prctl() 启用了PR_RSEQ_SLICE_EXTENSION机制,就可以把 rseq::slice_ctrl::request 设为 1 来请求延长当前时间片。当线程被中断且内核因此产生重新调度请求时,内核看到了用户态设置了 rseq::slice_ctrl::request =1,它可能选择不立即把线程换下 CPU,而是批准一次 rseq_slice_extension_nsec 时间片扩展并直接返回用户态继续执行。
所以整个逻辑就是:
1. PREEMPT_LAZY比PREEMPT_NONE引入了抢占;可能潜在导致了问题。
2. 但是我们内核现在又有基于RSEQ的时间片扩展,允许用户态告诉内核在未来多长时间内抑制抢占,从而封堵1里面引入的抢占。

所以这给笔者的感觉大概是:先给你一杯毒药毒你个半死,再给你一杯毒药以毒攻毒。最后,你完全没中毒。一个皆大欢喜的故事!至于你抱怨自己中毒了,那是你的错,因为你没有喝第二杯毒药。哈哈。
优先级继承和优先级顶棚的故事听起来动人,但是他们本质上是拆东墙补西墙,在简单的RTOS和workload有一定作用(尤其是锁依赖链简单的系统),在复杂的系统经常东墙和西墙最终还是一起垮掉。从业务层面上来大锁化小锁,锁化无锁,才是最根本的解决问题。
更多推荐
所有评论(0)