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の中に常駐するプログラムを動かす」という視点を持つと、アーキテクチャの設計レベルが一段階上がるはずだよ。
何か具体的な実装で詰まったら、いつでも聞いてくれ。応援しているよ!
コメント