GUCパラメータの追加


2005.03.14 に行った、PostgreSQL しくみ分科会 第9回勉強会のメモです。

今回は、guc にパラメータを追加して、自分で追加した elog の出力を制御するという機能を追加しました。
このページでは、勉強会のときに実際に行った手順ではなく、最終的にこうすればよかったという内容で書かれています。


■ はじめに

GUCとは、Grand Unified Configuration の略で、PostgreSQLの動的に変更できるパラメータを管理するモジュールである。要するに、postgresql.conf ファイルやPGOPTIONS環境変数、SETコマンドなどで設定できるパラメータを管理しているモジュールである。

モジュールの機能としては、postgresql.conf ファイルやPGOPTIONS環境変数、SETコマンドなどの結果を、ソースコード中のグローバル変数に設定するという役割を果たす。

今回の勉強会では、このGUCに独自パラメータを追加するということを行った。これは、自分でPostgreSQLの機能拡張を行った際に、パラメータをつけるような場合や、デバッグのために何らかの埋め込みを行う場合に役に立つ。

■ 関連するソースコードを眺める

GUCのパラメータが定義してあるファイルは、src/backend/utils/misc/guc.c である。

◇ パラメータの種類

GUCパラメータの種類は、パラメータのとる型によって4種類(bool用、int用、real用、string用)が定義されている。

static struct config_bool ConfigureNamesBool[] =
static struct config_int ConfigureNamesInt[] =
static struct config_real ConfigureNamesReal[] =
static struct config_string ConfigureNamesString[] =

◇パラメータの定義

これらのそれぞれに、次のような定義が書かれている。

static struct config_bool ConfigureNamesBool[] =
{
    {
        {"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
            gettext_noop("Enables the planner's use of sequential-scan plans."),
            NULL
        },
        &enable_seqscan,
        true, NULL, NULL
    },
    {
        {"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
            gettext_noop("Enables the planner's use of index-scan plans."),
            NULL
        },
        &enable_indexscan,
        true, NULL, NULL
    },
    {
        {"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
            gettext_noop("Enables the planner's use of TID scan plans."),
            NULL
        },
        &enable_tidscan,
        true, NULL, NULL
    },

     :
     :

    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL
    }
};

先頭から、enable_seqscan、enable_indexscan、enable_tidscan の定義であることが想像できる。
また、特徴として、最後にNULLだけで定義された、終端を示す End-of-list マーカーがおいてあることが挙げられる。
したがって、新規にパラメータを追加する場合、このEnd-of-list マーカーより前に定義を書く必要がある。

◇ データ構造

さて、構造体をもう少し詳しく見ていこう。bool型の定義である struct config_bool は次のようになっている。
構造体が定義されているのは、include/utils/guc_tables.h である。

struct config_bool
{
    struct config_generic gen;
    /* these fields must be set correctly in initial value: */
    /* (all but reset_val are constants) */
    bool       *variable;
    bool        reset_val;
    GucBoolAssignHook assign_hook;
    GucShowHook show_hook;
    /* variable fields, initialized at runtime: */
    bool        tentative_val;
};

先頭に、各型共通の構造体struct config_genericがあり、その後ろに型ごとに必要な属性が用意されている。先に、boolの場合の固有のところを説明する。

variable に、GUCのパラメータを設定すべきグローバル変数のアドレスを入れる。次のreset_val は初期値である。次のassign_hook、show_hookは、この値を設定するときと参照する時に使用hookではないかと思うが、ほとんどのパラメータはNULLになっているので、調査は省略する。また、最後のtentative_val は、コメントにあるように実行時に初期化される値なので、今回は説明を割愛する。

共通の定義である struct config_generic は次のようになっている。 

struct config_generic
{
    /* constant fields, must be set correctly in initial value: */
    const char *name;           /* パラメータの名前 */
    GucContext  context;        /* context required to set the variable */
    enum config_group group;    /* to help organize variables by function */
    const char *short_desc;     /* short desc. of this variable's purpose */
    const char *long_desc;      /* long desc. of this variable's purpose */
    int         flags;          /* flag bits, see below */
    /* variable fields, initialized at runtime: */
    enum config_type vartype;   /* type of variable (set only at startup) */
    int         status;         /* status bits, see below */
    GucSource   reset_source;   /* source of the reset_value */
    GucSource   tentative_source;       /* source of the tentative_value */
    GucSource   source;         /* source of the current actual value */
    GucStack   *stack;          /* stacked outside-of-transaction states */
};

先頭から順に、パラメータの名前 name、GUCのコンテキスト context、GUCのグループ group、このパラメータの短い説明 short_desc、このパラメータの長い説明 long_desc、bitフラグ flags などが並んでいる。これ以降は、コメントにあるように実行時に設定されるものなので、今回は説明は割愛する。

パラメータ名は、説明するまでもなく、postgresql.conf やSETコマンドで指定するときの名前である。

次にGUCコンテキストである。これは、GUCのパラメータがどのタイミングで変更可能であるかを示している。GucContext の定義は次のようになっている。

typedef enum
{
    PGC_INTERNAL,      // 実行中に変更不可
    PGC_POSTMASTER,    // postmaster 起動時のみ変更される
    PGC_SIGHUP,        // postmaster 起動時+postmasterがSIGHUPシグナルを受け取ったときに変更可能
    PGC_BACKEND,       // バックエンド起動時に変更される。つまり、PGOPTIONS環境変数を設定して
                       // 設定変更が行える。(postmasterがSIGHUPシグナルを受け取った時は変更されない)
    PGC_SUSET,         // DB管理アカウントのみSETコマンドで変更可能
    PGC_USERSET        // PGOPTIONS環境変数、SETコマンドで変更可能
} GucContext;

次に、group であるが、定義はしてあるようだが、実際に使っているかどうかはよくわからない。動作にもあまり影響がないようなので、今回は特に調べなかった。興味があれは、enum config_groupを見てほしい。

次のshort と long の説明である。実質的には、これらがこのファイルでのコメントの役割を果たしている。プログラム自体では使っていない(?)ようなので、NULLにして省略することもできる。

次の、flags はよくわからなかったが、今回追加しようとしているbool型では、他のパラメータでも定義されていなかったので特に調べなかった。

 

先ほどの enable_seqscan を例に挙げて、設定と比較してみよう。コメントを追加するために改行位置を変えてある。

    {
        {   "enable_seqscan",     <-- パラメータの名前
            PGC_USERSET,          <-- GucContext
            QUERY_TUNING_METHOD,  <-- group
            gettext_noop("Enables the planner's use of sequential-scan plans."),    <-- 短い説明
            NULL                  <-- 長い説明
                                  <-- flags 以降の設定は省略してある
        },
        &enable_seqscan,          <-- パラメータの値を設定するグローバル変数のアドレス
        true,                     <-- 初期値
        NULL,                     <-- GucBoolAssignHook
        NULL                      <-- GucShowHook
    },

■ パラメータ追加

では、実際にパラメータを追加して見る。
※ 勉強会では、パラメータ名やグローバル変数を shikumi で行っていたが、これだと前回の構文追加と衝突して不具合が出たので、このメモでは shikumi_guc と記述する。

◇ パラメータの追記

まずは、guc.c に新しいパラメータのエントリを追加する。追加場所は、static struct config_bool ConfigureNamesBool[] の一番最後(End-of-list マーカーの直前)とした。以下のように追加した。

      :
      :
    {
        {"integer_datetimes", PGC_INTERNAL, PRESET_OPTIONS,
            gettext_noop("Datetimes are integer based"),
            NULL,
            GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
        },
        &integer_datetimes,
#ifdef HAVE_INT64_TIMESTAMP
        true, NULL, NULL
#else
        false, NULL, NULL
#endif
    },

    {
        {"shikumi_guc",
            PGC_USERSET,
            UNGROUPED,
            gettext_noop("shikumi_guc desc."),
            NULL
        },
        &shikumi_guc,
        true, NULL, NULL
    },

    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL
    }
};

まず、パラメータ名は"shikumi_guc"とした。次にGUCコンテキストだが、SETコマンドで変更が可能になるようにPGC_USERSETを選択した。グループは、何でもよさそうだったので、UNGROUPEDを選択した。短い説明は上記のように適当に書き、長い説明はNULLにした。

bool型固有のところであるが、グローバル変数は shikumi_guc のアドレスを指定し、GucBoolAssignHook、GucShowHookは他のところ同様にNULLにした。坂田氏の説明では、bool型の設定と表示は準備されているので特に要らないらしいとのことであった。

グローバル変数は、後でpostmaster.c に追加して、postmaster.h でextern 宣言する予定である。guc.cは、postmaster.hを #include しているので、変数が未定義になる心配はない。もし、#include されていないファイルに extern 宣言する場合は、そのファイルを #include しておくか、guc.cに直接 extern 宣言する必要がある。

◇ パラメータを使う場所の変更

次に実際にguc.c に追加したパラメータ shikumi_guc を使う場所を変更する。

まず、初期値を確認するために、postmaster のServerLoop() に入ってすぐのところに if 文を使って elog() を配置した。

src/backend/postmaster/postmaster.c に次のように追加。

/*
 * Main idle loop of postmaster
 */
static int
ServerLoop(void)
{
    fd_set      readmask;
    int         nSockets;
    time_t      now,
                last_touch_time;
    struct timeval earlier,
                later;
    struct timezone tz;

    gettimeofday(&earlier, &tz);
    last_touch_time = time(NULL);

    nSockets = initMasks(&readmask);

    if (shikumi_guc)
    { 
        elog(LOG, "#IK shikumi_guc is TRUE");
    }
    else
    {
        elog(LOG, "#IK shikumi_guc is FALSE");
    }

    for (;;)
    {
        Port       *port;
        fd_set      rmask;

グローバル変数を定義するために、同じくpostmaster.c の先頭の方に shikumi_guc の定義を追加した。

#include "bootstrap/bootstrap.h"
#include "pgstat.h"

#ifdef EXEC_BACKEND
#include "storage/spin.h"
#endif

bool        shikumi_guc = true;

続いて、このグローバル変数を guc.c で使えるようにするために、include/postmaster/postmaster.h に extern宣言を追加する。
※ 7.4 までは、postmaster.h ファイルがないようなので、include/postgres.h の最後のほうにでも追加しておく。

#ifndef _POSTMASTER_H
#define _POSTMASTER_H

/* GUC options */
extern bool shikumi_guc;

extern bool EnableSSL;
extern bool SilentMode;

PostgreSQL 7.4の場合 (include/postgres.h)

#define DESCR(x)  extern int no_such_variable

typedef int4 aclitem;           /* PHONY definition for catalog use only */

extern bool shikumi_guc;

#endif   /* POSTGRES_H */

 

ServerLoop() の先頭だけでは、SETコマンドで値を変更した場合に変化が確認できないので、バックエンド側にも表示する場所を追加した。場所は、src/backend/tcop/postgres.c の PostgresMain() のループの最後である。また、先ほどは、elogのLOGで表示させたが、LOGだとサーバ側に表示されるだけで面白くないので、クライアント側にもメッセージが表示されるようにelogのNOTICEを使うようにした。

        }
        if (shikumi_guc)
       { 
           elog(NOTICE, "#IK shikumi_guc is TRUE");
       }
       else
       {
           elog(NOTICE, "#IK shikumi_guc is FALSE");
       }

    }                           /* end of input-reading loop */

    /* can't get here because the above loop never exits */
    Assert(false);

    return 1;                   /* keep compiler quiet */
}

PostgreSQL 8.0 で、extern 宣言をpostmaster.h で行った場合、tcop/postgres.c でグローバル変数 shikumi_guc が定義されていないと怒られるので、extern 宣言を追加しておく。本当は #include で追加すべきかもしれないが、GUCの1変数のためなので、今回はpostgres.cに直接extern宣言を追加した。ちなみに、7.4のように postgres.h の方にextern宣言を追加していた場合は、この処理は不要である。

extern int  optind;
extern char *optarg;

extern bool shikumi_guc;

/* ----------------
 *      global variables
 * ----------------
 */

以上で、今回の変更は終了である。

あとは、通常通りコンパイルして実行するだけである。

■ 実行結果

ここでは、前述の改造結果の実行例を示す。

postmaster起動時に、以下のように「LOG: #IK shikumi_guc is TRUE」を出力していることが確認できるはずである。

frisk(7)% pg_ctl start
postmaster starting
LOG:  #IK shikumi_guc is TRUE
LOG:  database system was shut down at 2005-03-14 22:01:13 JST
LOG:  checkpoint record is at 0/A2C9AC
LOG:  redo record is at 0/A2C9AC; undo record is at 0/0; shutdown TRUE
LOG:  next transaction ID: 553; next OID: 17230
LOG:  database system is ready

次に、一度postmasterを停止した後、$PGDATA/postgresql.conf の適当な場所に次の行を追加して、起動しなおしてみよう。

shikumi = false

起動時のメッセージは、次のようにFALSE になるはずである。

frisk(12)% pg_ctl start
postmaster starting
LOG:  #IK shikumi_guc is FALSE
LOG:  database system was shut down at 2005-03-16 16:01:14 JST
LOG:  checkpoint record is at 0/A2CA24
LOG:  redo record is at 0/A2CA24; undo record is at 0/0; shutdown TRUE
LOG:  next transaction ID: 553; next OID: 17230
LOG:  database system is ready

次は、別画面からpsql でアクセスしてみよう。次のようにSETコマンドで、shikumi_guc のオンオフができるはずである。
※ 斜体のところが手入力するところである。

frisk(14)% psql template1
Welcome to psql 8.0.1, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

template1=# set shikumi_guc = true;
NOTICE:  #IK shikumi_guc is TRUE
SET
template1=# set shikumi_guc = false;
NOTICE:  #IK shikumi_guc is FALSE
SET
template1=# set shikumi_guc = true;
NOTICE:  #IK shikumi_guc is TRUE
SET
template1=#

以上で、GUCの機能追加の動作確認は終了である。

◇ おまけ

ちなみに、GUCコンテキストを、PGC_INTERNAL にして実行すると、設定が変更できないと言ってエラーになる。

$PGDATA/postgresql.conf に shikumi_guc に関する記述があると次のようにFATALエラーになってpostmasterは起動できない。

frisk(14)% pg_ctl start
postmaster starting
FATAL:  parameter "shikumi_guc" cannot be changed

また、postgresql.conf に shikumi_guc に関する記述がなければ、postmasterは通常どおり起動できる。しかし、SETコマンドは、次のようにエラーになる。

frisk(19)% psql template1
Welcome to psql 8.0.1, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

template1=# set shikumi_guc = false;
ERROR:  parameter "shikumi_guc" cannot be changed
template1=#

2007.05.29 更新

2005.3.16 井久保