0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kafka / Spark / Flink / Iceberg で時刻(ts)の扱いを統一する設計

0
Last updated at Posted at 2026-04-07

Kafka / Spark / Flink / Iceberg で時刻(ts)の扱いを統一する設計

~2026年7月版(完全版)~

本記事は、Kafka・Hive・Spark・Flink・Iceberg・Trino・Grafanaで構成したログ分析基盤で、
時刻(ts)の扱いを統一するまでの検証結果をまとめたものです。


はじめに

ログ分析基盤では「時刻」は最も重要なデータです。

Kafka、Hive、Spark、Flink、Iceberg、Trino、Grafanaを組み合わせると、
各コンポーネントでタイムスタンプの扱いが少しずつ異なります。

私の環境でも、

  • Sparkでは正しく見える
  • TrinoではUTCに見える
  • Grafanaだけ9時間ずれる
  • 前日指定すると14時までしか表示されない

など、様々な問題が発生しました。

最終的には原因を切り分け、現在は全コンポーネントで統一できています。


システム構成

Kafka(JSON)
 │
 ├─ Hive Raw
 │    │
 │    ├─ Hive View
 │    │
 │    ├─ Hive Curated(Parquet)
 │    │
 │    └─ Spark
 │          │
 │          ▼
 │    Iceberg(syslog_iceberg)
 │
 └─ Flink(SQL)
           │
           ▼
     Iceberg(syslog_events)

              │
              ▼
            Trino
              │
              ▼
           Grafana

Kafkaの元データ

{
  "ts":"2026-07-11T01:05:14+09:00",
  "src_host":"raspi4-3",
  "program":"tubu-frontend",
  "message":"..."
}

ts は ISO8601(+09:00)で送信されています。


現在の設計方針

ts

イベント発生時刻そのもの。

全コンポーネントで同じ意味を持つ列です。

dt

JSTの日付。

パーティション・検索高速化用です。

hr

JSTの時間。

時間帯集計用です。

つまり

列 用途


ts イベント発生時刻
dt JST日付
hr JST時間帯

という役割分担にしています。

Hive Curated では、Parquet の TIMESTAMP を Spark/Iceberg へ直接渡すと
実行環境のタイムゾーン解釈に影響されることがあります。

そのため Hive Curated では次の列を分けて持ちます。

  • ts ... Hive 内での確認・集計用
  • ts_text ... Spark/Iceberg へ渡すための yyyy-MM-dd HH:mm:ss 文字列
  • ts_raw ... Kafka JSON 由来の元文字列

Spark/Iceberg へ投入するときは、ts_textTIMESTAMP_NTZ に変換します。


当初疑った原因

Hive Viewでは

cast(regexp_replace(substr(ts_raw,1,19),'T',' ') as timestamp)

としていました。

そのため

  • offsetを捨てている
  • absolute timeが失われる

と考えていました。

しかし検証した結果、

Spark→Iceberg→Trinoまでのデータ自体は問題ありませんでした。


本当の原因

最終的な原因はGrafana側でした。

以前は

at_timezone(ts,'Asia/Tokyo')

を使用していました。

Grafana自身もブラウザタイムゾーンへ変換するため、

結果として二重変換になっていました。

これにより

  • 9時間ずれる
  • 日付境界がおかしい
  • 前日指定で14時までしか表示されない

という現象が発生しました。


現在のGrafanaクエリ

現在は余計な変換は行いません。

SELECT
  CAST(date_trunc('hour', ts) AS timestamp) AS time,
  COUNT(*) AS value
FROM iceberg.logs.syslog_iceberg
WHERE dt BETWEEN CAST(from_iso8601_timestamp('${__from:date:iso}') AS date)
             AND CAST(from_iso8601_timestamp('${__to:date:iso}') AS date)
  AND ts BETWEEN from_iso8601_timestamp('${__from:date:iso}')
             AND from_iso8601_timestamp('${__to:date:iso}')
GROUP BY 1
ORDER BY 1;

これでGrafanaも正しく表示されます。


Spark側

SparkではHive CuratedからIcebergへ投入します。

投入時には

  • ts
  • dt
  • hr

を生成します。

余計なタイムゾーン変換は行いません。


Flink側

FlinkでもTIMESTAMPとして保持しています。

Sparkと意味が一致するため、

リアルタイム系・バッチ系で同じクエリを書けます。


Trino

Trinoではtsをそのまま利用します。

通常は

SELECT ts
FROM iceberg.logs.syslog_iceberg;

で十分です。

不要な at_timezone() は使用しません。


ts_rawは必要か

以前はts_raw保持を推奨していました。

しかし現在は

  • Kafka
  • HDFS Raw
  • Hive Raw

に元データが残るため、

Iceberg側では必須ではありません。

デバッグ用途で必要な場合のみ保持すれば十分です。


最終設計

Kafka(JSON +09:00)
        │
        ▼
Hive Raw
        │
        ▼
Hive View
        │
        ▼
Hive Curated
        │
        ▼
Spark/Flink
        │
        ▼
Iceberg
        │
        ▼
Trino
        │
        ▼
Grafana

設計ルールは非常にシンプルです。

  • ts = イベント発生時刻
  • dt = JST日付
  • hr = JST時間帯

役割を混ぜないことが重要です。


まとめ

今回分かったことは次のとおりです。

  • SparkとFlinkの時刻は現在統一済み
  • Icebergではtsをそのまま保持する
  • Trinoでもそのまま利用する
  • Grafanaでat_timezone()を重ねない
  • dt/hrは検索・集計専用として持つ

この設計により、KafkaからGrafanaまで一貫した時刻管理が実現できました。


おわりに

時刻の問題は「保存」「検索」「表示」のどこで変換されるかを切り分けることが重要です。

今回の構成では、保存側ではなく表示側(Grafana)が原因でした。

同様の構成を構築する方の参考になれば幸いです。

0
0
3

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?