postgres の current_timestamp
関数を使ったクエリをアプリケーションに実装していたところ、本番データベースにタイムゾーンのずれによる時刻の不整合を招きいれてしまった。
TL;DR
- RDSで非 UTC の時刻を扱うユースケースでは
current_timestamp
やcurrent_date
といったタイムゾーン依存の関数は使うべきでなく、プログラム経由で動的に生成するのがよい。 - 状況を再現させるコードをレポジトリに公開しているので、詳しくはこちらを参照いただけるといいかと思う
sato11/postgres-timezone-pitfall (https://github.com/sato11/postgres-timezone-pitfall)
再現する条件
アプリケーションのタイムゾーンとして UTC を使っていないこと
筆者の属するプロジェクトでは rails (activerecord) を使っており、タイムゾーンの設定は次のように JST を指定している。ここで UTC を指定しているような場合、特に問題はないだろうと思われる。
# config/application.rb
module SomeApplication
class Application < Rails::Application
...
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
# config.active_record.default_timezone = :utc
...
end
end
データベースに Amazon RDS を利用していること
RDS のタイムゾーンについて、AWSの公式FAQに次のようなエントリがある。
すべての Amazon RDS DB インスタンスは、デフォルトで UTC/GMT 時間を使用します。タイムゾーンの変更はオプションです。
データベースレイヤーで UTC タイムゾーンを使用するのがベストプラクティスです。UTC は夏時間 (DST) を持たないため、後でシフトするときに時間を調整する必要はありません。
ローカルタイムゾーンを使用する必要がある場合は、代わりにアプリケーションレイヤーでタイムゾーンを変換します。
より具体的には
UPDATE users SET updated_at = current_timestamp;
のように current_timestamp
を直接埋め込んだクエリを発行すると、データベースのタイムゾーン、すなわち UTC が採用されて、アプリケーションのタイムゾーンと齟齬が生じる。
アプリケーション層でローカル時間を生成してクエリに渡すには、次のようなアプローチをとる必要がある。以下は activerecord を前提に記述するが、やるべきことはどの言語/フレームワークを採用していても変わらないはず。
ActiveRecord::Base.sanitize_sql_array(['UPDATE users SET udpated_at = ?', Time.current])
もっとも、多くの場合はこのようなまわりくどい書き方をする必要がない可能性も高い。 activerecord であれば、単に user.touch
と記述すれば上記のユースケースは満たせる。
とはいえ筆者の場合、ある程度の大きさのバッチタスクの一部にて、大量のレコードをインスタンス化せずに一括更新する箇所でこの問題に遭遇した。似たような条件に出くわすことはないとはいえず、またそのような場合には十分な注意が必要となる。