在容器中加速 PostgreSQL
I’m ready to translate the article for you, but I don’t see the text you’d like translated—only the source link. Could you please provide the article content (or the portion you want translated) so I can convert it into Simplified Chinese while preserving the formatting and code blocks?
问题
在一台磁盘较慢的旧 CI 机器上运行测试套件时,发现 PostgreSQL 成为主要瓶颈。每次测试运行耗时 超过 1 小时。
罪魁祸首是什么?测试执行了大量的数据库操作,并使用 TRUNCATE 命令在每个测试后清理数据。由于磁盘 I/O 缓慢,PostgreSQL 大部分时间都在 将数据同步到磁盘——在 临时 CI 环境 中,这些操作完全没有必要,因为数据持久化并不重要。
在测试执行期间运行 top,显示了关键线索:
242503 postgres 20 0 184592 49420 39944 R 81.7 0.3 0:15.66 postgres: postgres api_test 10.89.5.6(43216) TRUNCATE TABLE
- PostgreSQL 为一次
TRUNCATE表消耗了 81.7 % CPU。 - 单次
TRUNCATE运行时间 超过 15 秒。
在磁盘慢的机器上,PostgreSQL 正在等待内核确认数据已写入物理存储——即使该操作仅需在测试之间清空表。
Source: …
修复方案 – 三个简单的 PostgreSQL 调整
services:
postgres:
image: postgres:16.11-alpine
environment:
POSTGRES_INITDB_ARGS: "--nosync"
POSTGRES_SHARED_BUFFERS: 256MB
tmpfs:
- /var/lib/postgresql/data:size=1g
1. --nosync 标志
POSTGRES_INITDB_ARGS: "--nosync"告诉 PostgreSQL 在数据库初始化期间 跳过fsync()调用。- 在 CI 环境中我们不关心持久性——如果容器崩溃,我们只会重新启动。
- 这可以消除导致数据库搭建变慢的昂贵磁盘同步操作。
2. 增大 shared_buffers
POSTGRES_SHARED_BUFFERS: 256MB(相较默认约 128 MB)为 PostgreSQL 提供了更多内存用于缓存经常访问的数据。- 在测试频繁访问相同表时非常有帮助。
3. 将数据目录挂载到 tmpfs
tmpfs:
- /var/lib/postgresql/data:size=1g
tmpfs为 PostgreSQL 的数据目录创建了一个 基于内存的文件系统。- 所有数据库操作都在 RAM 中完成,显著加速:
TRUNCATE操作 – 在测试之间实现瞬时清理- 索引更新 – 无需磁盘寻道
- WAL(预写日志)写入 – 纯内存操作
- 检查点操作 – 不必等待磁盘刷新
1 GB 的大小限制对大多数测试数据库来说已经相当宽裕;可根据数据量自行调整。
结果
| 指标 | 之前 | 之后 | 改进 |
|---|---|---|---|
| 总测试运行时间 | ~60 min | ~10 min | 快 6 倍 |
单个测试(例如 API::FilamentSupplierAssortmentsTest#test_create_validation_negative_price) | 25.5 s | 0.47 s | ≈ 快 55 倍 |
| 平均每个测试时间(24 个测试) | 27 s | 0.45 s | ≈ 快 60 倍 |
示例输出
优化前 tmpfs
API::FilamentSupplierAssortmentsTest#test_create_validation_negative_price = 25.536s
API::FilamentSupplierAssortmentsTest#test_list_with_a_single_assortment = 29.996s
API::FilamentSupplierAssortmentsTest#test_list_missing_token = 25.952s
优化后 tmpfs
API::FilamentSupplierAssortmentsTest#test_list_as_uber_curator = 0.474s
API::FilamentSupplierAssortmentsTest#test_list_as_assistant = 0.466s
API::FilamentSupplierAssortmentsTest#test_for_pressman_without_filament_supplier = 0.420s
为什么这样有效
TRUNCATE– PostgreSQL 正在将空表状态同步到磁盘。- 数据库初始化 – 每次 CI 运行都会重新创建数据库。
INSERT– 创建测试夹具(用户、角色,…)。- 事务提交 – 每个测试在事务中运行,事务会回滚。
- 频繁的小写入 – 在测试执行期间发生。
在慢速磁盘上,即使是创建测试用户或截断表这样简单的操作也会 秒 级别而不是 毫秒 级别。上面的 top 输出显示单个 TRUNCATE 在等待磁盘 I/O 时消耗了 81.7 % CPU。将其乘以数百个测试,就会导致 CI 运行耗时数小时。
实用指南
- Production – 保持启用
fsync并使用保守的持久性设置。 - CI – 数据是 短暂的;速度比持久性更重要。
- Profile your pipeline – 我们发现磁盘 I/O 是瓶颈,而不是 CPU 或内存。
- tmpfs 是终极的磁盘‑I/O 消除器——所有内容都在 RAM 中,意味着没有磁盘瓶颈。
- Memory –
tmpfs会占用 RAM;确保你的 CI runner 有足够的内存(≥ 1 GB 用于数据库)。
完整的 PostgreSQL 服务配置
services:
postgres:
image: postgres:16.11-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: dbpgpassword
POSTGRES_DB: api_test
POSTGRES_INITDB_ARGS: "--nosync"
POSTGRES_SHARED_BUFFERS: 256MB
ports:
- 5432
tmpfs:
- /var/lib/postgresql/data:size=1g
注意:
tmpfs字段在 Woodpecker CI 的后端官方支持(参见pipeline/backend/types/step.go)。如果遇到模式验证警告,可能是文档过时——该功能如预期工作。
要点
- 小的配置调整可以对 CI 速度产生 巨大的影响。
- 对于临时测试数据库,优化 速度而非耐久性 是合适的。
- 使用
tmpfs可以消除磁盘 I/O 瓶颈,将数小时的测试运行时间缩短至 几分钟。
祝测试愉快! 🚀
CI + tmpfs:简单、有效、无需代码更改
CI 通过原生 Docker 支持让这变得轻而易举——只需添加一个 tmpfs: 字段即可完成。
如果你使用 GitHub Actions、GitLab CI 或其他平台,可能需要使用 docker run 加 --tmpfs 标志或自定义 runner 配置等变通办法。
TL;DR: 我试过了。
tmpfs仍然更快 且 更简单。
能通过激进的 PostgreSQL 调优匹配 tmpfs 吗?
在看到 tmpfs 带来的显著提升后,我产生了以下疑问:
“我们是否可以通过激进地调优 PostgreSQL 设置来实现类似的性能?”
这在 tmpfs 不可用或 RAM 受限的环境中会非常有用。
实验:禁用所有持久性特性
services:
postgres:
command:
- postgres
- -c
- fsync=off # Skip forced disk syncs
- -c
- synchronous_commit=off # Async WAL writes
- -c
- wal_level=minimal # Minimal WAL overhead
- -c
- full_page_writes=off # Less WAL volume
- -c
- autovacuum=off # No background vacuum
- -c
- max_wal_size=1GB # Fewer checkpoints
- -c
- shared_buffers=256MB # More memory cache
即使使用了所有这些激进设置,tmpfs 仍然更快。
| 功能/方面 | 基于磁盘(即使 fsync=off) | 基于 tmpfs |
|---|---|---|
| 文件系统开销 – ext4/xfs 元数据操作 | ❌ | ✅ |
| 磁盘寻道 – 机械延迟/受限 IOPS | ❌ | ✅ |
| 内核缓冲区缓存 – 内存拷贝 | ❌ | ✅ |
Docker overlay2 – 存储驱动开销 | ❌ | ✅ |
| 配置复杂度(7+ 设置) | ❌ | ✅ |
| 纯 RAM 操作 – 无物理存储 | ✅ | ✅ |
| 零磁盘 I/O | ❌ | ✅ |
| 最大性能 – 没有比 RAM 更快的 | ❌ | ✅ |
奖励:其他 PostgreSQL CI 优化可考虑
如果你仍在寻找更多加速方法,请尝试以下调整。
禁用查询日志
command:
- postgres
- -c
- log_statement=none # Don't log any statements
- -c
- log_min_duration_statement=-1 # Don't log slow queries
其他调整
fsync=off在postgresql.conf中 – 禁用同步写入(类似于--nosync)。仅在临时、非持久化环境中使用。- 增加
work_mem– 为每个查询提供更多内存,可加速测试中的复杂操作。