1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

データ基盤構築の際にタイムゾーンに詰まった話

1
Last updated at Posted at 2025-12-14

API収集からBigQueryまで:データ基盤の時刻ズレを撲滅するUTC運用のベストプラクティス

データ基盤構築の最終盤、多くのエンジニアを絶望の淵に追いやるのが「タイムゾーンの沼」です。

外部APIから収集したデータの時刻がJSTだったりUTCだったりバラバラ。Cloud SQLに保存した時点でなぜか9時間ずれている。BigQueryで確認するとさらに時間が飛んでいる……。こうした時刻の不整合は、単なる「表示のズレ」では済みません。データの信頼性を根本から揺るがし、正確な分析を妨げる致命的な欠陥となります。

GCP (Cloud SQL, BigQuery)PrismaNestJS というモダンな技術スタックを前提に、データ収集から分析に至るパイプラインでタイムゾーン問題を「根絶」するためのベストプラクティスを解説します。

紹介するたった一つの鉄則を守れば、あなたはもう二度と時刻の計算に迷うことはありません。


1. 結論:タイムゾーン問題を解決する唯一の鉄則

複雑な問題に対処するには、シンプルかつ強力な原則が必要です。無数の試行錯誤の末にたどり着いた、データ基盤における時刻管理のゴールデンルールは以下の通りです。

  1. 保存(NestJS 〜 Cloud SQL 〜 BigQuery)はすべて「UTC(協定世界時)」で統一する
  2. 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のタイムゾーン設定の食い違い」による意図しない変換です。

これを防ぐため、以下の設定を徹底します。

  1. Cloud SQL (DB本体): システムタイムゾーンを UTC に設定する。
  2. Prisma (接続): 接続URLでタイムゾーンを明示する(例: PostgreSQLなら ?schema=public&timezone=UTC)。
  3. 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で運び、出口で変換する」。
このフローを徹底することで、データ基盤から時刻の曖昧さは消え去り、堅牢で信頼できるデータパイプラインが完成します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?