【実務・中級編】 バックグラウンドワーカーフレームワーク – PostgreSQL

PostgreSQLの「裏方」を操る:Background Workerで自作タスクを走らせよう

やあ。最近、PostgreSQLの奥深さにどっぷりハマっているかい?

PostgreSQLといえば、リレーショナルデータベースとして優秀なのは周知の事実だけど、実は「ただのデータの保管庫」以上のことができるって知ってるかな? そう、Background Worker(BGWorker)フレームワークの話だ。

「外部でcronを回せばいいじゃん」って思うかもしれない。でも、データベースの内部状態に密結合したタスクを、PostgreSQLのプロセス管理下で安全に実行できるとしたらどうだろう?今日は、この強力な武器について、現場の知見を交えて話していくよ。

—

そもそも、Background Workerって何者?

簡単に言うと、PostgreSQLの「ポストマスター(親プロセス)」の下で、独自のプロセスを起動して維持するための仕組みだ。

通常、PostgreSQLはクライアントからの接続ごとにプロセス(あるいはスレッド)を生成するけれど、BGWorkerを使うと、データベースが起動している間、あるいは特定のタイミングで、「独立したバックグラウンドプロセス」を常駐させることができる。

なぜこれを使うのか?

  • 共有メモリへのアクセス: PostgreSQLの内部データ構造や共有メモリに直接アクセスできる。
  • ライフサイクル管理: ポストマスターがプロセスを監視してくれる。万が一タスクがクラッシュしても、PostgreSQLが自動的に再起動を試みてくれるんだ。
  • 権限とコンテキスト: データベースの認証や接続情報を自前で管理する必要がない。

—

実践:どうやって動かすのか

BGWorkerを実装するには、C言語での拡張機能開発が必要になる。まずは「お作法」を軽く見てみよう。

一番大事なのは、`_PG_init()` 関数内で `RegisterBackgroundWorker()` を呼ぶことだ。

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_PostmasterStart;
worker.bgw_restart_time = BGW_NEVER_RESTART; // 死んだら再起動しない設定
snprintf(worker.bgw_library_name, BGW_MAXLEN, “my_extension”);
snprintf(worker.bgw_function_name, BGW_MAXLEN, “my_worker_main”);
snprintf(worker.bgw_name, BGW_MAXLEN, “my_awesome_worker”);

RegisterBackgroundWorker(&worker);
}

この `my_worker_main` 関数の中で、無限ループを回して処理を書くのが基本形だ。

void my_worker_main(Datum main_arg) {
// プロセスがシグナルを受け取れるように設定
pqsignal(SIGTERM, bgworker_sigterm_handler);
BackgroundWorkerUnblockSignals();

while (!got_sigterm) {
// ここで定期的なクリーンアップや統計処理を行う
pg_sleep(60); // 1分休む
}
}

—

現場で役立つ活用事例

理論だけだとピンとこないよね。僕が実際に現場でBGWorkerを活用した例をいくつか挙げておくよ。

1. アプリケーション層に影響を与えない非同期タスク

ログの集計や、数百万件のテーブルの定期的なパーティション管理など、重いクエリをDB内部のタスクとして逃がすときに便利だ。アプリケーションのAPIレスポンスタイムを犠牲にすることなく、DBが暇な時に裏でコツコツ作業を進められる。

2. 外部APIとの連携

「特定のテーブルにデータが入ったら、即座にSlackに通知を送る」なんて処理、トリガーでやるとDBが詰まるよね?
BGWorkerで「通知キューテーブル」を監視させておいて、溜まったら非同期でWebHookを叩く。こうすることで、メインのトランザクションを止めずに済む。

—

開発するときの「注意点」という名の苦労話

さて、ここからが一番大事な話だ。BGWorkerは強力だけど、諸刃の剣でもある。

  • デバッグが地獄: 通常のデバッガでアタッチするのは骨が折れる。`elog(LOG, …)` でログを吐きまくるのが一番の近道だけど、ログの洪水には気をつけて。
  • デッドロックとメモリリーク: 共有メモリを直接触るから、実装をミスるとPostgreSQL全体を道連れにしてクラッシュする。テストはこれでもかというほどやり込もう。
  • トランザクション管理: 自分で `StartTransactionCommand()` と `CommitTransactionCommand()` を呼び出す必要がある。これを忘れると、メモリが解放されずに即死するぞ。

—

最後に:まずは「動く」ところから

最初は難しく感じるかもしれない。でも、PostgreSQLのソースコードを読みながら、`contrib/worker_spi` というサンプルコードをいじり倒してみるのが一番の近道だ。

「DBはデータを保存するだけの場所」という固定観念を捨てて、「DBの中に常駐するプログラムを動かす」という視点を持つと、アーキテクチャの設計レベルが一段階上がるはずだよ。

何か具体的な実装で詰まったら、いつでも聞いてくれ。応援しているよ!

コメント

タイトルとURLをコピーしました