MyISAM 和 KPTI – Meltdown 修复带来的性能影响

最近我们收到一位用户的报告,他在将服务器升级到带有 KPTI(内核页表隔离 - Meltdown 漏洞的补救措施)的 Linux 内核后,看到了惊人的 90% 性能退步。

这 90% 的很大一部分是由于在旧版本的 VMware 中运行引起的,旧版本 VMware 不会将 CPU 的 PCID 和 INVPCID 功能传递给访客机。但我即使在裸金属上也能重现大约 40% 的性能退步。

这是测试用例

CREATE TABLE t1 (c1 INT PRIMARY KEY AUTO_INCREMENT, c2 INT) ENGINE=InnoDB;
INSERT INTO t1 (c2) VALUES (FLOOR(1000*RAND()));
INSERT INTO t1 (c2) SELECT FLOOR(1000*RAND()) FROM t1;
-- repeat last insert until there are at least 1024 rows in t1

SELECT COUNT(*) FROM t1 AS a JOIN t1 AS b WHERE b.c1>a.c1 AND b.c2<a.c1;

让我们看看在非 KPTI 内核和 KPTI 内核下,对 t1 表中不同行数执行最终 SELECT 查询的执行时间。

SELECT 查询的执行时间
行数 非 KPTI KPTI 性能退步
1024 0.40 s 0.64 s 37.5%
2048 1.24 s 1.94 s 36.1%
4096 4.22 s 7.05 s 40.1%
8192 16.10 s 26.92 s 40.2%

为什么?

如果我们查看 handler 状态变量,可以看到对于 8K 行,查询调用 Handler_read_rnd_next 超过 5000 万次。对于 MyISAM,这样的 handler 调用会导致 fget() 调用,进而导致一次 __fget 系统调用。

这是因为 MyISAM 引擎没有行缓存。虽然它在 Key Buffer 中缓存索引页,但没有用于行数据的此类缓存。为此,它依赖于操作系统中的通用页缓存。这运行得很好,然而由于该缓存位于内核中,因此在 MariaDB 服务器和缓存之间存在系统调用壁垒。

KPTI 引入的页表隔离增加了系统调用的开销。因此,像上述那样在一个紧密循环中读取许多 MyISAM 行的工作负载会显著变慢。当行已经在页缓存中时,相对减慢实际上更大!

如何解决

在 MyISAM 引擎中无法修复此问题,因为它需要彻底重新设计。但好消息是,大多数其他引擎确实有行缓存。对于 InnoDB 而言是 InnoDB Buffer Pool,对于 ARIA 而言是 ARIA Page Cache。

为我们的表使用不同的引擎可以消除性能退步。以下是使用默认 128M 页缓存的 ARIA 的数字

切换到 ARIA 后 SELECT 查询的执行时间
行数 非 KPTI KPTI
1024 0.18 s 0.18 s
2048 0.57 s 0.57 s
4096 1.85 s 1.84 s
8192 6.34 s 6.30 s

结论

MyISAM 和 KPTI 配合得不好。如果您正在对 MyISAM 表运行表扫描工作负载,那么您可能会看到 KPTI 修复 Meltdown 带来的相当严重的性能影响。切换到 ARIA 或 InnoDB,并确保行缓存足够大。