二探PgSQL18的异步IO
本文介绍PgSQL18中使用worker的方式进行异步IO。主要通过PgAioWorkerSubmissionQueue队列管理异步IO请求。用户服务进程生产异步IO请求,将其放到上面的队列中,io_worker进程从队列中取异步IO请求进行消费。
1、计算可以预读的连续页数
上篇博文已介绍。
2、将IO分批次
PgAioCtl* pgaio_ctl作为异步IO全局管理器,管理所有的异步IO请求,每个异步IO请求对应一个io_handles[]。尽管PgAioHandle* io_handles管理的一次异步IO请求处理的IO页都是连续的,但加载内存页对应的共享内存也需要连续。由于申请pin住的共享内存页内存并不是连续,所以需要将IO请求进一步拆分,以连续的内存页为单位,将其拆分成iovecs[i],每个iovecs[i]管理内存块起使地址及连续后的大小。
1)io_handles[i]的op标记本次请求是读请求还是写请求;
2)iovec_off作为本次io所属iovecs[]的起使槽位(一个IO中的iovecs[]槽位是连续申请的)。
3)PgAioOpData op_data对应本次IO读文件的元数据:以读为例,fd为文件句柄,iov_length为iovecs[]使用的槽位个数,offset为fd文件的偏移位置开始读。读的时候取到对应的iovecs[],从而得到iov_len读取长度。
4)op_data中offset读取偏移位置是由PgAioTargetData中的blockNum(本次IO的起使页号)计算得到的,如下图所示:

函数调用说明:
read_stream_start_pending_read->StartReadBuffers->StartReadBuffersImpl->AsyncReadBuffers->smgrstartreadv->mdstartreadv->FileStartReadV->pgaio_io_start_readv->pgaio_io_stage-> pgaio_my_backend->staged_ios[pgaio_my_backend->num_staged_ios++] = ioh;1) AsyncReadBuffers函数中申请一个ioh句柄,在mdstartreadv函数中得到iovecs[]的起使槽iov,通过iovcnt = buffers_to_iovec(iov, buffers, nblocks_this_segment);将IO拆分成iovcnt个,并计算出读的起使偏移seekpos,然后调用下一步的函数进行填充
2) pgaio_io_start_readv填充ioh异步IO句柄的fd、offset和iovecs[]的槽个数(下一小阶介绍)
3) 由pgaio_io_stage将异步IO句柄放到staged_ios[]中
注:在AioShmemInit中可见每个ioh句柄对应io_max_combine_limit个iovecs[]槽,他的起使槽位是固定的。
该IO句柄怎么提交到异步队列呢?下文继续。
3、IOMETHOD_WORKER异步IO的管理结构

io_worker_control的idle_worker_mask可以认为是一个bitmap,标记workers[]已使用的所有槽,也就是将异步IO请求放到对应的槽位上(对应到异步IO worker进程)处理异步IO。
4、IOMETHOD_WORKER异步IO队列

PgAioBackend从pgaio_ctl->backend_state[MyProcNumber]取,作为本进程的异步IO请求的临时存放队列。异步IO请求,也就是io句柄PgAioHandle需要先放到这里,等及攒够32个后(尽量多的积攒)就会在pgaio_io_acquire_nb函数中调用pgaio_submit_staged提交。worker的异步IO方式下使用pgaio_worker_submit函数进行提交:

上述代码中可知,在队列锁内,将staged_ios[]数组记录积攒的异步io句柄的序号放到提交队列中。如果队列放满了,还有剩下异步IO请求,则将剩余的调用pgaio_io_perform_synchronously函数由本进程同步提交掉。至于放到提交队列的IO请求,则由pgaio_worker_choose_idle选择一个空闲的异步io worker进程,通过SetLatch该进程的latch通知对应io worker进程唤醒。通过kill命令向io worker进程发送SIGURG信号唤醒。

5、io worker进程的处理

该进程在WaitLatch处被唤醒后,在队列锁内取出一个io句柄进行同步IO的执行;可以看到它还会选择其他空闲的io worker进程将其唤醒。也就是做到多个IO worker并发消耗提交队列的IO请求进行IO。




