VPC環境で利用できます。
Cloud DB for PostgreSQLで pg_cron extensionを提供していますので、以下のご利用ガイドをご参照ください。pg_cronは、PostgreSQL内部で cron式に基づいて SQLタスクを予約実行する extension機能です。
コンソールによる extensionのインストール
Cloud DB For PostgreSQLでは superuser権限を提供していないので、コンソールを通じて superuser権限が必要な extensionをインストールできます。詳細は、以下のガイドをご参照ください。
pg_cronをインストールする際は、タスクを予約する対象のデータベースを選択します。インストールプロセス中には DBサーバが再起動され、HA(高可用性)構成では Failover方式によりサーバが順次再起動されます。
- pg_cronは、PostgreSQLクラスタごとに1つのデータベースにのみインストールできます。既にインストールされている場合、別のデータベースに追加でインストールすることはできません。
- インストール時に選択したデータベースの DB ownerアカウントにのみ、pg_cronの使用権限が付与されます。他の DBユーザーには権限が付与されません。
- したがって、pg_cronを使用するには、当該データベースの DB ownerアカウントでログインする必要があります。
- DB ownerアカウントには、権限を再付与する権限(GRANT OPTION)がないため、DB ownerアカウントは他の DBユーザーに対して cronスキーマの権限を付与(GRANT)することはできません。
pg_cron とは
pg_cronをインストールすると、対象のデータベースに cron スキーマが作成され、以下の構成要素を通じてタスクを予約・管理します。Cloud DB for PostgreSQLでは superuser権限が提供されないため、以下の表の DB ownerアカウント使用の項目に従い、一部の関数は使用できません。
| 区分 | 名前 | 説明 | DB ownerアカウント使用 |
|---|---|---|---|
| テーブル | cron.job | 予約されたタスク定義 | 照会(自分のタスク) |
| テーブル | cron.job_run_details | タスク実行履歴 | 照会・削除(自分の履歴) |
| 関数 | cron.schedule | タスク予約 | 使用可能 |
| 関数 | cron.unschedule | タスク削除 | 使用可能 |
| 関数 | cron.schedule_in_database | 他のデータベースのタスク予約 | サポートしない |
| 関数 | cron.alter_job | 予約されたタスクの変更 | サポートしない |
予約されたタスクは、そのタスクを登録したユーザーの権限で実行されます。登録済みのユーザーがアクセスできないテーブルに対しては、タスクを行うことはできません。
- Cloud DB for PostgreSQLでは superuser権限が提供されていないため、DB ownerアカウントでは
cron.schedule(タスク予約)、cron.unschedule(タスク削除)およびcron.job·cron.job_run_details照会を使用できます。 cron.schedule_in_database(他のデータベースのタスク予約)およびcron.alter_job(予約されたタスクの変更)は、DB ownerアカウントには提供されません。呼び出すと、permission denied for functionエラーが発生します。これら2つの関数は、username引数として他のアカウントの権限でタスクを実行できるため、セキュリティ上の理由から制限されています。- 予約されたタスクのスケジュールやコマンドを変更するには、
cron.unscheduleで一度削除してから再度予約します。 - pg_cronタスクは Primaryサーバでのみ実行されます。Failoverが発生すると、新しい Primaryサーバで予約されたタスクが自動的に継続して実行されます。
タスクの予約と管理
インストール時に選択したデータベースに DB ownerアカウントでアクセスし、タスクを予約します。
- Cloud DB for PostgreSQLでは、セキュリティ上の理由から新規データベースでの
publicスキーマの使用は推奨されません。そのため、別途スキーマを作成して使用します。 - 予約タスクの SQLでは、スキーマを含む名前(
sales.orders)を使用することをお勧めします。タスクは、search_path設定に関係なく動作してこそ安全です。
タスク予約
cron.schedule(job_name, schedule, command) 形式で予約します。
-- 毎日03:00に sales.ordersテーブルの VACUUM ANALYZEを実行
SELECT cron.schedule('nightly-vacuum-orders', '0 3 * * *', 'VACUUM ANALYZE sales.orders');
-- 毎週日曜日05:00に、90日以上前の注文を削除
SELECT cron.schedule('purge-old-orders', '0 5 * * 0', $$DELETE FROM sales.orders WHERE ordered_at < now() - interval '90 days'$$);
- スケジュールには、標準の cron式(分、時、日、月、曜日)が使用されます。
'10 seconds'のように、秒単位の間隔も指定できます。
- pg_cronは、予約されたタスクをバックグラウンドワーカーとして実行します(
cron.use_background_workersがonに設定されます)。同時に実行できるタスク数は、サーバのバックグラウンドワーカー数によって制限されます。 - 複数のタスクの実行時間が重なると、同時実行の制限に達する可能性があるため、負荷の高いタスクや多数のタスクについては、実行時間を分散させることをお勧めします。
- 同じタスクの前回の実行が終了していない場合、次の実行は重複して実行されることなく、前回の実行が終了するまで待機します。
タスクの照会と変更
予約されたタスクは、cron.jobで照会します。スケジュールやコマンドを変更する際は cron.alter_jobを使用できないため、cron.unscheduleで削除してから同じ名前で再度予約します。
-- 予約されたタスクを照会(自分が登録したタスクのみ表示)
SELECT jobid, jobname, schedule, command, active FROM cron.job;
-- スケジュール変更: 既存のタスクを削除した後、新しいスケジュールで再度予約します。
SELECT cron.unschedule('purge-old-orders');
SELECT cron.schedule('purge-old-orders', '0 6 * * 0', $$DELETE FROM sales.orders WHERE ordered_at < now() - interval '90 days'$$);
-- タスク削除
SELECT cron.unschedule('nightly-vacuum-orders');
ユースケース: 日別売上集計の取り込み
毎日早朝に前日の注文を集計し、要約テーブルに取り込むユースケースです。以下のユースケースは、スキーマやテーブルの作成から検証まで、そのまま実行できます。このユースケースでは、pg_cronがインストールされたデータベースを maindb、当該データベースの DB ownerを mainownerと仮定します。すべてのタスクは、maindbに mainownerアカウントでアクセスしてから登録します。
-- maindb に mainownerアカウントでアクセスします。
\c maindb mainowner
-- 1.使用するスキーマを作成します(Cloud DB for PostgreSQLでは、publicスキーマの使用は推奨されていないため、別のスキーマを使用します)。
CREATE SCHEMA sales;
-- 2.ソース注文テーブルを作成し、サンプルデータを入力します(運用中のテーブルがある場合は省略します)。
CREATE TABLE sales.orders (
order_id bigserial PRIMARY KEY,
amount numeric(12,2) NOT NULL,
ordered_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO sales.orders (amount, ordered_at) VALUES
(15000, current_date - 1 + time '09:30'), -- 昨日の注文
(32000, current_date - 1 + time '13:10'), -- 昨日の注文
(8900, current_date - 1 + time '20:05'), -- 昨日の注文
(47000, current_date + time '08:00'); -- 今日の注文(昨日の集計には含まれない)
-- 3.集計結果を保存するための要約テーブルを作成します。
CREATE TABLE sales.daily_sales_summary (
sales_date date PRIMARY KEY,
order_cnt integer NOT NULL,
total_amount numeric(14,2) NOT NULL
);
-- 4.まず、動作を迅速に検証するために、毎分実行するようにタスクを予約します。
-- スキーマを含む名前を使用し、同じ日付で再実行されても重複しないよう、ON CONFLICTで更新します。
SELECT cron.schedule(
'rollup-daily-sales',
'* * * * *',
$$INSERT INTO sales.daily_sales_summary (sales_date, order_cnt, total_amount)
SELECT current_date - 1, count(*), coalesce(sum(amount), 0)
FROM sales.orders
WHERE ordered_at >= current_date - 1
AND ordered_at < current_date
ON CONFLICT (sales_date)
DO UPDATE SET order_cnt = EXCLUDED.order_cnt,
total_amount = EXCLUDED.total_amount$$
);
-- 5.1~2分ほど待ってから、取り込み結果とタスク実行履歴を確認します。
SELECT sales_date, order_cnt, total_amount
FROM sales.daily_sales_summary
ORDER BY sales_date DESC
LIMIT 10;
SELECT jobid, status, return_message, start_time
FROM cron.job_run_details
WHERE jobid = (SELECT jobid FROM cron.job WHERE jobname = 'rollup-daily-sales')
ORDER BY start_time DESC
LIMIT 5;
-- 6.正常に動作することを確認したら、テストタスクを削除して運用スケジュール(毎日00:05)として再度予約します。
-- (cron.alter_jobは使用できないため、unscheduleを行った後、再度予約します)。
SELECT cron.unschedule('rollup-daily-sales');
SELECT cron.schedule(
'rollup-daily-sales',
'5 0 * * *',
$$INSERT INTO sales.daily_sales_summary (sales_date, order_cnt, total_amount)
SELECT current_date - 1, count(*), coalesce(sum(amount), 0)
FROM sales.orders
WHERE ordered_at >= current_date - 1
AND ordered_at < current_date
ON CONFLICT (sales_date)
DO UPDATE SET order_cnt = EXCLUDED.order_cnt,
total_amount = EXCLUDED.total_amount$$
);
- 集計基準日(
current_date)は、セッションおよびサーバのタイムゾーンによって決定されます。タイムゾーンによって集計対象の日付が異なる場合があるため、意図した期間が正しく集計されているかご確認ください。 - テスト時には、毎分(
* * * * *)の実行で動作を素早く確認し、cron.unschedule後に運用スケジュールに再度予約します(cron.alter_jobは、DB ownerアカウントでは使用できません)。
実行履歴のモニタリング
cron.job_run_detailsで、タスクの実行履歴を確認できます。自分が登録したタスクの履歴のみ照会されます。
-- 最近の実行履歴を照会
SELECT jobid, jobname, status, return_message, start_time, end_time
FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 20;
cron.job_run_detailsは自動的にクリーンアップされないため、以下のようにクリーンアップタスクを予約することをお勧めします。
-- 毎週日曜日05:00に、30日以上前の実行履歴を削除
SELECT cron.schedule('cleanup-cron-history', '0 5 * * 0', $$DELETE FROM cron.job_run_details WHERE end_time < now() - interval '30 days'$$);
cron.job_run_detailsはタスクの実行ごとに蓄積されるため、クリーンアップタスクを予約しないとテーブルのサイズがどんどん大きくなってしまいます。