#author("2018-04-11T14:46:57+00:00","default:haikikyou","haikikyou")
#author("2018-04-11T14:47:43+00:00","default:haikikyou","haikikyou")
[[PostgreSQL/開発]]

#contents

* バックグラウンドワーカプロセス [#q64b878d]

PostgreSQLでは、クライアントコネクション接続を処理するバックエンドプロセスとは異なるプロセスを実行することができる。このバックグラウンドワーカプロセスは、''postmasterプロセスによって起動''(fork)され、ワーカプロセスの状態はpostmasterの起動・停止・監視とリンクしている。このバックグラウンドワーカの仕組みは、パラレルワーカでも使われており、バックエンドプロセスから動的にバックグラウンドワーカプロセスを登録(※)することも可能である。

バックグラウンドワーカは、バージョン9.3から機能追加されている。~
9.4からは動的にバックグラウンドワーカを起動できるように拡張されている。


#ref(./background-worker-overview.png,100%)


&label(info){補足};
- ※ 登録と記述しているのは、実際にプロセス生成を行なうのがpostmasterプロセスであるためである。バックグラウンドワーカの起動は、呼び出し側がposmasterにシグナル送信(SIGUSR1)し、postmasterがforkすることで実現されている。
- バックグラウンドワーカプロセスの最大数は、max_worker_processesパラメータで設定する。
* バックグラウンドワーカプロセスのフロー [#j230d805]

バックグラウンドワーカは、Postmaster起動時及び起動後のタイミングで登録することができる(便宜上、ここではPostmaster起動時のワーカを静的、Posmaster起動後のワーカを動的と呼ぶこととする)。静的、動的では以下の違いがある。

- 静的バックグラウンドワーカ('''BackgroundWorker''')
-- ''%%%postmaster起動時%%%''(postmasterプロセスからの呼び出し)
-- shared_preload_librariesの初期化中
- 動的バックグラウンドワーカ('''DynamicBackgroundWorker''')
--''%%% postmaster起動時以外%%%''(''NOT'' postmasterプロセスからの呼び出し)。バックエンドプロセス、静的バックグラウンドワーカ、動的バックグラウンドワーカから登録が可能。

静的・動的でバックグラウンドワーカを登録する関数は異なるが、バックグラウンドワーカ自体は、同じ構造体変数で表現され、共有メモリ上のバックグラウンドワーカ配列で管理される。

#ref(./background-worker-process-image.png,100%)
* BackgroundWorker構造体 [#wdbe9fdd]

|~番号|~データ型|~フィールド|~説明|h
|1|char|bgw_name[BGW_MAXLEN]|バックグラウンドワーカの名前。プロセスリスト、ログで使用される。|
|2|int|bgw_flags|バックグラウンドワーカが要求する機能。&br;●&code(){BGWORKER_SHMEM_ACCESS};&br;共有メモリへのアクセスをする。&br;●&code(){BGWORKER_BACKEND_DATABASE_CONNECTION};&br;データベース接続をする。&label(fatal){注意};データベース接続をする場合は、&code(){BGWORKER_SHMEM_ACCESS};も指定する必要がある。|
|3|BgWorkerStartTime|bgw_start_time|バックグラウンドワーカの起動タイミング。&label(warn){参考};[[bgworker_should_start_now()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/postmaster.c;h=a8e19c92381dc0de643b1837149ff148026ce175;hb=67854bc59a8b283f02f2a8f0c0095df639bedf06#l5749]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/};};&br;●&code(){BgWorkerStart_PostmasterStart};&br;postmasterの起動時でServerLoop()の前(pmState = &code(){PM_STARTUP};、StartupStatus = &code(){STARTUP_RUNNING};)。&br;リカバリ開始のタイミング &label(warn){参考};[[Postmasterプロセス>PostgreSQL/解析/Postmasterプロセス]]&br;●&code(){BgWorkerStart_ConsistentState};&br;ホットスタンバイモードで参照クエリの受け付けが可能になったタイミング(pmState = &code(){PM_HOT_STANDBY};、次のログが確認されたタイミング:&code(){database system is ready to accept read only connections};)&br;●&code(){BgWorkerStart_RecoveryFinished};&br;リカバリが完了し、システムが通常の参照/更新クエリを実行できるようになった時(pmState = &code(){PM_RUN};、StartupStatus = &code(){STARTUP_NOT_RUNNING};)&br;&label(warn){参考};[[reaper()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/postmaster.c;h=a8e19c92381dc0de643b1837149ff148026ce175;hb=67854bc59a8b283f02f2a8f0c0095df639bedf06#l2900]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/};};|
|4|int|bgw_restart_time|バックグラウンドワーカがクラッシュ(終了コード0以外)してから再起動するまでの時間間隔(秒)。&br;&code(){BGW_NEVER_RESTART};が指定された場合は再起動しない。|
|5|char|bgw_library_name[BGW_MAXLEN]|バックグラウンドワーカモジュールのライブラリ名。ワーカのコードを含む拡張機能モジュールのファイル名。モジュールの動的ロードに必要。|
|6|char|bgw_function_name[BGW_MAXLEN]|バックグラウンドワーカのエントリーポイント。&code(){entrypoint(Datum main_arg)};という呼び出しとなる。|
|7|Datum|bgw_main_arg|バックグラウンドワーカのエントリーポイントに渡される引数。|
|8|char|bgw_extra[BGW_EXTRALEN]|引数以外にデータの受け渡しが必要な場合に使用可能な領域。|
|9|pid_t|bgw_notify_pid|バックグラウンドワーカの終了を通知するプロセスID(&code(){SIGUSR1};シグナルが送信される)。通常は、呼び出しプロセスのPIDである&code(){MyProcPid};を指定する。|
|9|pid_t|bgw_notify_pid|バックグラウンドワーカの起動および終了を通知するプロセスID(&code(){SIGUSR1};シグナルが送信される)。通常は、呼び出しプロセスのPIDである&code(){MyProcPid};を指定する。|
&label(warn){参考};[[struct BackgroundWorker>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/postmaster/bgworker.h;h=e2ecd3c9eb256d35e3e34b221c117c6adc5073be;hb=67854bc59a8b283f02f2a8f0c0095df639bedf06#l88]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/};};

- &code(){BGW_MAXLEN};は、128byteで定義されている。したがって、BackgroundWorkerの引数のデータサイズは、&code(){BGW_MAXLEN};までとなる。

** &label(sample){サンプル}; ''静的バックグラウンドワーカの登録'' [#e9c6b313]
バックグラウンドワーカの登録は、&code(){BackgroundWorker};構造体を介して行なう。以下、静的バックグラウンドワーカを登録する例である。

&code(){src/test/modules/worker_spi};にあるサンプルが大変参考になる。

#geshi(c){{{
void
_PG_init(void)
{
	BackgroundWorker worker;

	memset(&worker, 0, sizeof(worker));

        // ワーカは、データベース接続を必要とする
	worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
		BGWORKER_BACKEND_DATABASE_CONNECTION;

        /* リカバリ完了しクエリの受け付け可能になったタイミングで起動 */
	worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
        /* クラッシュしても再起動しない */
	worker.bgw_restart_time = BGW_NEVER_RESTART;
        /* モジュール名 */
	sprintf(worker.bgw_library_name, "myworker");
        /* エントリーポイント */
	sprintf(worker.bgw_function_name, "myworker_main");
        /* 静的バックグラウンドワーカの場合は、0以外は無効(ワーカが登録されない) */
	worker.bgw_notify_pid = 0;
        /* プロセスリストやログに表示される名前 */
	snprintf(worker.bgw_name, BGW_MAXLEN, "myworker");

	/* ワーカスロットに登録 */
	RegisterBackgroundWorker(&worker);
}


/*
 * ワーカのエントリポイント
 */
void myworker_main(Datum main_arg)
{
    /* ワーカの処理 */
}
}}}

** &label(sample){サンプル}; ''動的バックグラウンドワーカの登録'' [#mf804be4]

&code(){*handle};には、&code(){palloc};で&code(){BackgroundWorkerHandle};構造体のメモリ領域が確保される。メモリコンテキストは、特に指定されていないので、&code(){CurrentMemoryContext};から割り当てられるのに注意。

#geshi(c){{{
static void
RegisterDynamicWorker()
{
	BackgroundWorker worker;

	/* 
	 * Handle for worker.
	 * You can wait for the worker to shutdown with this handle.
	 */
	BackgroundWorkerHandle *handle;

	memset(&worker, 0, sizeof(worker));
	worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
	worker.bgw_start_time = BgWorkerStart_ConsistentState;
	worker.bgw_restart_time = BGW_NEVER_RESTART;
	sprintf(worker.bgw_library_name, "myworker");
	sprintf(worker.bgw_function_name, "myworker_main");
	snprintf(worker.bgw_name, BGW_MAXLEN, "myworker");
	worker.bgw_main_arg = Int32GetDatum(MyProcPid);
	/* Send SIGUSR1 at start and end */
	worker.bgw_notify_pid = MyProcPid;

	/*
	 * Register a worker.
	 * The worker is boot via postmaster.
	 */
	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
		ereport(WARNING,
				(errmsg("could not register background worker")));
}
}}}
* バックグラウンドワーカの終了 [#sf4c89d1]

** 静的バックグラウンドワーカ [#e2850928]

postmasterの停止でワーカも停止する。~
ワーカのみ停止したい場合は、シグナルを送ることで可能(ブロックや停止拒否していない場合)。

** 動的バックグラウンドワーカ [#ydbaae1c]

以下説明のため、バックグラウンドワーカ(ワーカ)を登録するプロセスを便宜上オーナと呼ぶ。

*** オーナが終了 [#r74a682f]

ワーカはオーナの終了を知ることはない。&code(){BackgroundWorker}; 構造体のメンバ変数&code(){bgw_notify_pid};に&code(){0};以外を指定していた場合、オーナが終了すると&code(){BackgroundWorkerStopNotifications};関数が呼ばれ、&code(){bgw_notify_pid};が&code(){0};に設定される。従って、ワーカでこの変数をチェックすればオーナの終了を知ることができる。

&label(warn){参考};[[BackgroundWorkerStopNotifications()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/bgworker.c;h=b690b1b0b85e905572919f86255c0f6c09f54b16;hb=67854bc59a8b283f02f2a8f0c0095df639bedf06#l485]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/};};

また、別の方法として&code(){on_proc_exit};による終了ハンドラを利用するという方法も考えられる。通常、ワーカはループ内でLatchによるWaitを使って何らかの処理を繰り返し実行するような実装になると思うが、オーナありきのワーカの場合、オーナの終了をできるだけ早期に検知し、なおかつ自身も停止した方がワーカスロットの無駄な占有もしなくて済むはず。

例えば以下のような感じである。

#geshi(c){{{
void
RegisterDynamicWorker(void)
{
	/* Register a background worker */

	/* Set a cleanup handler for the worker */
	on_proc_exit(worker_cleanup, 0);
}

static void
worker_cleanup(int code, Datum arg)
{
	pid_t pid;
	BgwHandleStatus status;

	if (worker_handle)
	{
		/*
		 * If the worker has started, kill it.
		 */
		status = GetBackgroundWorkerPid(worker_handle, &pid);
		if (status != BGWH_STOPPED)
		{
			kill(pid, SIGTERM);
			return;
		}

		/*
		 * We wait for the worker to startup.
		 */
		status = WaitForBackgroundWorkerStartup(worker_handle, &pid);
		if (status == BGWH_STARTED)
			kill(pid, SIGTERM); /* Other approach: TerminateBackgroundWorker(worker_handle) */
	}

	return;
}
}}}
*** ワーカの終了を待つ [#l4d027a5]

バッググラウンドワーカが何らかの処理を行ない、呼び出し側でその終了を待つ場合は以下のようになる。

#geshi(c){{{
BgwHandleStatus status;
BackgroundWorker worker;
BackgroundWorkerHandle *handle;

/* Setup struct BackgroundWorker */

/*
 * Register a worker.
 * The worker is boot via postmaster.
 */
if (!RegisterDynamicBackgroundWorker(&worker, &handle))
{
	ereport(WARNING,
			(errmsg("could not register background worker")));
	return;
}
status = WaitForBackgroundWorkerShutdown(handle);
if (status == BGWH_POSTMASTER_DIED)
	proc_exit(1);

/* Do something */
}}}
** ワーカの開始・終了通知(SIGUSR1)を受けとる [#c11f64be]

バックグラウンドワーカの登録時、ワーカを登録するプロセス(ここでは、オーナと呼ぶ)は、&code(){BackgroundWorker};構造体変数の&code(){bgw_notify_pid};に自身のプロセスIDを指定すると、ワーカ開始・終了の通知をシグナル&code(){SIGUSR1};で受け取ることができる。

&label(sample){サンプル}; ''オーナでワーカの開始・終了通知を受け取る''

#geshi(c){{{
static volatile sig_atomic_t got_sigusr1 = false;
static volatile sig_atomic_t got_sigterm = false;

static void
myworker_sigusr1(SIGNAL_ARGS)
{
	int	save_errno = errno;

	got_sigusr1 = true;

	procsignal_sigusr1_handler(postgres_signal_arg);

	errno = save_errno;
}

static void
myworker_sigterm(SIGNAL_ARGS)
{
	int	save_errno = errno;

	got_sigterm = true;

	SetLatch(MyLatch);

	errno = save_errno;
}

void myworker_main(Datum main_arg)
{
	pqsignal(SIGUSR1, myworker_sigusr1);
        pqsignal(SIGTERM, myworker_sigterm);

        /* Now we can receive signals */
	BackgroundWorkerUnblockSignals();

	while (!git_sigterm)
	{
		/* Here Wait Latch */

		if (got_sigusr1)
		{
			got_sigusr1 = false;
			/* Do something */
		}

		/*
		 * Do something
		 */
	}

	proc_exit(0);
}
}}}

* BackgroundWorkerのクラッシュ [#k26074db]

BackgroundWorkerがクラッシュした場合、他のバックグラウンドワーカも再起動されるケースがある。以下の条件を満たす場合である。

+ &code(){bgw_flags};に&code(){BGWORKER_SHMEM_ACCESS};が指定されている かつ 終了コードが、&code(){0};でも&code(){1};でもない。
+ &code(){bgw_flags};に&code(){BGWORKER_SHMEM_ACCESS};が指定されている かつ PostmasterChildSlotがすでに解放済みであるとき。

上記のどちらのケースに該当する場合、&code(){HandleChildCrash()};が呼ばれ、他のワーカプロセスにSITQUITシグナルが送信される。~
(コマンドラインで&code(){-s};が指定されていた場合は、SIGSTOPとなる)。

BackgroundWorkerがクラッシュし、&code(){bgw_restart_time};が&code(){BGW_NEVER_RESTART};でない場合、指定時間後にpostmasterプロセスにより再起動される。~
終了ステータスコードが&code(){0};の場合はクラッシュとして扱われないので、再起動が不要な場合は&code(){0};で終わるように処理すると良いだろう。

&label(warn){参考};
- [[CleanupBackgroundWorker()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/postmaster.c;h=a8e19c92381dc0de643b1837149ff148026ce175;hb=ad4fb805ad08c86dd6389e6755081dfd7c864416#l3143]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/heads/REL_10_STABLE};};
- [[HandleChildCrash()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/postmaster.c;h=a8e19c92381dc0de643b1837149ff148026ce175;hb=ad4fb805ad08c86dd6389e6755081dfd7c864416#l3319]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/heads/REL_10_STABLE};};
- [[maybe_start_bgworkers()>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/postmaster.c;h=a8e19c92381dc0de643b1837149ff148026ce175;hb=ad4fb805ad08c86dd6389e6755081dfd7c864416#l5888]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/heads/REL_10_STABLE};};
* 参考リンク [#o7ea3c3f]

- [[並列クエリを実行するPostgreSQLのアーキテクチャ>https://www.slideshare.net/kaigai/postgresql-64441503]] - &size(11){&color(gray){https://www.slideshare.net/kaigai/};};
- [[PostgreSQL 9.6.X 文書 第45章 バックグラウンドワーカプロセス>https://www.postgresql.jp/document/9.6/html/bgworker.html]] - &size(11){&color(gray){https://www.postgresql.jp/document/9.6/html/};};
- [[Chapter 47. Background Worker Processes>https://www.postgresql.org/docs/10/static/bgworker.html]] - &size(11){&color(gray){https://www.postgresql.org/docs/10/static/};};
- [[postmaster/bgworker.c>https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/postmaster/bgworker.c;h=b690b1b0b85e905572919f86255c0f6c09f54b16;hb=b8279a783dafb7c715b3835d4c9244d3451c4c1a]] - &size(11){&color(gray){https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/heads/REL_10_STABLE};};
- [[PostgreSQL のバックグラウンドワーカー(Background Worker)の使い方>http://www.nminoru.jp/~nminoru/postgresql/pg-background-worker.html#struct-background-worker]] - &size(11){&color(gray){http://www.nminoru.jp/~nminoru/postgresql/};};

PR

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP