何时选择x86-64还是aarch64
在 PlanetScale 上创建 Postgres 或 Neki 集群时,aarch64 与 x86-64 即便 vCPU 和内存相同,性能也可能相差悬殊,且创建后无法更换架构。
中文
复制

云上的 vCPU 并不都一样。
在 PlanetScale 上创建 Postgres 或 Neki 数据库时,你必须在 aarch64(ARM)和 x86-64 之间二选一。两个集群即便 vCPU 数量和内存完全相同,只要架构不同,性能就可能差得很远。这些差异值得提前搞清楚,因为集群创建之后,CPU 架构就没法轻易换了。
ARM 和 x86 是为解决不同问题而生的
x86 是在桌面端长大的,优先考虑的是向后兼容和性能。它 1978 年诞生于 Intel,而 IBM 决定在初代 PC 上采用 x86 处理器,让它从此成为个人电脑的标配。
英国公司 Acorn 开发了第一颗 ARM 处理器。为了压低制造成本,他们想用便宜的塑料封装。为了避免芯片过热,团队把功耗预算定在了 1 瓦。
1985 年 4 月,Acorn 的工程师第一次给这颗芯片通电,它跑起来了,但电源功率表读数为零。测试板上的一个故障导致芯片根本没有接到电源上,它靠的是周围电路漏过来的电流。正确接上电源后,它的功耗约为十分之一瓦,远低于 1 瓦的预算。ARM 设计师 Sophie Wilson 后来称这种能效表现纯属意外。
ARM 的低功耗让它很适合手机。当数据中心运营商开始想办法削减电费和散热成本时,这种能效对服务器也变得有吸引力了。AWS 和 Google 如今都在为云服务器自研 ARM 处理器,分别是 Graviton 和 Axion。
CPU 架构如何影响 Postgres
对数据库来说,差异首先体现在云厂商怎么计算 vCPU。
什么是超线程?
一个 CPU 核心有多个执行单元,但单线程很少能让它们全部忙起来。多数时候,线程都在停顿,等待内存里的数据。超线程让一个核心同时装载两个线程。一个停顿时,核心就去执行另一个线程的指令。可以想象一个厨房里有两个厨师。他们能做出更多菜,但烤箱和水槽是共用的。在支持超线程的 x86 实例上,云厂商把每个线程当作一个 vCPU 出售。AWS Graviton、Google Axion 这类 ARM 服务器芯片不用超线程。每个 vCPU 就是一整个核心,各自有独立的烤箱和水槽。
大量并发查询,还是一条重查询
这正是 ARM 的强项:大量查询同时到来时,多出来的核心能派上用场。比如一家网店,可能要同时为几百名顾客查询商品、检查库存、更新购物车。每条查询都不大,但数据库可以把它们分散到各个核心上。这些核心还留出了余量,让并行查询 worker 和 autovacuum(Postgres 的后台清理进程)与业务查询一起运行。再看一种报表看板:一条查询要花好几秒,在数百万条销售记录上计算汇总。如果这条查询是 CPU 密集型的,又没用并行 worker,多出来的核心并不会让它更快跑完。这时单核速度更重要,而这正是 x86 通常占优的地方。x86 服务器芯片的时钟频率(核心执行指令的速率)一般更高,所以一个核心能更快跑完单条查询。
更宽的向量寄存器
核心的性能还取决于每条指令能做多少事。有些指令一次处理多个值。假设你要比较两个 32 位数字列表。一条向量指令可以一步比较多对数字:128 位向量比四对,256 位向量比八对,512 位向量比十六对。向量越宽,一条指令处理的数据就越多,做同样的活所需的指令数也就越少。在 x86 上,AVX2 是一组使用 256 位向量的向量指令。部分芯片还支持 AVX-512,把位宽扩展到 512 位。AWS 的 ARM 服务器芯片 Graviton4 和 Graviton5 使用 128 位向量。TIN 是 PlanetScale 为 Postgres 打造的全文本搜索索引,它用向量指令来比较位图。每个页级位图包含 256 位,每一位记录某个数据库页是否含有某个搜索词。对于一张名为 articles、带有一个名为 body 的文本列的表,我们可以建一个 TIN 索引,搜索同时包含 “postgres” 和 “replication” 的文章:
CREATE INDEX articles_body_tin ON articles USING tin (body);
SELECT *
FROM articles
WHERE body ==> 'postgres AND replication';
==> 运算符执行 TIN 搜索。TIN 将两个词的位图求交,找出可能同时包含这两个词的页面。在 x86 上,它可以用一条 AVX2 AND 一次性求交全部 256 位。pgvector 是用于向量相似度搜索的 Postgres 扩展,它也针对 x86 做了一些专门优化。其 AVX-512 代码 计算 Hamming 距离和 Jaccard 距离,用来衡量两个位向量之间的差异或重叠程度。在 ARM 上,这些计算走的是通用代码路径。因此对某些索引类型来说,x86 有明显优势。
为什么不能在现有集群上更换架构
Postgres 在两种架构上都能运行,但它写到磁盘上的文件并不能在两者之间通用。把 Postgres 数据库迁移到新硬件的常规做法,是在那边搭一个副本,然后把它提升为主库。跨架构时这招行不通,原因在于副本的构建方式。副本不会从头重建数据库,它先拿到主库磁盘文件的一份逐字节拷贝,包括表和索引,然后重放 write-ahead log——一份记录所有变更的流水账——来保持同步。两台机器对这些文件的解读一旦有差异,就可能算出错误的结果。pg_trgm 是 Postgres 用于快速文本匹配和相似度搜索的扩展,它说明了这个问题可以有多微妙。当初设计它时,并没有考虑把索引在不同架构之间搬来搬去。Postgres 用 C 写成,而 C 里一个普通的 char 是有符号还是无符号由编译器决定。GCC 和 Clang 这两个常见的 C 编译器,在 x86-64 Linux 上默认 char 为有符号,在 ARM64 Linux 上默认 char 为无符号。于是同一个字节 0xFF,在一台机器上是 −1,在另一台上是 255。pg_trgm 用 char 比较来对索引中的 trigram(三个字符一组)排序。把索引拷到另一种架构上,新机器给出的顺序可能和原来不一致。Postgres 在 18 版中解决了这个问题。但这很好地说明了问题的本质:拷贝字节并不能保证两台机器对它们的含义理解一致。PlanetScale 让主库和它的副本保持在同一种 CPU 架构上。在 Neki 中,架构固定在分片所用的配置 profile 里。这些都不影响日常变更。调整集群规模或增加副本都保持同一架构,所以照常可用。如果你确实决定换架构,就需要迁移到一个新集群:在那里建好表和索引,然后用逻辑复制拷贝行数据并跟上新写入。目标端会根据这些行自行构建索引项,因此排序结果与它自己的比较规则一致。
如何选择
对于新应用,如果只是普通的读写操作,创建集群时选 ARM 是个不错的默认选项。它的性价比很高,处理并行负载也很出色。在 PlanetScale 上,相同 vCPU 和内存的 ARM 规格通常比 x86 规格便宜一些,具体因规格和区域而异。当前价格见定价。如果你的负载主要集中在少数几个 CPU 密集型查询上,那就值得仔细考虑 x86。如果性能对你的负载确实关键,并且你想为它挑出最合适的架构,那就在分别独立的 ARM 和 x86 集群上跑一遍,再决定用哪个。保持数据和配置一致,并按照你在高峰期预期的并发量来测试。对比吞吐量、较慢查询的响应时间和成本。把现有的 PlanetScale 数据库迁移到另一种架构需要做一次迁移,所以先确认性能或成本上的收益值得这番折腾。