この記事は PostgreSQL 9.5 に基づいて記述している。
このページでは PostgreSQL のエクステンション(extension)を開発する人向けに、バックグラウンドワーカー(Background Worker)の使い方を解説する。
PostgreSQL の他の記事へのインデックスはここ。
更新履歴
(2017.04.02) 作成。
(2017.04.05) 図を追加。
目次
1. はじめに
PostgreSQL はインスタンスをマルチプロセス構成で管理しており、個々のプロセスはシングルスレッド動作している。
- DBMS デーモンプロセス 通称 postmaster プロセス は、最初に生成されるプロセスで他のプロセスを管理する。TCP ポートを listen し、必要に応じてバックエンドプロセスを生成する役目も負う。 PostgreSQL のテーブルとブロックのデータ構造の 1.1節 も参照のこと。
- バックエンドプロセス(Backend Process) は、データベースクラスタへ接続するコネクション毎に生成され、セッションで行われるコマンドの処理を実施する。
PostgreSQL が自分のエクステンションの中で並列処理(parallel processing)または並行処理(concurrent processing)を行いたい場合、別のプロセスを立てて実行を行うことになる。 このために PostgreSQL が提供している仕組みが バックグラウンドワーカー(Background Worker)である。
PostgreSQL をマルチスレッド動作させることは非常に難しい。 いろいろな箇所でフラグ変数をグローバル変数として公開し、これをフックしたり一時的にトリガーにしている箇所があるため、非同期シグナルの類が動作しない。 またメモリ管理関数もマルチスレッドに対応していない。
バックグラウンドワーカーには 2 つの種類が存在する。
1つ目は無印のバックグラウンドワーカーである。 PostgreSQL のインスタンスを起動時に作成され、インスタンスが存在する間に常駐する。 このバックグラウンドワーカーは postmaster からのみ「登録」ができる。 無印のバックグラウンドワーカーは PostgreSQL 9.3 から利用できる。
2つ目は動的バックグラウンドワーカー(Dynamic Background Worker)である。 動的バックグラウンドワーカーは postmaster 以外から「生成」することができる。 これはユーザー定義関数(User Defined Function; UDF)の中で動的バックグラウンドワーカーを生成し、処理を行ってから、動的バックグラウンドワーカーを終了させることも可能である。 動的バックグラウンドワーカーは PostgreSQL 9.4 から利用できる。
説明が混乱するので、この文書では無印のバックグラウンドワーカーの方を静的バックグラウンドワーカーと呼ぶことにする。 一般的に使われる言葉ではない。
2. 静的バックグラウンドワーカー
2.1 静的バックグラウンドワーカーの登録
静的バックグラウンドワーカーは、postmaster プロセス内で RegisterBackgroundWorker() を使うことで「登録」ができる。
RegisterBackgroundWorker() は以下の形式をとる。
void RegisterBackgroundWorker(BackgroundWorker *worker);
第1引数には登録するバックグラウンドワーカーの情報を BackgroundWorker 構造体で渡す。 BackgroundWorker 構造体のメンバー変数は 表1 のように指定する。 ただし指定していないメンバー変数はゼロパディングしておくこと。
RegisterBackgroundWorker() の引数に渡す BackgroundWorker 構造体はローカル変数でもよい。
その内容はバックグラウンドワーカーのプロセスにコピーされ、グローバル変数の MyBgworkerEntry グローバル変数から参照できるようになる。
RegisterBackgroundWorker() は引数が void 型だが、「登録」に失敗することはある(例えば GUC パラメータの max_worker_process が不足した場合に)。
この場合はエラーが発生する。
RegisterBackgroundWorker() で「登録」したバックグラウンドワーカーは BackgroundWorker 構造体の bgw_start_time の指定に従い起動する。
起動したバックグラウンドワーカーは RegisterBackgroundWorker() とは無関係で動作を開始する。
ユーザーが RegisterBackgroundWorker() を使って静的バックグラウンドワーカーを登録するには、postgresql.conf の shared_preload_libraries の中に起動時にロードするモジュール名を指定し、その _PG_init() 中で呼び出す。
例えばモジュール名が shm_test.so なら shared_preload_libraries = 'shm_test' と入力する。
shared_preload_libraries = 'shm_test'
void
_PG_init(void)
{
BackgroundWorker worker;
/* shared_preload_libraries 以外からロードされた場合には何もしない */
if (!process_shared_preload_libraries_in_progress)
return;
/* 予めゼロパディングする */
memset(&worker, 0, sizeof(worker));
/* worker に値を設定する */
RegisterBackgroundWorker(&worker);
}
2.2 BackgroundWorker 構造体
BackgroundWorker 構造体は以下のようになる。
| メンバー名 | データ型 | 説明 |
|---|---|---|
| bgw_name | char [BGW_MAXLEN(64)] | バックグラウンドワーカーの名前。 これはデバッグ情報などにのみ利用されるので適当につけてよい。 |
| bgw_flags | int |
バックグラウンドワーカーに与える機能を以下2つのマクロの論理和で与える。
|
| bgw_start_time | BgWorkerStartTime |
バックエンドプロセスが起動するタイミングを BgWorkerStartTime 列挙子のうち1つで指定する。
Postmaster はその起動シーケンスとして、以下のようなフェーズを持っている。
|
| bgw_restart_time | int | バックグラウンドワーカーがエラーで終了した後に、再起動するまでに待機時間を秒数を指定する。 bgw_restart_time は BGW_NEVER_RESTART(0) を指定した場合、エラー終了した後にバックグラウンドワーカーが再起動しなくなる。 |
| bgw_main | bgworker_main_type |
バックグラウンドワーカーの処理の実体となるコールバック関数。
typedef void (*bgworker_main_type) (Datum main_arg); |
| bgw_library_name | char [BGW_MAXLEN(64)] | バックグラウンドワーカーの処理の実体となるコールバック関数の共有メモリライブラリを指定する。 bgw_main が NULL の場合のみ利用される。 |
| bgw_function_name | char [BGW_MAXLEN(64)] | バックグラウンドワーカーの処理の実体となるコールバック関数の関数名を指定する。 bgw_main が NULL の場合のみ利用される。 |
| bgw_main_arg | Datum |
バックグラウンドワーカーのコールバック関数の第1引数に渡す。 この引数にポインタを渡したい場合、PostgreSQL のメモリ管理関数の解説の3章で述べたような共有メモリから渡す必要がある。 |
| bgw_extra | char [BGW_EXTRALEN(128)] |
バックグラウンドワーカー側に渡す余分な情報を渡す。 バックグラウンドワーカー・プロセス側からは MyBgworkerEntry->bgw_extra でアクセスできる。
|
| bgw_notify_pid | pid_t |
静的バックグラウンドワーカーでは 0 を指定する必要がある。
0 以外を指定すると RegisterBackgroundWorker() はエラーとなる。これは動的バックグラウンドワーカーで利用する。 |
コールバック関数は bgw_main と bgw_library_name & bgw_function_name のどちらか一方を指定する。
- bgw_main は
RegisterBackgroundWorker()を呼び出すのと同じ共有ライブラリに含まれる関数をコールバック関数とする場合にのみ使える。 - bgw_library_name & bgw_function_name は別の共有ライブラリに含まれる関数を指定する。 共有ライブラリがロードされ、その中の関数を呼び出す。 そのため bgw_function_name で指定する関数には PGDLLIMPORT を付ける必要がある。
2.3 コールバック関数
静的バックグラウンドワーカーのコールバック関数は、典型的に以下のように記述する。
void
foo_main(Datum main_arg)
{
pqsignal(SIGHUP, sighup_handler);
pqsignal(SIGTERM, sigterm_handler);
pqsignal(SIGQUIT, sigterm_handler);
pqsignal(SIGINT, sigterm_handler);
BackgroundWorkerUnblockSignals();
/*
* SIGUSR1 シグナルの標準ハンドラである procsignal_sigusr1_handler() は
* set_latch_on_sigusr1 が true の場合 SIGUSR1 を捕捉すると SetLatch() を呼ぶ。
*/
set_latch_on_sigusr1 = true;
/* データベースへ接続する */
#if 1
BackgroundWorkerInitializeConnection("dbname", "username");
#else
BackgroundWorkerInitializeConnectionByOid(dboid, useroid);
#endif
while (!gotSigterm)
{
int rc;
/*
* 非同期割り込みをチェック
* 場合によってはここでエラーが発生しプロセスは終了する。
*/
CHECK_FOR_INTERRUPTS();
/* 状態が変化するまで待機 */
rc = WaitLatch(&MyProc->procLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
100 /* msec */);
ResetLatch(&MyProc->procLatch);
/* postmaster が終了した場合は終了する */
if (rc & WL_POSTMASTER_DEATH)
proc_exit(1);
if (gotSigterm)
goto done;
/* SIGHUP が来た場合 postgresql.conf をリロードする */
if (gotSighup)
{
gotSighup = false;
ProcessConfigFile(PGC_SIGHUP);
}
/*
* ここに処理コードを書く。
* 必要なら処理の切れ目で CHECK_FOR_INTERRUPTS() を呼ぶ。
*/
}
done:
/* code=1 を指定するのはリスタート */
proc_exit(1);
}
static void
sigterm_handler(SIGNAL_ARGS)
{
int save_errno = errno;
gotSigterm = true;
if (MyProc)
SetLatch(&MyProc->procLatch);
errno = save_errno;
}
static void
sighup_handler(SIGNAL_ARGS)
{
int save_errno = errno;
gotSighup = true;
if (MyProc)
SetLatch(&MyProc->procLatch);
errno = save_errno;
}
3. 動的バックグラウンドワーカー
3.1 動的バックグラウンドワーカーの登録
動的バックグラウンドワーカーは RegisterDynamicBackgroundWorker() を使うことで「登録」ができる。
RegisterDynamicBackgroundWorker() は以下の形式をとる。
静的バックグラウンドワーカーを登録する RegisterBackgroundWorker() は postmaster プロセス内でしか呼びなかったが、RegisterDynamicBackgroundWorker() は逆に postmaster プロセスでは呼び出せない。
バックエンドプロセス、静的バックグラウンドワーカー、他の動的バックグラウンドワーカーから呼び出すのは OK である。
bool RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle);
RegisterDynamicBackgroundWorker()によるが登録が成功した場合は true を返し handle に BackgroundWorkerHandle 型のポインタが格納される。 登録が失敗した場合は false が返る。RegisterBackgroundWorker()の戻り値が void 型で登録の失敗を検出できなかったのと対象的である。- postmaster プロセスが呼び出した場合は失敗し false が返る。
- 動的バックグラウンドワーカーは、静的バックグラウンドワーカーと合わせて max_worker_processes 以下しか作れない。 これを超過した時に失敗し false が返る。
- 動的バックグラウンドワーカーの設定は
RegisterBackgroundWorker()と同じく BackgroundWorker 構造体(表1)で渡す。 ただし以下の点が異なる。- bgw_start_time と bgw_restart_time は無効である。
- bgw_notify_pid が有効である。 bgw_notify_pid の用途と効果は 3.4 節で説明する。
- handle に渡される BackgroundWorkerHandle 型のポインタはハンドルとして利用する。
これは内部的に
palloc()されたメモリで、動的バックグラウンドワーカーの利用が終わったらpfree()する必要がある。
3.2 動的バックグラウンドワーカーの起動の確認
RegisterDynamicBackgroundWorker() で登録された動的バックグラウンドワーカーは postmaster によってプロセスとして生成される。
その進行状態を RegisterDynamicBackgroundWorker() を呼び出したプロセス側で確認する方法が提供されている。
GetBackgroundWorkerPid() は RegisterDynamicBackgroundWorker() で渡されたハンドルを使って、動的バックグラウンドワーカーの状態を返す。
この関数は即時状態を返却する。
BgwHandleStatus GetBackgroundWorkerPid(BackgroundWorkerHandle *handle, pid_t *pid);
- BGWH_STARTED。 動的バックグラウンドワーカーが起動済みである。 pid に動的バックグラウンドワーカーの PID も返る。
- BGWH_NOT_YET_STARTED。 動的バックグラウンドワーカーが起動中である。
- BGWH_STOPPED。 動的バックグラウンドワーカーが停止した。
- BGWH_POSTMASTER_DIED。 postmaster が終了することにより動的バックグラウンドワーカーが停止した。
WaitForBackgroundWorkerStartup() は RegisterDynamicBackgroundWorker() で渡されたハンドルを使って、動的バックグラウンドワーカーが
起動済み状態(BGWH_STARTED)になるか、停止状態(BGWH_STOPPED か BGWH_POSTMASTER_DIED)になるまで待機する。
BGWH_STARTED が返る場合は、pid に動的バックグラウンドワーカーの PID も設定される。
BgwHandleStatus WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pid);
3.3 動的バックグラウンドワーカーの終了
TerminateBackgroundWorker() に動的バックグラウンドワーカーのハンドラを指定することで外部から停止を指示する。
ただしこの関数を呼び出しても SIGUSR1 シグナルが送られるだけで、停止を保証しない。
この関数自体は即時復帰する。
void TerminateBackgroundWorker(BackgroundWorkerHandle *handle);
WaitForBackgroundWorkerStartup() のように動的バックグラウンドワーカーの終了を待機する WaitForBackgroundWorkerShutdown() も存在する。
BgwHandleStatus WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle);
3.4 bgw_notify_pid
PostgreSQL はバックグラウンドワーカー・プロセスもバックエンドプロセス・プロセスも postmaster プロセスの子プロセスである。
しかし通常、RegisterDynamicBackgroundWorker() を呼んだプロセスと動的バックグラウンドワーカーの間には、プロセスの親子関係とは別に、特別な呼び出し関係が期待される。
これを bgw_notify_pid メンバー変数を使うことで指定する。
現在のプロセスの PID は MyProcPid で取得できるので、以下のように起動する。
BackgroundWorker worker;
BackgroundWorkerHandle *handle;
/* ... */
worker.bgw_notify_pid = MyProcPid;
if (!RegisterDynamicBackgroundWorker(&worker, &handle))
ereport(PANIC,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("could not register dynamic background process"),
errhint("You may need to increase max_worker_processes.")));
以降、bgw_notify_pid が指すプロセスを呼び出し元プロセスと呼ぶことにする。
以降、動的バックグラウンドワーカーは内部に持っているステータス(3.2 節)の変化が起きると、bgw_notify_pid のプロセスに SIGUSR1 シグナルを送信する。 この機構を使って呼び出し元プロセスや動的バックグラウンドワーカーの終了を捕捉する。

動的バックグラウンドワーカーが終了
動的バックグラウンドワーカーが終了した場合、呼び出し元プロセスに SIGUSR1 が送信される。
SIGUSR1 到着後に自分が作成した動的バックグラウンドワーカーを GetBackgroundWorkerPid() でチェックし、回収処置をとることができる。
呼び出し元プロセスが終了した場合
呼び出し元プロセスが終了した場合でも、動的バックグラウンドワーカーはすぐには終了しない。
ただし呼び出し元プロセスの終了を(親プロセスである) postmaster プロセスが検知すると、動的バックグラウンドワーカーの MyBgworkerEntry->bgw_notify_pid は 0 に上書きされる。
そのため動的バックグラウンドワーカー内で自身の MyBgworkerEntry->bgw_notify_pid を時々確認することで、呼び出し元プロセスの生存または終了を検知できる。
3.5 コールバック関数
動的バックグラウンドワーカーのコールバック関数は、典型的に以下のように記述する。
void
foo_main(Datum main_arg)
{
pqsignal(SIGTERM, sigterm_handler);
pqsignal(SIGQUIT, sigterm_handler);
pqsignal(SIGINT, sigterm_handler);
BackgroundWorkerUnblockSignals();
/*
* 動的バックグラウンドワーカーが走行中に別プロセスが異常終了した場合、postmaster は
* 子プロセスを全て殺した上で動的バックグラウンドワーカーのプロセスを再作成してしまう。
*
* この時、呼び出し元のプロセスがバックエンドプロセスなら、そちらは再作成されない。
* おかしな状況なので動的バックグラウンドワーカーを終了する。
*/
if (MyBgworkerEntry->bgw_notify_pid == 0)
proc_exit(1);
/*
* SIGUSR1 シグナルの標準ハンドラである procsignal_sigusr1_handler() は
* set_latch_on_sigusr1 が true の場合 SIGUSR1 を捕捉すると SetLatch() を呼ぶ。
*/
set_latch_on_sigusr1 = true;
/* データベースへ接続する */
#if 1
BackgroundWorkerInitializeConnection("dbname", "username");
#else
BackgroundWorkerInitializeConnectionByOid(dboid, useroid);
#endif
while (!gotSigterm)
{
int rc;
/*
* 非同期割り込みをチェック
* 場合によってはここでエラーが発生しプロセスは終了する。
*/
CHECK_FOR_INTERRUPTS();
/* 状態が変化するまで待機 */
rc = WaitLatch(&MyProc->procLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
100 /* msec */);
ResetLatch(&MyProc->procLatch);
/* postmaster が終了した場合は終了する */
if (rc & WL_POSTMASTER_DEATH)
proc_exit(1);
if (gotSigterm)
goto done;
/*
* SIGHUP シグナルによる postgresql.conf の読み直し対応は不要。
* 動的バックグラウンドワーカーを再作成した方がよい。
*/
if (MyBgworkerEntry->bgw_notify_pid != 0)
goto done;
/*
* ここに処理コードを書く。
* 必要なら処理の切れ目で CHECK_FOR_INTERRUPTS() を呼ぶ。
*/
}
done:
/* 動的バックグラウンドワーカーは終了 */
proc_exit(0);
}
static void
sigterm_handler(SIGNAL_ARGS)
{
int save_errno = errno;
gotSigterm = true;
if (MyProc)
SetLatch(&MyProc->procLatch);
errno = save_errno;
}
4. 注意事項
4.1 BackgroundWorkerHandle の再利用
postmaster の内部に max_worker_processes 分のバックグランドワーカーのスロットがある。
バックグラウンドワーカーは静的なものも動的なものもこのスロットを消費する。
RegisterDynamicBackgroundWorker() が返す BackgroundWorkerHandle はこのスロットを確保してから動的バックグラウンドワーカーを起動する。
一方、RegisterDynamicBackgroundWorker() によって起動したバックグラウンドワーカーは、RegisterDynamicBackgroundWorker() を呼び出したプロセスとは連動せずに終了しスロットを解放することがある。
この場合、その後に RegisterDynamicBackgroundWorker() が max_worker_processes 回呼び出されると、解放したスロットが再利用される。
呼び出し元プロセス #1 が RegisterDynamicBackgroundWorker() → GetBackgroundWorkerPid() の間に、動的バックグラウンドワーカーが終了した場合 BGWH_STOPPED が返るが、そのうち第3者となる別のプロセス #2 から RegisterDynamicBackgroundWorker() の呼び出しによって最初のプロセスが参照している(と思っている)スロットを再利用することが考えられる。
この場合、GetBackgroundWorkerPid() はどのような値を返すのか?

結論から言うとプロセス #1 とプロセス #2 で GetBackgroundWorkerPid() が別の結果を返す。
これは BackgroundWorkerSlot の中の generation によって実現している。
スロットが再利用される度に generation がカウントアップして行く。
BackgroundWorkerHandle の中にも generation あり、一致しない場合はスロットが再利用され世代が進んでいると判断されて BGWH_STOPPED を返す。
struct BackgroundWorkerHandle
{
int slot;
uint64 generation;
};
generation は 64 ビットなので、カウンター自身が一周することは現実的にないので一周した場合はケアしない。
4.2 その他
- MyProc は bgw_flags に BGWORKER_SHMEM_ACCESS に設定していないと NULL になる。
- バックグラウンドワーカーの XactIsoLevel は default_transaction_isolation に依存する。