坑边闲话:ZFS 是一个多功能的存储栈解决方案,作为经典的一站式本地存储系统解决方案,它必然不可能被人轻松掌握。精进技能需要长久学习和思考,以及亲自上手对真实应用场景做细致的优化测试。此前有 UP 主在 B 站批评 ZFS 无法做到开箱即用,这主要是因为他们对 ZFS 不够了解,正所谓「难者不会,会者不难」,沟通的鸿沟就在这里。在复杂的 ZFS 存储优化中,最容易上手并取得巨大收益的一项参数是 recordsize, 特别是在数据库应用场景中,不同的 recordsize 会带来巨大的性能差异。

图 1. 笔者曾在 B 站评论区用心回复某位网友的提问。

1. 数据库页面大小检查指南·

核心目标是让数据库的 page_size 等于 ZFS 数据集的 recordsize. 为什么要这么做呢?因为数据库只是文件系统上的一个 file,但是在这个 file 内部,数据库程序会将其划分为不同的 page,数据库程序发起 I/O 的时候,以 page 的大小为基本单位。如果 page 远远小于 recordsize, 如

  • page_size, 4KiB,
  • recordsize, 128KiB,

那么数据库读取一个 page,ZFS 就要至少读取一个 128KiB 的 record,此时的读放大非常严重,大概是 32 倍!同理,写放大的计算亦如此。

此外,数据库 I/O 有着巨大的随机性,因为用户的访问是随机的,一般来说没有很合适的通用预测算法进行预取,因此将 recordsizepage_size 进行统一是比较理想的决定。此外,ZFS 的压缩机制会导致实际存储的 record 略小于 recordsize, 这也能节约一定的存储空间,特别是对 MariaDB/MySQL 这种数据库。

一般来说,按照本文优化的策略,数据库 I/O 延迟将会比默认的 ZFS 参数降低 30%. 开启 ZFS 压缩,常见 Docker 应用的 PostgreSQL 的压缩率会在 1.8x 左右,MariaDB 压缩率会在 2.5x 左右。

由于不同系统可能会在编译时或初始化时调节数据库的 page_size, 所以接下来将会介绍一些查询 page_size 的方法,方便读者调节 ZFS recordsize.

注意,目前飞牛系统并不开放调节参数,甚至不能创建数据集,因此本文所有操作均默认用户非常熟悉 ZFS 命令行。数据无价!大模型可能提供错误的命令行,因此不宜盲目对存储系统进行 AI 辅助操作

1.1 什么是数据库页面大小?·

在数据库系统中,页面(Page)块(Block) 是数据库存储和管理数据的基本单位。页面大小是数据库在磁盘和内存之间传输数据的最小单元。理解和检查页面大小对于数据库性能调优和故障排查非常重要。

不同的数据库系统有不同的默认页面大小:

  • PostgreSQL: 默认 8KB
  • MariaDB/MySQL: 默认 16KB
  • SQLite: 默认 4KB(可配置为 1KB - 64KB)
  • MongoDB: 默认 32KB
  • Oracle: 默认 8KB(可配置)

1.2 为什么要关注页面大小?·

  • 性能影响: 较大的页面可以减少 I/O 操作次数,但会增加内存使用
  • 存储效率: 页面大小影响数据在磁盘上的存储方式
  • 迁移兼容性: 不同页面大小的数据库之间迁移需要特别注意
  • 优化建议: 不同工作负载可能需要不同的页面大小

2. 常见数据库页面大小检查方法·

2.1 PostgreSQL·

2.1.1 使用 SQL 查询·

连接到 PostgreSQL 数据库后,执行以下命令:

1
SHOW block_size;

示例输出:

1
2
3
4
 block_size
------------
8192
(1 row)

这表示页面大小为 8192 字节(8KB)。

2.1.2 使用 psql 命令·

1
psql -U username -d database_name -c "SHOW block_size;"

2.1.3 在 Docker 容器中检查·

如果 PostgreSQL 运行在 Docker 容器中:

1
docker exec container_name psql -U username -d database_name -c "SHOW block_size;"

实际示例:

1
2
3
4
5
# 检查 Immich 的 PostgreSQL
docker exec immich_db psql -U postgres -d immich -c "SHOW block_size;"

# 检查 Mattermost 的 PostgreSQL
docker exec mattermost_db psql -U mmuser -d mattermost -c "SHOW block_size;"

2.2 MariaDB / MySQL·

2.2.1 使用 SQL 查询·

连接到 MariaDB/MySQL 后,执行:

1
SHOW VARIABLES LIKE 'innodb_page_size';

示例输出:

1
2
3
4
5
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+

这表示 InnoDB 引擎的页面大小为 16384 字节(16KB)。

2.2.2 使用命令行·

1
mysql -u username -p -e "SHOW VARIABLES LIKE 'innodb_page_size';"

2.2.3 在 Docker 容器中检查·

1
docker exec container_name mysql -uroot -ppassword -e "SHOW VARIABLES LIKE 'innodb_page_size';"

实际示例:

1
2
3
4
5
# 检查 NextCloud 的 MariaDB(使用 mysql 命令)
docker exec nextcloud_db mysql -uroot -p123456root -e "SHOW VARIABLES LIKE 'innodb_page_size';"

# 检查 PhotoPrism 的 MariaDB(较新版本使用 mariadb 命令)
docker exec photoprism_db mariadb -uroot -pinsecure -e "SHOW VARIABLES LIKE 'innodb_page_size';"

注意事项:

  • MariaDB 11+ 版本可能需要使用 mariadb 命令而不是 mysql
  • InnoDB 页面大小在数据库初始化时设置,之后无法更改

2.3 SQLite·

SQLite 的页面大小可以通过 PRAGMA 命令查询。

2.3.1 使用 SQLite 命令行·

1
sqlite3 database.db "PRAGMA page_size;"

示例输出:

1
4096

2.3.2 在 SQLite 交互式 Shell 中·

1
sqlite3 database.db

然后在 SQLite shell 中执行:

1
PRAGMA page_size;

2.3.3 查看更多页面信息·

1
2
3
PRAGMA page_count;      -- 数据库中的页面总数
PRAGMA page_size; -- 每个页面的字节数
PRAGMA freelist_count; -- 空闲页面数

计算数据库大小:

1
2
3
-- 数据库大小 = page_count × page_size
SELECT (page_count * page_size) / 1024.0 / 1024.0 AS size_mb
FROM pragma_page_count(), pragma_page_size();

2.4 MongoDB·

MongoDB 使用文档存储,但也有内部页面大小的概念。

2.4.1 使用 MongoDB Shell·

连接到 MongoDB 后:

1
db.serverStatus().storageEngine

或查看集合的统计信息:

1
db.collection_name.stats()

在输出中查找 blockSizepageSize 字段。

2.4.2 在 Docker 容器中检查·

1
docker exec mongo_container mongosh --eval "db.serverStatus().storageEngine"

2.5 Redis·

Redis 是内存数据库,没有传统意义上的页面大小,但可以查看内存分配器信息。

2.5.1 查看内存信息·

1
redis-cli INFO memory

查找 mem_allocator 来了解内存分配策略。

2.6 Oracle Database·

2.6.1 查询数据字典·

1
2
3
SELECT value
FROM v$parameter
WHERE name = 'db_block_size';

或者:

1
SHOW PARAMETER db_block_size;

3. 实际应用场景·

场景 1: 容器化环境中的批量检查·

如果你在 Docker 环境中运行多个数据库服务,可以创建一个脚本来批量检查:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

echo "=== PostgreSQL Databases ==="
docker exec immich_db psql -U postgres -d immich -c "SHOW block_size;"
docker exec mattermost_db psql -U mmuser -d mattermost -c "SHOW block_size;"

echo ""
echo "=== MariaDB Databases ==="
docker exec nextcloud_db mysql -uroot -p123456root -e "SHOW VARIABLES LIKE 'innodb_page_size';"
docker exec photoprism_db mariadb -uroot -pinsecure -e "SHOW VARIABLES LIKE 'innodb_page_size';"

场景 2: 性能调优·

在某些情况下,你可能需要修改页面大小以优化性能:

PostgreSQL: 页面大小在编译时确定,需要重新编译才能更改。

MariaDB/MySQL: 可以在初始化时设置:

1
docker run -e MYSQL_INNODB_PAGE_SIZE=8192 mariadb:latest

SQLite: 可以在创建数据库时设置:

1
2
PRAGMA page_size = 8192;
VACUUM; -- 重建数据库以应用新的页面大小

4. 常见问题·

Q1: 为什么不同数据库的默认页面大小不同?·

不同数据库系统根据其设计目标和典型使用场景选择了不同的默认值:

  • PostgreSQL (8KB): 平衡了小型和大型事务的性能
  • MariaDB/MySQL (16KB): 针对大型数据集和复杂查询优化
  • SQLite (4KB): 适合嵌入式和移动设备,匹配文件系统的块大小

Q2: 可以更改现有数据库的页面大小吗?·

  • PostgreSQL: 不能,页面大小是编译时常量
  • MariaDB/MySQL: 不能,必须在数据库初始化时设置
  • SQLite: 可以,但需要使用 VACUUM 命令重建整个数据库

Q3: 如何选择合适的页面大小?·

考虑以下因素:

  • 数据类型: 大量 BLOB/TEXT 数据可能受益于较大的页面
  • 工作负载: OLTP(在线事务处理)通常使用较小页面,OLAP(在线分析处理)使用较大页面
  • 硬件: SSD 可能受益于与其内部块大小匹配的页面大小
  • 内存: 较大页面需要更多缓冲区内存

总结·

了解和检查数据库页面大小是数据库管理的基础技能。虽然大多数情况下使用默认值即可,但在特定场景下调整页面大小可以显著提升性能。

快速参考:

数据库 默认页面大小 检查命令
PostgreSQL 8KB SHOW block_size;
MariaDB/MySQL 16KB SHOW VARIABLES LIKE 'innodb_page_size';
SQLite 4KB PRAGMA page_size;
MongoDB 32KB db.serverStatus().storageEngine
Oracle 8KB SHOW PARAMETER db_block_size;

希望这篇指南能帮助你更好地理解和管理数据库的页面大小配置!

作者注: 本文中的实际示例基于真实的容器化部署环境,包括 NextCloud、PhotoPrism、Immich 和 Mattermost 等服务。