ZFS(Zettabyte File System)是一种先进的文件系统,支持快照、克隆、压缩和加密等功能,还可以在多个磁盘上创建一个存储池,并将其作为单个文件系统进行管理。

我一般将 ZFS 用于大容量存储设备上,今天因为需要导入大量数据,临时将MySQL的数据库文件放置到了ZFS卷上,结果发现MySQL写入性能极差。经@熊同学提醒,并查阅文档后得到如下解决方案:

调整 ZFS 卷参数

zfs create pool/name
zfs set atime=off \
    sync=disabled  \
    compression=lz4 \
    logbias=throughput \
    primarycache=metadata \
    recordsize=16k \
    xattr=sa \
    redundant_metadata=most \
    pool/name
# 查询 ZFS 卷当前属性
zfs get all pool/name
  • sync=disabled 禁用所有显式磁盘刷新到任何一个文件系统。后台提交线程仍会每 5 秒将写入的数据刷新到磁盘。除非绝对确定不关心应用程序的数据一致性保障!否则不要这样做。

  • compression=lz4 LZ4 压缩非常快。事实上,如果您拥有现代处理器,您很可能能够比 SSD 读取压缩数据更快地解压缩数据。

  • logbias=throughput 如果设置为 throughput,则 ZFS 不使用池的单独日志设备。相反,ZFS 优化同步操作以实现全局池吞吐量和有效使用资源。默认值为 latency。对于大多数配置,建议使用默认值。使用 logbias=throughput 值可能会提高编写数据库文件的性能。

  • primarycache=metadata 由于 InnoDB 已经通过缓冲池进行了自己的缓存,因此在页面缓存中或在 ZFS 的情况下,也没有将数据缓存到页面缓存中的好处。由于 O_DIRECT 不会在 ZFS 上禁用缓存,因此我们可以通过将 primarycache 设置为仅缓存元数据(默认值为“all”)来禁用 ARC 中数据的缓存。

  • recordsize=16k InnoDB 使用 16KB 页面。使用此参数,我们告诉 ZFS 将块大小限制为 16KB。这可以防止多个页面被写入一个更大的块中,因为这将需要读取整个更大的块以进行对该块中任何一个页面的未来更新。

  • xattr=sa 这指示 ZFS 将扩展文件属性存储在文件的 inode 中,而不是隐藏目录中,因为将其存储在 inode 中更有效。这仅在使用 SELinux 的系统上才真正相关,因为这使用扩展属性来存储文件的安全上下文。

  • redundant_metadata=most ZFS 默认情况下会存储所有元数据的额外副本,超出镜像提供的冗余。由于我们优先考虑写入密集型工作负载的性能,因此我们降低了此冗余级别。

调整 MySQL/MariaDB 配置

sync_binlog = 0
innodb_flush_log_at_trx_commit = 0
innodb_log_write_ahead_size = 16384
innodb_doublewrite = 0
innodb_flush_neighbors = 0
innodb_use_native_aio = 0
innodb_use_atomic_writes = 0
innodb_compression_default = OFF
innodb_file_per_table = ON
  • sync_binlog=0 禁用磁盘刷新

  • innodb_flush_log_at_trx_commit=0 禁用磁盘刷新以在不重新启动 mysqld 的情况下暂时提高写入性能。

  • innodb_log_write_ahead_size=16384 优化存储子系统与 MySQL / MariaDB 之间的交互。InnoDB 默认为表空间使用 16KB 页面大小,使用当前选项将减少 InnoDB 表空间和日志的要求为 16KB 块,而复制日志没有固定的块大小。如果我们将 InnoDB 日志保留在数据目录中(默认情况下),则应将其设置为我们设置的 ZFS recordsize=16K

  • innodb_doublewrite=0 InnoDB 双写有一个目的——防止在应用程序崩溃时破坏数据。由于 ZFS 上的提交是原子性的,并且我们已经将 InnoDB 页面大小 innodb_log_write_ahead_size 与 ZFS recordsize 对齐,因此不可能发生破坏,页面要么整个块被写入,要么整个块丢失。

  • innodb_flush_neighbors=0 当需要将页面刷新到磁盘时,InnoDB 还会刷新所有附近的页面。这对于旋转硬盘和传统文件系统很有益,但通常只会浪费磁盘 I/O。即使使用机械硬盘,在 ZFS 上禁用它也更好。

  • innodb_use_native_aio=0 在 Linux 上,ZFS 驱动程序的 AIO 实现是一个兼容性桥接器。

  • innodb_use_atomic_writes=0 充分利用 ZFS 原生 AIO。

  • innodb_compression_default=OFF ZFS LZ4 压缩非常快,因此我们应该将压缩留给 ZFS,而不使用 InnoDB 内置的页面压缩。将压缩留给 ZFS 不会破坏块对齐。

  • innodb_file_per_table=ON 将表存储在单独的文件中,以便更轻松地备份、恢复或重定位。

结论

调整后,一个事务的执行时间从平均 6000ms 缩短到了 70ms 左右,基本上接近 ext4 文件系统下的性能水准。