第47章 バックグラウンドワーカプロセス

PostgreSQLはユーザ提供のコードを別のプロセスとして実行できるように拡張することができます。 このプロセスはpostgresによって起動、終了、監視され、サーバの状態に密接にリンクした寿命を持つことができます。 これらのプロセスはPostgreSQLの共有メモリ領域にアタッチしたり、データベースの内部に接続するオプションを持ちます。これらはまた、通常のクライアントに接続された実際のサーバプロセスのように複数のトランザクションを連続して実行することができます。 また、アプリケーションはlibpqとリンクすることにより通常のクライアントアプリケーションのようにサーバに接続して動作することができます。

警告

バックグラウンドワーカを使うにあたっては、堅牢性とセキュリティリスクを考慮しなくてはなりません。なぜならば、C言語で書かれており、データへのアクセスが制限されていないためです。 バックグラウンドワーカプロセスを含むモジュールを有効にしたいと思っている管理者は、細心の注意を払って実践してください。 バックグラウンドワーカプロセスの実行は、注意深く検査されたモジュールだけを許可する必要があります。

バックグラウンドワーカは、モジュールをshared_preload_librariesに記すことによって、PostgreSQLスタート時に初期化できます。 バックグラウンドワーカとして実行したいモジュールは、_PG_init()からRegisterBackgroundWorker(BackgroundWorker *worker)を呼び出すことで登録できます。 バックグラウンドワーカはシステム起動後もRegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle)を呼び出すことによって開始することができます。 postmasterからのみ呼び出すことができるRegisterBackgroundWorkerとは異なり、RegisterDynamicBackgroundWorkerは通常のバックエンドまたは他のバックグラウンドワーカから呼び出す必要があります。

BackgroundWorkerの構造体は以下のように定義されます。

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* in seconds, or BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    int         bgw_notify_pid;
} BackgroundWorker;

bgw_namebgw_typeは、ログメッセージ、プロセス一覧、および同様の場面で使用される文字列です。 bgw_typeは、同じ種類のバックグラウンドワーカで全て同じになるため、例えば同じ種類のワーカをプロセス一覧でグループ化することができます。 一方でbgw_nameは、特定のプロセスに関する追加情報を含むことができます。 (通常、bgw_nameの文字列は何らかの形で種類に関する情報を含んでいますが、必須であるというわけではありません。)

bgw_flagsは、モジュールが要求する機能をOR演算したビットマスクです。可能な値は以下の通りです。

BGWORKER_SHMEM_ACCESS

共有メモリへのアクセスを要求します。 共有メモリアクセスがないワーカは、重量または軽量のロック、共有バッファ、ワーカが作成して利用したいカスタムデータ構造等、PostgreSQLの共有データ構造にアクセスできません。

BGWORKER_BACKEND_DATABASE_CONNECTION

トランザクションやクエリの実行が出来るデータベース接続を要求します。 BGWORKER_BACKEND_DATABASE_CONNECTIONを使用してデータベースに接続するバックグラウンドワーカはBGWORKER_SHMEM_ACCESSを使用して共有メモリにアタッチしなければなりません。さもなければ起動時に失敗します。

bgw_start_timeは、postgresがプロセスを起動するべきタイミングを指定します。 そのタイミングは、以下のうちの1つです。 BgWorkerStart_PostmasterStartpostgres自身が初期化を終えるとすぐに起動します。これを要求するプロセスはデータベース接続に望ましいものではありません)、 BgWorkerStart_ConsistentState(ホットスタンバイで一貫性のある状態に到達し、データベースに接続して参照のみのクエリが実行できるようになると起動します)、 BgWorkerStart_RecoveryFinished(システムが通常の参照/更新クエリを実行できるようになると起動します)。 最後の2つの値は、ホットスタンバイでないサーバでは同等であることに注意してください。 この設定はいつプロセスが起動されるかを示すだけであることに注意してください。 これらのプロセスは、違う状態になったときに停止するわけではありません。

bgw_restart_timeは、プロセスがクラッシュした場合にpostgresがそのプロセスを再起動するために待つ必要のある間隔を秒単位で指定します。 これは任意の正の値、またはクラッシュしても再起動させない場合にBGW_NEVER_RESTARTを指定します。

bgw_library_nameはバックグラウンドワーカの初期エントリーポイントのためのライブラリ名です。 その指定されたライブラリがワーカプロセスによって動的にロードされます。呼び出すべき関数を特定するためにbgw_function_nameが使用されます。 コアコードから関数をロードする場合、"postgres"を設定する必要があります。

bgw_function_nameは新しいバックグラウンドワーカから動的にロードされるときに初期エントリポイントの関数名です。

bgw_main_argは、バックグラウンドワーカのメイン関数のDatum引数です。 メイン関数は単一のDatum引数を取り、voidを返します。 bgw_main_argは引数として渡されます。 加えて、グローバル変数MyBgworkerEntryは、登録時に渡されたBackgroundWorker構造体のコピーを指しています。 ワーカはこの構造を調べることがあり、役に立ちます。

Windowsの(どこか他の場所でEXEC_BACKENDが定義されている)場合、または動的バックグラウンドワーカは、Datumを参照で渡すのは安全ではありません。値のみで渡してください。 引数が必要な場合は、int32型または他の小さな値を渡し、共有メモリに割り当てられた配列へのインデックスとしてそれを使用するのが最も安全です。 cstringtextのようなポインタを渡された場合は、新しいバックグラウンドワーカプロセスから有効になりません。

bgw_extraはバックグラウンドワーカに渡す追加データを含めることが出来ます。 bgw_main_argとは異なり、このデータはワーカのメイン関数の引数として渡されていませんが、上述したようにMyBgworkerEntryを介してアクセスすることが出来ます。

bgw_notify_pidはプロセスの開始時と終了時にpostmasterがSIGUSR1を送信するPostgreSQLバックエンドプロセスのPIDです。 それはpostmasterの起動時に登録されたワーカの場合、またはワーカを登録しているバックエンドがワーカーの起動を待ちたくない場合は0にする必要があります。 それ以外の場合は、MyProcPidで初期化する必要があります。

ひとたび実行すると、このプロセスはBackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags)またはBackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags)を呼び出すことによって、データベースに接続できます。 これはプロセスにSPIインタフェースを使用してのトランザクションとクエリの実行を許します。 もし、dbnameがNULLであった場合、またはdboidInvalidOidであった場合には、そのセッションは特定のデータベースに接続しません。しかし、共有カタログにはアクセス出来ます。 もし、usernameがNULLの場合、またはuseroidInvalidOidの場合には、そのプロセスはinitdb時に作成されたスーパーユーザとして実行されます。 flagsとしてBGWORKER_BYPASS_ALLOWCONNが設定されている場合、データベースへ接続する際のユーザ接続を許可しない制約を回避することが出来ます。 バックグラウンドワーカはこれら2つの関数をどちらかを一度だけ呼ぶことが出来ます。 データベースを切り替えることができません。

バックグラウンドワーカのメイン関数に制御が達したとき、シグナルは最初にブロックされています。このブロックは解除されなければなりません。 これは、必要に応じてプロセスがシグナルハンドラをカスタマイズできるようにするためです。 シグナルは、新しいプロセスでBackgroundWorkerUnblockSignalsを呼び出すことにより解除でき、BackgroundWorkerBlockSignalsを呼び出すことでブロックできます。

バックグラウンドワーカは、bgw_restart_timeBGW_NEVER_RESTARTに設定されている場合、または終了コード0で終了した場合、またはTerminateBackgroundWorkerによって終了した場合、postmasterに自動的に登録が解除されて終了します。 それ以外の場合、bgw_restart_timeで設定された時間の後に再起動します。または、バックエンドの障害のためにpostmasterがクラスタを再初期化した場合は、すぐに再起動します。 一時的に実行を中断するだけでよいバックエンドは、終了するのではなく、割り込み可能なスリープを使用する必要があります。 これはWaitLatch()を呼び出すことによって可能になります。 この関数を呼び出すときにはWL_POSTMASTER_DEATHフラグが設定されているか確認し、postgres自身が終了する緊急事態には、リターンコードを確認するようにしてください。

バックグラウンドワーカをRegisterDynamicBackgroundWorker関数により登録している場合、登録を実行するバックエンドはワーカの状態に関する情報を取得することが可能です。 取得したい場合はRegisterDynamicBackgroundWorkerに2番目の引数としてBackgroundWorkerHandle *のアドレスを渡す必要があります。 もし登録に成功した場合、このポインタは後でGetBackgroundWorkerPid(BackgroundWorkerHandle *,pid_t *)またはTerminateBackgroundWorker(BackgroundWorkerHandle *)に渡すことができるopaque(不透明)ハンドルで、初期化されます。 GetBackgroundWorkerPidはワーカの状態を監視することができます。以下の返り値が得られます。 BGWH_NOT_YET_STARTEDワーカはまだpostmasterにより開始されていない。 BGWH_STOPPED開始されたが、もはや実行されていない。 BGWH_STARTED実行中です。 この最後のケースでは、PIDは、2番目の引数を介して返されます。 TerminateBackgroundWorkerはワーカが実行していた場合postmasterがワーカにSIGTERMを送信し、実行が終了次第すぐに登録を解除します。

場合によっては、バックグラウンドワーカが起動するのを待ってから、ワーカを登録したい場合もあるでしょう。 これは bgw_notify_pidMyProcPidで初期化し、登録時に得られたBackgroundWorkerHandle *を使用してWaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle,pid_t *)関数を呼び出すことで実現します。 postmasterがバックグラウンドワーカを開始しようと試みたか、postmasterが死ぬまで、この関数はブロックします。 バックグラウンドワーカが実行されている場合、戻り値はBGWH_STARTEDとなり、指定されたアドレスにPIDが書き込まれます。 そうでない場合、戻り値はBGWH_STOPPEDまたはBGWH_POSTMASTER_DIEDになります。

登録時に得られたBackgroundWorkerHandle *を使用してWaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)関数を呼び出すことで、バックグラウンドワーカがシャットダウンするのを待つこともできます。 バックグラウンドワーカが終了するか、postmasterが死ぬまで、この関数はブロックします。 バックグラウンドワーカが終了した場合の戻り値はBGWH_STOPPED、postmasterが死んだ場合の戻り値はBGWH_POSTMASTER_DIEDになります。

バックグラウンドワーカは、サーバプログラミングインタフェース(SPI)経由でNOTIFYコマンドにより非同期に通知を送る場合、囲んでいるトランザクションをコミットした後、通知を配信することができるように明示的にProcessCompletedNotifiesを呼ぶ必要があります。 バックグラウンドワーカは、SPIを通じてLISTENによる非同期通知の受信を登録した場合、ワーカがこれらの通知をログに記録しますが、ワーカが傍受し、それらの通知に応答するためのプログラム的な方法はありません。

バックグラウンドワーカの実例として、src/test/modules/worker_spiというモジュールがあります。 これはいくつかの有用な技術を示しています。

登録できるバックグラウンドワーカの最大数はmax_worker_processesによって制限されています。