API収集からBigQueryまで:データ基盤の時刻ズレを撲滅するUTC運用のベストプラクティス
データ基盤構築の最終盤、多くのエンジニアを絶望の淵に追いやるのが「タイムゾーンの沼」です。
外部APIから収集したデータの時刻がJSTだったりUTCだったりバラバラ。Cloud SQLに保存した時点でなぜか9時間ずれている。BigQueryで確認するとさらに時間が飛んでいる……。こうした時刻の不整合は、単なる「表示のズレ」では済みません。データの信頼性を根本から揺るがし、正確な分析を妨げる致命的な欠陥となります。
GCP (Cloud SQL, BigQuery)、Prisma、NestJS というモダンな技術スタックを前提に、データ収集から分析に至るパイプラインでタイムゾーン問題を「根絶」するためのベストプラクティスを解説します。
紹介するたった一つの鉄則を守れば、あなたはもう二度と時刻の計算に迷うことはありません。
1. 結論:タイムゾーン問題を解決する唯一の鉄則
複雑な問題に対処するには、シンプルかつ強力な原則が必要です。無数の試行錯誤の末にたどり着いた、データ基盤における時刻管理のゴールデンルールは以下の通りです。
- 保存(NestJS 〜 Cloud SQL 〜 BigQuery)はすべて「UTC(協定世界時)」で統一する
- JST(日本時間)への変換は、最後のアウトプット(SQL分析・BIツール)の瞬間のみ行う
なぜ「UTCへの統一」が絶対なのか。それは、パイプラインの途中で行われる「親切心でのJST変換」こそが、二重変換やタイムゾーン情報の欠落といった静かなデータ破損を引き起こす元凶だからです。
「途中はすべてUTC、出口だけJST」。このルールを徹底することで、各レイヤーの責任範囲が明確になり、デバッグが劇的に容易になります。
2. UTC基準データパイプラインの全体像
具体的な実装に入る前に、目指すべきデータフローを確認しましょう。
- 収集(Ingest): APIからの入力を正規化
- 保存(Store): UTCで永続化
- 分析(Analyze): クエリ時にローカルタイムへ変換
各ステージでこのルールをどうコードに落とし込むか、順を追って解説します。
3. レイヤー別実践ガイド
3.1. データ収集層 (NestJS):入口でのタイムゾーン正規化
データパイプラインの「入口」であるNestJS層は、後続の処理を守るための関所です。
外部APIから送られてくる時刻データは、2023-10-01T15:00:00+09:00 (JST) であったり 2023-10-01T06:00:00Z (UTC) であったりと、フォーマットが混在しているのが常です。
ここでやるべきは、**「受け取った瞬間に JavaScript の Date オブジェクト(UTC)へ正規化する」**ことです。文字列のまま時刻を加工してはいけません。
dayjs などの信頼性の高いライブラリを使用し、タイムゾーン情報を正しくパースして Date オブジェクトに変換します。一度 Date オブジェクトになれば、それは「UTC 1970年1月1日からの経過ミリ秒」という絶対値になり、タイムゾーンの概念によるブレが消滅します。
参考: Date - MDN Web Docs
"JavaScript の Date オブジェクトは、単一の瞬間をプラットフォームに依存しない形式で表します。"
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date
実装例:
import * as dayjs from 'dayjs';
import * as utc from 'dayjs/plugin/utc';
import * as timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
// APIから受け取った時刻文字列の例(バラバラなフォーマット)
const apiResponseJst = "2023-10-01T15:00:00+09:00";
const apiResponseUtc = "2023-10-01T06:00:00Z";
// dayjsでパースし、内部的にUTCで管理されるJS標準のDateオブジェクトへ変換
// これにより、両者は「同じ絶対時刻」として扱われる
const normalizedDate1 = dayjs(apiResponseJst).toDate();
const normalizedDate2 = dayjs(apiResponseUtc).toDate();
// Prismaには、この正規化されたDateオブジェクトを渡す
3.2. データ保存層 (Prisma & Cloud SQL):一貫したUTCでの永続化
正規化されたデータをDBに保存する際、最も怖いのが「アプリとDBのタイムゾーン設定の食い違い」による意図しない変換です。
これを防ぐため、以下の設定を徹底します。
- Cloud SQL (DB本体): システムタイムゾーンを UTC に設定する。
-
Prisma (接続): 接続URLでタイムゾーンを明示する(例: PostgreSQLなら
?schema=public&timezone=UTC)。 - Schema (型定義): タイムゾーン情報を持つ型を使用する。
特にPostgreSQLを使用する場合、TIMESTAMP WITH TIME ZONE(Prismaでは @db.Timestamptz)の使用を強く推奨します。これは「保存はUTC、表示はセッション設定」という挙動をするため、UTC運用のベストプラクティスに合致します。
参考: PostgreSQL 日付/時刻データ型
"timestamp with time zone... UTC として格納され、クライアントに表示される際に変換されます。"
https://www.postgresql.jp/document/current/html/datatype-datetime.html
schema.prisma の例:
model Report {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
// PostgreSQLの場合、Timestamptzを使うのが最も安全
dataTime DateTime @db.Timestamptz
}
3.3. データ分析層 (BigQuery):出力時のJST変換
パイプラインの最終段階、分析層です。
Cloud SQLから同期されたデータは、BigQuery上では TIMESTAMP 型として保持されます。Google Cloudの仕様上、BigQueryの TIMESTAMP 型は UTC で保存される絶対時刻です。
ここで初めて、人間が理解しやすい JST へ変換します。
参考: BigQuery データ型 (TIMESTAMP)
"TIMESTAMP オブジェクトは、タイムゾーンに依存しない絶対的な時点をマイクロ秒単位の精度で表します。"
https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type
クエリ例:
SELECT
-- 元データはUTC (例: 2023-10-01 06:00:00 UTC)
event_timestamp,
-- 分析・レポート用にここで初めてJSTに変換 (例: 2023-10-01 15:00:00 JST)
DATETIME(event_timestamp, 'Asia/Tokyo') AS event_timestamp_jst,
-- 日付集計をする場合もタイムゾーン指定を忘れずに
DATE(event_timestamp, 'Asia/Tokyo') AS event_date_jst
FROM
`your-project.your_dataset.your_table`
この「出口で変換する」アプローチなら、将来的に「ニューヨーク支社のレポートが欲しい」と言われても、クエリのタイムゾーン指定を変えるだけで対応でき、元データを破壊することがありません。
4. よくある落とし穴とデバッグ手法
完璧な設計をしても、管理画面を見て「あれ、時間がずれてる?」と疑心暗鬼になることがあります。そのほとんどは 「ツールの表示上の罠」 です。
-
Prisma Studio / BigQuery コンソールの罠
これらのツールは、ブラウザのタイムゾーン(PCの設定)に合わせて、UTCのデータを勝手にJSTに変換して表示することがあります。「DBにはUTCで入れたはずなのに、JSTで表示されている(またはその逆)」という現象に惑わされないでください。 -
生データ確認の重要性
時刻のズレを疑ったときは、UIの表示を信じず、必ず SQL を叩いて確認しましょう。関数を通さない「生」の値が UTC として正しいかをチェックすることが、唯一の確実なデバッグ方法です。
5. まとめ:タイムゾーン沼からの完全な脱出
本記事で解説したアプローチをまとめます。
-
入口(NestJS):
dayjs等でDateオブジェクト(UTC)に正規化する。 - 保存(Prisma/Cloud SQL): DB設定も接続も UTC で統一し、余計な変換をさせない。
-
出口(BigQuery): 最後のクエリで初めて
Asia/Tokyoに変換する。
「入口で正規化し、UTCで運び、出口で変換する」。
このフローを徹底することで、データ基盤から時刻の曖昧さは消え去り、堅牢で信頼できるデータパイプラインが完成します。