現在、自分はRustでGraphQL APIフレームワーク Senax を開発中です。
大規模バージョンアップを控えているためGitHubでの更新は少なめですが、開発自体は進んでいます。
フレームワークでは「開発者が無意識に行っていることを明確化し、設定で定義できる」ようにすることが必要になってきます。
今回は MySQLからPostgreSQL対応に拡張した際に考えた日時型とタイムゾーンの扱い についてまとめます。
MySQLの日付型
MySQLには主に次の2つがあります。
datetime
timestamp
範囲と用途
-
timestamp
は'1970-01-01 00:00:01' UTC
から'2038-01-19 03:14:07' UTC
までしか対応していません。
→ WL#1872 により32bit OSサポートを廃止すれば2116年まで拡張の可能性あり - ただし、任意の日時の格納はできないため、
created_at
など現在時刻を保存する以外はdatetime
一択 となります
タイムゾーンの違い
-
datetime
: タイムゾーン非対応 -
timestamp
: タイムゾーン対応
このため、MySQLユーザは「DBのタイムゾーンサポート」という概念をあまり意識しないまま運用していることが多いと思われます。
PostgreSQLの日付型
PostgreSQLには次のような型があります。
-
timestamp
(timestamp without time zone) -
timestamptz
(timestamp with time zone)
特徴
-
timestamptz
はタイムゾーン対応で、十分な範囲を保証しています - グローバルサービスを意識する(日本の)開発者は 当然タイムゾーン対応をすべき という認識を持っていることが多いと思われます
タイムゾーン対応とは何か?
ここで言う「タイムゾーン対応」とは、登録時の値のタイムゾーンをそのまま保存することではありません。
実際には以下の仕組みです。
- 内部的に UTCタイムスタンプ に変換して保存
- 出力時に サーバ設定またはクライアント設定のタイムゾーン に変換して返す
誰が変換すべきか?
ここが本質的な問題です。
-
スマホアプリ等なら、海外移動時に現地タイムゾーンへの変換は クライアント側で行うべき
- さもないと、サーバ側で全世界のタイムゾーン+サマータイムを保証する必要が出てきます
-
MySQLがタイムゾーンサポートを限定的にしたのも、未来のサマータイム保証の難しさが理由にあると思われます
-
クライアントでできない場合はAPサーバで変換します
DBのタイムゾーンを使う場合は、クライアントのタイムゾーンをDBに伝えて「日時を素通しで変換」させる必要がありますが、これが実際に広く使われているかは疑問です。
結論として、DB側タイムゾーンのメリットはほぼありません。
ただ、直接データを確認する際にcreated_at
などが現地時刻になるのが便利というのはあるでしょう。
また、APサーバのプログラミング言語との相性はあります。APサーバがUTCを扱えないのであればDB側タイムゾーンに依存する必要があるかもしれません。
ベストプラクティス
実際の開発現場でグローバル対応する可能性がある場合の指針としては以下がベストだと思われます。
-
MySQL
-
datetime
を使い、UTCで保存する - → シンプルで2038年問題の影響も受けず、タイムゾーンの解釈を誤るリスクも避けられる
-
-
PostgreSQL
-
timestamptz
を使う - → 内部的にUTCで管理され、クライアントやサーバのタイムゾーンに応じて出力が自動変換されるため、運用が容易
-
つまり、
MySQLでは UTC + datetime、PostgreSQLでは timestamptz を選ぶのが無難
フレームワークでサポートすべき3パターン
日本時間しか必要ないケースを考慮して、フレームワークで扱うべきは以下の3パターンだと考えています。
タイプ | MySQL | PostgreSQL | Rust |
---|---|---|---|
タイムゾーンなし日本時間 | datetime |
timestamp |
chrono::NaiveDateTime |
UTC | datetime |
timestamp |
chrono::DateTime<Utc> |
タイムゾーン付き日本時間 | timestamp |
timestamptz |
chrono::DateTime<Local> |
まとめ
- DB側タイムゾーン変換は避け、基本は UTC保存・クライアント変換表示 がベスト
- ただし、運用上「現地時刻で見られると便利」ということも無視できません
- フレームワーク側では、少なくとも上記3パターンを選べるようにするべきだと考えています
👉 Senax ではこの設計を前提に進めています。
他の開発者の方の実運用経験やベストプラクティスがあればぜひ教えていただきたいです。