ZFS 针对使用场景的优化方法 

Last Update: 2023-09-24

目录

用于虚拟化环境中

primarycache

如果使用 zvol 来存储虚拟机硬盘数据,使用 primarycache=metadata ;如果使用 .qcow2 (QEMU) 文件来存储虚拟机的硬盘数据,使用 primarycache=none

虚拟机内部有自己的缓存机制,在内部缓存未被命中时查找 ARC 中的 metadata 就可以加快检索 zvol 的速度, primarycache=all 会导致 ARC 中的缓存与虚拟机内部的缓存重复,从而造成空间浪费。

QEMU 默认在内存里保存了一份 L2 table,这个 L2 table 的大小可以通过 l2-cache-size 参数设置,它可以看作 .qcow2 文件内的数据的 metadata 缓存,所以此时不需要 ZFS 重复创建缓存。

存储数据库文件

ashift 与 recordsize 的值

ashift 值确定了 ZFS 分配 block 的最小值与增量的梯度,而 recordsize 的值确定了 ZFS 分配 block 的最大值。更多信息可以看 ZFS 技巧与知识 一文中关于 ashift 与 recordsize 的描述。

总而言之:

dataset 的 primarycache(ARC)

写在前面: 网络上有很多教程说要设置 primarycache=metadata。这是错的。MySQL/MariaDB 等依赖自建缓存的数据库这么设置没问题,但是 PostgreSQL 这种使用双缓存设计的数据库就推荐 primarycache=all。

ZFS dataset primarycache 控制这个 dataset 在 ARC 上的缓存方式,默认值为 all。这告诉 ZFS 在 ARC 中缓存整个用户文件。而 primarycache=metadata 则告诉 ZFS 只需要在 ARC 中缓存用户文件的 metadata 部分,数据部分不需要缓存。

然而,数据库有自己的缓存系统。primarycache=all 可能导致 ARC 中与数据库缓存中有相同的数据,从而浪费了内存空间。

但还有另一种情况,数据库缓存没有被命中时,ARC 中的数据被命中了,此时 ARC 中用户文件的数据部分提升了数据库的性能。

但不能使用 primarycache=no 关闭 dataset 的 ARC 缓存。这样的话,一旦数据库的缓存没有被命中,就需要真正检索文件系统了。而如果 ARC 缓存了用户文件的 metadata 的话,一旦数据库的缓存没有被命中,ARC 中的 metadata 缓存会提升数据库检索文件系统的性能表现。

注意到 MySQL/MariaDB 的 InnoDB 引擎 (innodb_buffer_pool_size) 以及 Oracle 都推荐使用内存的 80% 作为缓存,也就是说,它们依赖的是自己创建的缓存。而 PostgreSQL 则推荐 shared_buffer 的值为内存的 25%,这是因为 PostgreSQL 采用的是 shared_buffer 和操作系统/文件系统双缓存的机制;PostgreSQL 的缓存策略也是向着双缓存方向优化的,所以可以观察到 PostgreSQL 在用 shared_buffer 的空间时很小心 (有频繁的换入换出)。

针对 MySQL/MariaDB 等依赖自建缓存的数据库,将数据库文件所在 dataset 的 primarycache 设置为 metadata,将内存让给数据库的自建缓存。ARC 中只缓存数据库文件的 metadata 信息,让数据库在未命中缓存时,优化检索文件系统的速度。

如果把 MySQL/MariaDB 这种依赖自建缓存的数据库的缓存空间调整的足够小,也可以设置 primarycache=all 来让数据库使用外置缓存以提高性能,但这种配置组合的性能可能就会略微差于给数据库足够的,自建缓存的空间的情况下的性能。

这两个配置方案的核心在于,如果数据库文件所在 dataset 使用 primarycache=all 的情况下,分给数据库缓存的内存不能与 ARC 容量接近,一定要拉开足够大的容量差距,优先满足其中一个的需要,不能出现两个都不够用的情况。

针对 PostgreSQL 等使用双缓存机制的数据库,将数据库文件所在 dataset 的 primarycache 设置为 all,以使得数据库未命中自建缓存的内容时,可以再去 ARC 中检索数据。

然而对于 PostgreSQL 这种使用双缓存机制的数据库来说,给它足够大的空间让其自建缓存,然后给很小或者关闭的操作系统/文件系统缓存 (primarycache=metadata 或者 none),就像依赖自建缓存机制的数据库那样的话,性能的下降的问题要严重得多。 所以也不推荐这样的配置。

secondarycache 同 primarycache 类似,它控制 L2ARC 的缓存方式。当然,如果系统中没有 L2ARC 的话,这个参数就不生效。但是要注意,L2ARC 的独立性较强,ARC 淘汰的数据也不会直接进入 L2ARC,它偏向于缓存非连续的小块数据。

数据库文件所在 dataset 的 secondarycache 的值推荐与 primarycache 相同。

注意: MySQL/MariaDB MyISAM 引擎并没有缓存,使用 MyISAM 时应设置 primarycache=all 与 secondarycache=all 以确保数据库有缓存可用。

其他零散优化

显然,数据库文件不需要更新 atime,关闭数据库文件所在 dataset 的 atime 即可。

dataset logbias 使用默认值 latency。通常,ZFS 按照使用顺序写操作来写入元数据和数据,logbias=throughput 会导致这些写入操作被分开,从而导致硬盘空间碎片化变得更严重。

? 通过内核参数 zfs_prefetch_disable 禁用预加载

ZFS prefetch 优化了多线程并行读的性能,而关闭 prefetch 可以提升单线程读的性能。

ZFS prefetch 通过几个读模型来判断是否需要预加载某些数据以优化读性能。

MySQL/MariaDB 的 InnoDB 引擎,以及 PostgreSQL 都自带了 prefetch 的逻辑。然而,众所周知,OLTP 的工作负载以随机读为主,不会从 prefetch 中受益。所以数据库并不需要这个功能。

但是也没有人严格证明 ZFS 的 prefetch 与数据库的 prefetch 有冲突。只有网络上一些论坛与教程的反馈表示,在某些特定情况下,数据库负载较高时,关闭这个功能可以提高数据库的读性能。

所以,如果系统中还有其他可能从 ZFS prefetch 中受益的程序,开着它;如果系统中只跑数据库,那么稳妥起见,关闭 ZFS prefetch。

此外,如果 dataset 设置了 primarycache=metadata 则不会触发文件级的 prefetch。

注意: MySQL/MariaDB 的 MyISAM 引擎并没有预加载的逻辑,所以使用 MyISAM 时应保证开启 prefetch 以确保有预加载功能可用。