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

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];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* 秒単位、もしくは、BGW_NEVER_RESTART */
    bgworker_main_type bgw_main;
    char        bgw_library_name[BGW_MAXLEN];   /* bgw_mainがNULLの場合のみ */
    char        bgw_function_name[BGW_MAXLEN];  /* bgw_mainがNULLの場合のみ */
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    int         bgw_notify_pid;
} BackgroundWorker;

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_mainは、プロセスが起動されたときに実行される関数へのポインタです。 このフィールドはコアサーバ内の関数を起動する場合にのみ安全に使用することができます。なぜならば、共有ライブラリは、異なるバックエンドプロセスにおける異なる開始アドレスにロードすることができるためです。 どのプラットフォームでもshared_preload_libraries以外の仕組みを使用してライブラリがロードされた場合、この開始アドレスの差異は発生します。 shared_preload_librariesの仕組みを使う場合であっても、Windows上、およびEXEC_BACKENDが使用される場合は、アドレス空間の配置の差異がそれでも発生します。 したがって、このAPIのほとんどのユーザーは、このフィールドがNULLに設定する必要があります。 その場合は、bgw_library_namebgw_function_nameがエントリポイントを決定するために使用されます。

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

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

bgw_main_argは、バックグラウンドワーカのメイン関数のDatum引数です。 bgw_main経由か、bgw_library_namebgw_function_nameの組み合わせ経由であるかにかかわらず、メイン関数は単一の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)またはBackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid)を呼び出すことによって、データベースに接続できます。 これはプロセスにSPIインタフェースを使用してのトランザクションとクエリの実行を許します。 もし、dbnameがNULLであった場合、またはdboidInvalidOidであった場合には、そのセッションは特定のデータベースに接続しません。しかし、共有カタログにはアクセス出来ます。 もし、usernameがNULLの場合、またはuseroidInvalidOidの場合には、そのプロセスはinitdb時に作成されたスーパーユーザとして実行されます。 バックグラウンドワーカはこれら2つの関数をどちらかを一度だけ呼ぶことが出来ます。 データベースを切り替えることができません。

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

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

バックグラウンドワーカをRegisterDynamicBackgroundWorker関数により登録している場合、登録を実行するバックエンドはワーカの状態に関する情報を取得することが可能です。 取得したい場合はRegisterDynamicBackgroundWorkerに2番目の引数としてBackgroundWorkerHandle *のアドレスを渡す必要があります。 もし登録に成功した場合、このポインタは後でGetBackgroundWorkerPid(BackgroundWorkerHandle *,pid_t *)またはTerminateBackgroundWorker(BackgroundWorkerHandle *)に渡すことができるopaue(不透明)ハンドルで、初期化されます。 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になります。

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

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

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