PostgreSQL ring bufferポリシー

9995 ワード

PostgreSQL ring bufferポリシー


When running a query that needs to access a large number of pages just once,such as VACUUM or a large sequential scan, a different strategy is used.A page that has been touched only by such a scan is unlikely to be needed again soon, so instead of running the normal clock sweep algorithm and blowing out the entire buffer cache, a small ring of buffers is allocated using the normal clock sweep algorithm and those buffers are reused for the whole scan. This also implies that much of the write traffic caused by such a statement will be done by the backend itself and not pushed off onto other processes.
src/backend/storage/buffer/READMEのring bufferの紹介を引用し、ring bufferは大量のアクセスページが必要な場合、vacuumや大量の全テーブルスキャンなどの場合に採用される特殊な戦略である.通常のクロックスキャンアルゴリズムのようにバッファ全体を交換するのではなく、小さなバッファでクロックスキャンアルゴリズムを使用し、バッファを再利用してスキャン全体を完了します.これにより、大量の全テーブルスキャンによるバッファヒット率の低下が回避されます.

一、ring bufferポリシーを使用するシーン


/src/backend/storage/buffer/freelist.c/GetAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
    BufferAccessStrategy strategy;
    int            ring_size;

    /*
     * Select ring size to use.  See buffer/README for rationales.
     *
     * Note: if you change the ring size for BAS_BULKREAD, see also
     * SYNC_SCAN_REPORT_INTERVAL in access/heap/syncscan.c.
     */
    switch (btype)
    {
        case BAS_NORMAL:
            /* if someone asks for NORMAL, just give 'em a "default" object */
            return NULL;

        case BAS_BULKREAD:
            ring_size = 256 * 1024 / BLCKSZ;
            break;
        case BAS_BULKWRITE:
            ring_size = 16 * 1024 * 1024 / BLCKSZ;
            break;
        case BAS_VACUUM:
            ring_size = 256 * 1024 / BLCKSZ;
            break;

        default:
            elog(ERROR, "unrecognized buffer access strategy: %d",
                 (int) btype);
            return NULL;        /* keep compiler quiet */
    }

    /* Make sure ring isn't an undue fraction of shared buffers */
    ring_size = Min(NBuffers / 8, ring_size);

    /* Allocate the object and initialize all elements to zeroes */
    strategy = (BufferAccessStrategy)
        palloc0(offsetof(BufferAccessStrategyData, buffers) +
                ring_size * sizeof(Buffer));

    /* Set fields that don't start out zero */
    strategy->btype = btype;
    strategy->ring_size = ring_size;

    return strategy;
}

1.BAS_BULKREAD


大量読み取りの場合、256 KBのメモリ領域が割り当てられますが、READMEではこのサイズの原因を説明しています.このサイズがL 2キャッシュに適しているため、OSが共有キャッシュにキャッシュされる伝送効率が高くなります.では、どのような状況で一括読み取りになるのでしょうか.
src/backend/access/heap/heapam.c/
if (!RelationUsesLocalBuffers(scan->rs_rd) &&
        scan->rs_nblocks > NBuffers / 4)
    {
        allow_strat = scan->rs_allow_strat;
        allow_sync = scan->rs_allow_sync;
    }
    else
        allow_strat = allow_sync = false;

    if (allow_strat)
    {
        /* During a rescan, keep the previous strategy object. */
        if (scan->rs_strategy == NULL)
            scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
    }

つまり、テーブルが一時テーブルでなく、スキャンされたブロック数がsharedより大きい場合bufferの1/4のブロック数は、BAS_を使用しますBULKREADポリシー.

2.BAS_BULKWRITE


一括書きの場合、16 MBのスペースが割り当てられます.小さいring bufferはwal flushを頻繁に行い、書き込みの効率を低下させるためです.一括書きの場合は、次のようになります.
  • COPY FROMコマンド
  • CREATE TABLE ASコマンド
  • CREATE MATERIZED VIEWまたはREFRESH MATERIZED VIEWコマンド
  • ALTER TABLEコマンド
  • 3.BAS_VACUUM


    vacuumでは256 KBのメモリ領域が使用され、順次スキャンされます.汚れたページがある場合は、このbufferを再利用するにはwalが書き込まれなければなりません.

    二、テスト


    pg_buffercacheはsharedを観察するのに役立ちますbufferで使用する場合

    1.BAS_BULKREADテスト

    # pg_buffercache 
    postgres=# create extension pg_buffercache ;
    CREATE EXTENSION
    
    # shared_buffer 
    postgres=# show shared_buffers ;
     shared_buffers
    ----------------
     128MB
    (1 row)
    
    # 
    postgres=# create table test as  select generate_series(1,1000000);
    SELECT 1000000
    
    # , shared_buffer 1/4
    postgres=# \dt+ test
                       List of relations
     Schema | Name | Type  |  Owner   | Size  | Description
    --------+------+-------+----------+-------+-------------
     public | test | table | postgres | 35 MB |
    (1 row)
    
    # , shared_buffer
    pg_ctl restart
    
    # 
    postgres=# set max_parallel_workers_per_gather =0;
    SET
    
    # shared_buffer test 
    postgres=# select * from pg_buffercache where relfilenode ='test'::regclass;
     bufferid | relfilenode | reltablespace | reldatabase | relforknumber | relblocknumber | isdirty | usagecount | pinning_backends
    ----------+-------------+---------------+-------------+---------------+----------------+---------+------------+------------------
    (0 rows)
    
    # 
    postgres=# select count(1) from test;
      count
    ---------
     1000000
    (1 row)
    
    # shared_buffer test 
    postgres=# select count(1) from pg_buffercache where relfilenode ='test'::regclass;
     count
    -------
        32
    (1 row)
    
     32 , 256KB(32*8) 

    2.BAS_BULKWRITE

    # 
    postgres=# copy test to '/home/postgres/test.csv' with csv;
    COPY 1000000
    
    # 
    pg_ctl restart
    
    # shared_buffer test 
    postgres=# select count(1) from pg_buffercache where relfilenode ='test'::regclass;
     count
    -------
         0
    (1 row)
    
    #copy from  test 
    postgres=# copy test from '/home/postgres/test.csv';
    COPY 1000000
    
    # shared_buffer test 
    postgres=# select count(1) from pg_buffercache where relfilenode='test'::regclass;
     count
    -------
      2051
    (1 row)
    
     16MB, 2048(16*1024/8), , 
    

    pg_buffercacheにrelforknumberというフィールドがあります.この定義は次のとおりです.
    /src/include/common/relpath.h
    typedef enum ForkNumber
    {
        InvalidForkNumber = -1,
        MAIN_FORKNUM = 0,
        FSM_FORKNUM,
        VISIBILITYMAP_FORKNUM,
        INIT_FORKNUM
    
        /*
         * NOTE: if you add a new fork, change MAX_FORKNUM and possibly
         * FORKNAMECHARS below, and update the forkNames array in
         * src/common/relpath.c
         */
    } ForkNumber;

    0はmainデータファイル、1はfsmファイル、2はvmファイルです.
    pgを調べてみますbuffercache
    postgres=# select relforknumber,count(1) from pg_buffercache where relfilenode='test'::regclass group by relforknumber;
     relforknumber | count
    ---------------+-------
                 0 |  2048
                 1 |     3
    (2 rows)

    データファイルbufferブロックは我々が予想した結果と一致し,複数の3つのブロックはfsmファイルにアクセスするブロックであることが分かった.
    postgres=# select relfilenode,relforknumber,relblocknumber,isdirty,usagecount,pinning_backends from pg_buffercache where relfilenode='test'::regclass and relforknumber!='0';
     relfilenode | relforknumber | relblocknumber | isdirty | usagecount | pinning_backends
    -------------+---------------+----------------+---------+------------+------------------
           16437 |             1 |              3 | f       |          5 |                0
           16437 |             1 |              0 | f       |          5 |                0
           16437 |             1 |              2 | f       |          5 |                0
    (3 rows)

    fsmへのアクセスの呼び出し関係は、CopyFrom->CopyFromInsertBatch->heap_multi_insert->RelationGetBufferForTuple->GetPageWithFreeSpace->fsm_search.bufferを挿入したブロック数=1000または行のサイズを合わせて64 KBを超えるとブラシ書き込みがトリガーされ、fsmファイルにアクセスして空きスペースを探し、fsmファイルにどのようにアクセスするかは、ここでは具体的に議論しません.
    src/backend/commands/copy.c/CopyFrom
    if (useHeapMultiInsert)
                    {
                        /* Add this tuple to the tuple buffer */
                        if (nBufferedTuples == 0)
                            firstBufferedLineNo = cstate->cur_lineno;
                        bufferedTuples[nBufferedTuples++] = tuple;
                        bufferedTuplesSize += tuple->t_len;
    
                        /*
                         * If the buffer filled up, flush it.  Also flush if the
                         * total size of all the tuples in the buffer becomes
                         * large, to avoid using large amounts of memory for the
                         * buffer when the tuples are exceptionally wide.
                         */
                        if (nBufferedTuples == MAX_BUFFERED_TUPLES ||
                            bufferedTuplesSize > 65535)
                        {
                            CopyFromInsertBatch(cstate, estate, mycid, hi_options,
                                                resultRelInfo, myslot, bistate,
                                                nBufferedTuples, bufferedTuples,
                                                firstBufferedLineNo);
                            nBufferedTuples = 0;
                            bufferedTuplesSize = 0;
                        }
                    }

    3.BAS_VACUUM

    # shared_buffer test 
    postgres=# select count(1) from pg_buffercache where relfilenode ='test'::regclass;
     count
    -------
         0
    (1 row)
    
    #vacuum 
    postgres=# vacuum test ;
    VACUUM
    postgres=# select count(1) from pg_buffercache where relfilenode ='test'::regclass;
     count
    -------
        37
    (1 row)
    

    予想結果は32で、結果は37で、原因を見てみましょう.
    postgres=# select relfilenode,relforknumber,relblocknumber,isdirty,usagecount,pinning_backends from pg_buffercache where relfilenode='test'::regclass and relforknumber!='0';
     relfilenode | relforknumber | relblocknumber | isdirty | usagecount | pinning_backends
    -------------+---------------+----------------+---------+------------+------------------
           16437 |             2 |              0 | t       |          2 |                0
           16437 |             1 |              2 | f       |          5 |                0
           16437 |             1 |              3 | t       |          5 |                0
           16437 |             1 |              0 | t       |          1 |                0
           16437 |             1 |              1 | t       |          1 |                0
    (5 rows)

    ここではfsmファイルの異なるブロックへのアクセスが4回,vmファイルへのアクセスが1回,5を減らすとちょうど32ブロックであることがわかる.