PG 19 异步 I/O 与缓冲区管理深度优化
PG 19 异步 I/O 与缓冲区管理优化
本文档解读 Andres Freund 提交的异步 I/O(io_uring)和缓冲区管理优化,这些改进提升了大规模 I/O 操作时的性能。
相关 Commit
41d3d64e87af | |
f39cb8c01106 | |
8df3c48e466c | |
c0af4eb4e71e | |
513374a47a71 |
核心价值
io_uring 支持:Linux 原生异步 I/O,减少等待 减少复制:页面写出时不再复制 优化锁释放:更高效的缓冲区解锁
原理详解
1. 缓冲区管理器架构
1.1 核心结构 (src/backend/storage/buffer/buf_internals.h)
typedefstructBufferDesc
{
int buf_id; /* 缓冲区 ID */
int state; /* 状态标志 */
slock_t mutex; /* 保护此结构的锁 */
LWLock *content_lock; /* 内容锁 *//* 标识信息 */
Oid relfilenode; /* 物理文件标识 */
ForkNumber forkNum;
BlockNumber blockNum;
LogicalRelFileNode rnode;
} BufferDesc;
1.2 缓冲区状态
#define BM_LOCKED BUF_DEFINE_FLAG(0) /* 正在被 I/O */
#define BM_DIRTY BUF_DEFINE_FLAG(1) /* 脏页,需要写出 */
#define BM_VALID BUF_DEFINE_FLAG(2) /* 数据有效 */
#define BM_IO_IN_PROGRESS BUF_DEFINE_FLAG(4) /* I/O 进行中 */
#define BM_IO_ERROR BUF_DEFINE_FLAG(5) /* I/O 错误 */
2. UnlockReleaseBuffer 优化
2.1 优化前 (f39cb8c01106 之前)
/*
* 优化前:分离操作
*/
UnlockBuffer(); /* 第一次调用:释放内容锁 */
ReleaseBuffer(); /* 第二次调用:释放 pin,减少引用计数 */
2.2 优化后 (f39cb8c01106)
/*
* 优化后:合并操作
* 减少函数调用开销和锁操作
*/
UnlockReleaseBuffer(BufferDesc *buf)
{
/* 同时释放内容锁和 pin */
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
UnpinBuffer(buf, true);
}
2.3 使用场景 (8df3c48e466c)
现在在以下场景使用 UnlockReleaseBuffer():
/* 页面写出后 */
if (buf == InvalidBuffer)
{
UnlockReleaseBuffer(currentBuffer);
currentBuffer = InvalidBuffer;
}/* 错误处理路径 */
UnlockReleaseBuffer(currentBuffer);
3. 脏页写出优化
3.1 问题 (41d3d64e87af 之前)
/*
* 优化前:写出时复制页面
* 会产生一次额外的内存复制
*/
Page tmppage = palloc(BLCKSZ);
CopyPage(page, tmppage); /* 复制到临时页面 */
log_newpage_buffer(tmppage, true); /* 写出临时页面 */
pfree(tmppage);
3.2 优化后
/*
* 优化后:直接写出页面
* 无需复制
*/
START_CRIT_SECTION();
MarkBufferDirty(buf);
log_newpage_buffer(buf, true); /* 直接写出页面 */
END_CRIT_SECTION();
4. io_uring 异步 I/O
4.1 核心接口 (src/backend/storage/aio/)
/*
* PgAioHandle - AIO 操作句柄
* 定义于 src/include/storage/aio_internal.h
*/
structPgAioHandle
{
uint8 state; /* 状态 */
uint8 target; /* 操作目标 */
uint8 op; /* 操作类型 (PGAIO_OP_READV, PGAIO_OP_WRITEV) */
uint8 flags; /* 标志位 */
// ...
int32 result; /* IO 操作原始结果 */
PgAioOpData op_data; /* 操作数据 */
PgAioTargetData target_data; /* 目标数据 */
};/*
* PgAioUringContext - io_uring 实例上下文
* 每个后端有一个 io_uring 实例
*/
typedefstructPgAioUringContext
{
LWLock completion_lock; /* 保护完成队列 */
structio_uringio_uring_ring;/* io_uring 实例 */
} PgAioUringContext;
4.2 异步处理判断 (pgaio_uring_should_use_async())
/*
* 决定是否使用异步处理
* 位于 src/backend/storage/aio/method_io_uring.c
*/
staticbool
pgaio_uring_should_use_async(PgAioHandle *ioh, size_t io_size)
{
/*
* 直接 IO 不需要强制异步处理
*/
if (!(ioh->flags & PGAIO_HF_BUFFERED))
returnfalse;/*
* IO 队列深度较大时,异步处理开销更小
*/
if (dclist_count(&pgaio_my_backend->in_flight_ios) > 4)
returntrue;
/*
* 大 IO 时,并行内存拷贝收益更大
* 阈值: 4 * BLCKSZ (通常是 4 * 8192 = 32768 bytes)
*/
if (io_size >= (BLCKSZ * 4))
returntrue;
returnfalse;
}
4.3 提交 IO (pgaio_uring_submit())
/*
* 提交 IO 到 io_uring
* 位于 src/backend/storage/aio/method_io_uring.c line 415
*/
staticint
pgaio_uring_submit(uint16 num_staged_ios, PgAioHandle **staged_ios)
{
structio_uring *uring_instance = &pgaio_my_uring_context->io_uring_ring;for (int i = 0; i < num_staged_ios; i++)
{
PgAioHandle *ioh = staged_ios[i];
structio_uring_sqe *sqe;
sqe = io_uring_get_sqe(uring_instance);
pgaio_uring_sq_from_io(ioh, sqe);
}
/* 提交到内核 */
ret = io_uring_submit(uring_instance);
// ...
}
4.4 异步标志设置
/*
* 对于大 IO,设置 IOSQE_ASYNC 标志
* 这让 io_uring 在后台线程执行 IO
*/
if (pgaio_uring_should_use_async(ioh, io_size))
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
5. PinBuffer 顺序修复 (c0af4eb4e71e)
5.1 问题背景
在 PinBuffer() 函数中,锁获取的顺序非常重要。错误的顺序可能导致死锁或条件变量信号丢失。
5.2 修复内容
修复涉及调整 PinBuffer() 中检查 BM_VALID 和 BM_LOCKED 标志的顺序,确保在 buffer header spinlock 被持有时不会尝试等待条件变量。
staticbool
PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
bool skip_if_not_valid)
{
// ... old_buf_state = pg_atomic_read_u64(&buf->state);
for (;;)
{
/* 先检查是否需要跳过非有效缓冲区 */
if (unlikely(skip_if_not_valid && !(old_buf_state & BM_VALID)))
returnfalse;
/* 如果 buffer 正在被 I/O 操作,spinlock 会被持有,此时不能等待 */
if (unlikely(old_buf_state & BM_LOCKED))
{
old_buf_state = WaitBufHdrUnlocked(buf);
continue;
}
/* 执行原子操作增加引用计数 */
buf_state = old_buf_state;
buf_state += BUF_REFCOUNT_ONE;
// ...
}
}
关键点:当 BM_LOCKED 标志被设置时,表示有其他进程正在对该 buffer 进行 I/O 操作,此时 spinlock 会被持有。应使用 WaitBufHdrUnlocked() 等待,而不是直接操作。
6. WaitReadBuffers 返回值 (513374a47a71)
/*
* 函数现在返回是否实际等待了
*/
staticbool
WaitReadBuffers(List *buffers)
{
bool waited = false; foreach(cell, buffers)
{
BufferDesc *buf = (BufferDesc *) lfirst(cell);
if (buf->state & BM_IO_IN_PROGRESS)
{
waited = true; /* 需要等待 */
WaitIO(buf);
}
}
return waited; /* 返回是否等待了 */
}
使用实践
1. 监控缓冲区活动
-- 查看缓冲区统计
SELECT
buffers_alloc,
buffers_checkpoint,
buffers_clean,
buffers_backend,
buffers_backend_fsync
FROM pg_stat_bgwriter;-- 查看共享缓冲区使用
SELECT
count(*) as total_buffers,
count(*) FILTER (WHERE state & 2 != 0) as dirty_buffers,
count(*) FILTER (WHERE state & 16 != 0) as io_in_progress
FROM pg_buffer_descriptors;
2. 调优参数
# 共享缓冲区大小
shared_buffers = 8GB# 后端写出阈值
backend_write_threshold = 0.25
# 检查点触发写出
checkpoint_completion_target = 0.9
# 异步 I/O(如果内核支持)
effective_io_concurrency = 200
# 同步 I/O 行为
synchronous_commit = on
3. 监控 AIO 使用
-- 查看异步 I/O 配置
SHOW effective_io_concurrency;-- 查看系统异步 I/O 能力
-- Linux: cat /proc/sys/fs/aio-max-nr
4. 性能诊断
4.1 高 I/O 等待
-- 查看正在等待 I/O 的查询
SELECT
pid,
state,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE state != 'idle'
AND wait_event_type = 'IO';
4.2 脏缓冲区积压
-- 查看脏缓冲区数量
SELECT
datname,
n_秧nserted,
n_dead_tup,
n_mod_since_analyze
FROM pg_stat_database;-- 查看检查点活动
SELECT * FROM pg_stat_checkpoints;
5. Linux io_uring 配置
# 检查 io_uring 支持
cat /proc/sys/kernel/osfaddr# 如果需要,调整限制
echo 1000000 > /proc/sys/fs/aio-max-nr
6. 常见问题处理
6.1 高缓冲区命中率
-- 如果命中率低,考虑增加 shared_buffers
SELECT
sum(blks_hit) as hits,
sum(blks_read) asreads,
sum(blks_hit) * 100.0 / NULLIF(sum(blks_hit) + sum(blks_read), 0) as hit_ratio
FROM pg_stat_database;
6.2 检查点 I/O 尖峰
# 使用更平滑的检查点
checkpoint_completion_target = 0.95# 增加检查点缓冲区
checkpoint_timeout = 15min
总结
异步 I/O 和缓冲区管理优化对 PostgreSQL 的 I/O 性能有显著影响。
关键要点:
UnlockReleaseBuffer:合并锁释放操作,减少开销 脏页写出:消除不必要的页面复制 io_uring:支持 Linux 原生异步 I/O 避免重复等待:I/O 已进行时不重复等待
对 DBA 的影响:
高 I/O 场景下性能提升 检查点期间更平滑的 I/O 更好的大规模数据操作性能 无需额外配置,自动生效