Aurora PostgreSQLでサポートされていたのでpg_cronを試してみました。
pg_cron とは?
Githubのサイトの翻訳ですが、データベース内で拡張機能として実行される、PostgreSQL (10 以上) 用のシンプルな cron ベースのジョブスケジューラとのことです。
定期的なバキューム実行などのメンテナンス用途だけでなく、ストアドプロシージャの呼び出しなども対応しているので色々な用途に使えそうです。
Aurora PostgreSQLでの設定
以下の手順で設定を行います。
1. パラメータグループのpg_cron設定追加
DBインスタンスのパラメータグループのshared_preload_librariesにpg_cronを追加します。
デフォルトでpg_stat_statementsが設定されていたので、カンマ区切りでpg_stat_statements,pg_cronのように設定します。
2. cronのタイムゾーン設定
同じくDBインスタンスのパラメータグループのcron.timezoneの値をGMTからAsis/Tokyoに変更します。
※このパラメータを設定しておかないと、DBのタイムゾーンは日本時間でも、cron設定時はGMTを設定することになります。
3. PostgreSQL DB インスタンス再起動
今回のshared_preload_librariesとcron.timezoneのパラメータはStaticのタイプなので、反映のために再起動します。
4. 拡張機能の有効化
rds_superuser 権限のあるユーザー(デフォルト構成ならpostgres)で以下のSQLを実行します。
CREATE EXTENSION pg_cron;
プロジェクトで個別にデータベースを用意している場合は接続先のデータベースをpostgresにする必要があります。
異なるデータベースに接続している場合は、以下のエラーが出るので接続先を確認してみてください。
can only create extension in database postgres
また、再起動を行っていないと以下のエラーが出ます。
pg_cron can only be loaded via shared_preload_libraries
ジョブの作成・確認・削除(基本コマンド)
ジョブ登録
- スキーマの指定はした方が良い
- 実行DBがpostgresではない場合はジョブ登録後に変更が必要(後述参照)
-- 基本の構文
-- SELECT cron.schedule ('job_name','schedule','command');
-- (例)毎日10:00にVACUUM
SELECT cron.schedule(
'daily_vacuum',
'0 10 * * *', -- JST 10:00
'VACUUM some_table'
);
-- (例)ジョブの履歴を7日残して削除
SELECT cron.schedule(
'delete_job_run_details_daily'
'0 15 * * *', -- JST 00:00 (UTC 15:00)
$$DELETE FROM cron.job_run_details WHERE end_time < now() - interval '7 days'$$
);
-- (例)毎日18:00にステータスを更新 ※DB変更が必要な場合は後述の内容を参照
SELECT cron.schedule(
'user_status_1_to_2_daily_18',
'0 18 * * *', -- JST 18:00
$$UPDATE myschema.m_user SET status = '2' WHERE status = '1';$$
);
-- (例)ストアドプロシージャの実行 ※DB変更が必要な場合は後述の内容を参照
SELECT cron.schedule(
'xxx_func_daily',
'0 3 * * *', -- JST 3:00
'SELECT myschema.update_xxx();'
);
ジョブ一覧
rds_superuser(または同等権限)以外は「自分のジョブだけ」見える挙動とのことです。 (ここ試してないです)
SELECT * FROM cron.job;
ジョブ履歴情報
ジョブの開始時刻と終了時刻、ジョブのステータス、ジョブから返されたエラーメッセージなどの確認に利用できます。(更新で試した更新件数も確認できました)
SELECT * FROM cron.job_run_details;
ジョブ削除
-- jobid で削除
SELECT cron.unschedule(145);
-- jobname で削除
SELECT cron.unschedule('daily_vacuum');
実行対象DBの変更方法
pg_cronの管理はpostgresデータベースにあります。
実行対象データベースを変えたい場合は、以下の手順で実行します。
- postgresのデータベースでジョブ登録
SELECT cron.schedule('xxx_func_daily', '0 3 * * *', 'SELECT update_xxx();'); - 対象データベースの変更
-- ジョブIDで更新(cron.jobで確認) UPDATE cron.job SET database = 'mytestdb' WHERE jobid = 10; -- ジョブ名で更新 UPDATE cron.job SET database = 'mytestdb' WHERE jobname = 'xxx_func_daily';
運用での注意点
ワーカープロセス不足
パラメータで指定できるmax_worker_processesがcron.max_running_jobsより大きくないとジョブが失敗し得ます。
パラメータのデフォルト値はGREATEST($DBInstanceVCPU*2,8)になります。
例えば、db.t4g.medium(vCPU:2, RAM:4GB)の場合は、以下の通りで8になります。
-- 変数部分の計算
$DBInstanceVCPU * 2 = 2 * 2 = 4
-- GREATEST関数を実行した結果(大きい方が選択される)
GREATEST(4, 8) = 8
-- SQLで実行するなら以下で確認
SELECT GREATEST(2*2, 8); -- 8が返却される
ジョブ履歴情報の肥大化
実行履歴テーブル cron.job_run_details は増え続けるので、必要なら定期パージ(または cron.log_run=off でログテーブルに書かない運用)を検討します。
最後に
アプリで管理しているデータ更新を今回のようなジョブで実行すると、将来運用担当者が変わった際に「どの機能が更新している??」みたいなことになりそうなので、個人的には期限なしでの設定はおすすめしないです。
一時的な対応に限定して、バッチを用意するのが難しい場合(「開発者の手が空いてないー!」とか「対応する時間がぁー!!」の場合)は暫定対応として検討してみてもらえればと考えます。
参考サイト

