基本信息

    利用下面的语句可以创建此插件:

    实际上,创建插件的过程只是用下面的语句创建了pg_prewarm函数。这个函数是此插件提供的唯一函数:

    1. mode text default 'buffer',
    2. fork text default 'main',
    3. first_block int8 default null,
    4. last_block int8 default null)
    5. RETURNS int8
    6. AS 'MODULE_PATHNAME', 'pg_prewarm'
    7. LANGUAGE C

    函数的第一个参数是要做prewarm的表名,第二个参数是prewarm的模式(prefetch模式表示异步预取到操作系统cache;read表示同步预取;buffer则表示同步读入到PG的shared buffer),第三个参数是relation fork的类型(一般用main,其他类型有visibilitymap和fsm,参见[2]),最后两个参数是开始和结束的block number(一个表的block number从0开始,block总数可以通过pg_class系统表的relpages字段获得)。

    性能实测

    再来看看,这个prewarm性能上能达到多大效果。我们先将PG的shared buffer设为2G,OS总的memory有7G。然后创建下面的大小近1G的表test:

    1. pgbench=# \d test
    2. Table "public.test"
    3. Column | Type | Modifiers
    4. --------+---------------+-----------
    5. name | character(20) |
    1. pgbench=# SELECT pg_size_pretty(pg_total_relation_size('test'));
    2. pg_size_pretty
    3. 995 MB

    1)不进行pg_prewarm的情况:

    可以看到,近1G的表,全表扫描一遍,耗时22秒多。

    2)下面我们先做read这种模式的prewarm,test表的数据被同步读入操作系统cache(pg_prewarm返回的是处理的block数目,此处我们没指定block number,也就是读入test的所有block),然后再做全表扫:

    1. pgbench=# select pg_prewarm('test', 'read', 'main');
    2. pg_prewarm
    3. ------------
    4. 127389
    1. pgbench=# explain analyze select count(*) from test;
    2. QUERY PLAN
    3. --------------------------------------------------------------------------------------------------------------------------
    4. Aggregate (cost=377389.90..377389.91 rows=1 width=0) (actual time=8577.767..8577.767 rows=1 loops=1)
    5. -> Seq Scan on test (cost=0.00..327389.72 rows=20000072 width=0) (actual time=0.086..4716.444 rows=20000002 loops=1)
    6. Planning time: 0.049 ms
    7. Execution time: 8577.831 ms

    时间降至8秒多!这时反复执行全表扫描,时间稳定在8秒多。

    3)再尝试buffer模式:

    1. pgbench=# select pg_prewarm('test', 'buffer', 'main');
    2. pg_prewarm
    3. 127389

    比read模式时间略少,但相差不大。可见,如果操作系统的cache够大,数据取到OS cache还是shared buffer对执行时间影响不大(在不考虑其他应用影响PG的情况下)。

    1. explain analyze select pg_prewarm('test', 'prefetch', 'main');
    2. QUERY PLAN
    3. ------------------------------------------------------------------------------------------
    4. Result (cost=0.00..0.01 rows=1 width=0) (actual time=1011.338..1011.339 rows=1 loops=1)
    5. Planning time: 0.124 ms
    6. Execution time: 1011.402 ms
    1. explain analyze select count(*) from test;
    2. QUERY PLAN
    3. --------------------------------------------------------------------------------------------------------------------------
    4. Aggregate (cost=377389.90..377389.91 rows=1 width=0) (actual time=8420.652..8420.652 rows=1 loops=1)
    5. -> Seq Scan on test (cost=0.00..327389.72 rows=20000072 width=0) (actual time=0.065..4583.200 rows=20000002 loops=1)
    6. Planning time: 0.344 ms
    7. Execution time: 8420.723 ms

    可以看到,总的完成时间是9秒多,使用pg_prewarm做预取大大缩短了总时间。因此在进行全表扫描前,做一次异步的prewarm,不失为一种优化全表查询的方法。

    实现

    pg_prewarm的代码只有一个pg_prewarm.c文件。可以看出,prefetch模式下,对于表的每个block,调用一次PrefetchBuffer,后面的调用为:

      可见,它是最终调用posix_fadvise,把读请求交给操作系统,然后返回,实现的异步读取。

      而在read和buffer模式(调用逻辑分别如下)中,最终都调用了系统调用read,来实现同步读入OS cache和shared buffer的(注意buffer模式实际上是先读入OS cache,再拷贝到shared buffer):

      1. buffer模式:ReadBufferExtended -> ReadBuffer_common -> smgrread -> mdread -> FileRead -> read

      问题