Impala で unix_timestamp(now()) を実行しても現在時刻が返ってこない件について

impala-logo
(画像は Impala の Web サイト より転載)

GMOインターネット 次世代システム研究室 兼 GMOアドパートナーズ グループCTO室のM. Y.(自称DevOps担当)です。今回は、普段の業務で気付いた Impala の小ネタをご紹介します。小ネタではありますが、Impala 初見の人は結構つまづきやすいポイントだと思います。

出題編:Impala の TIMESTAMP 型の不思議な動作

最近、Impala に格納したデータを JDBC 経由で取得するコードを書いてみたところ、なぜか格納したつもりの時間よりも9時間前の時間が返される、ということがありました。

Impala には TIMESTAMP 型で格納されているデータを、java.sql.Timestamp クラスのオブジェクトとして取得しているのに、なんで時間がずれるんだろう? データを入れ間違えたのかな?と思って、impala-shell を使ってデータを調べていたら、さらに不思議な動作に気づきました。unix_timestamp(now()) が現在時刻のUNIXタイムスタンプを返していないようなのです。

Impala には now 関数があり、マニュアルの Built-in Function Support によると、これは現在時刻を TIMESTAMP 型の値として返す関数です。例えば、SELECT 文で実行すると、以下のような値を返します。以下は、11月20日の14時に実行した例です。これは確かに現在時刻を返しています。

そして、now() の返り値を unix_timestamp() に渡して、現在時刻のUNIXタイムスタンプを取得すると、以下のような結果が得られます。

この 1448029930 から日時を逆算すると、JST の 2015-11-20 23:32:10 (このSQLを実行した時刻の9時間後)になりました。本来は 1447997530 になるはずなのですが……? どういうこと?

Impala の TIMESTAMP は UTC との相対値として格納される

この異常事態にそれまでの調査はどうでもよくなってしまい、とりあえずImpalaのマニュアルに飛びつきました。Impalaのマニュアルには、TIMESTAMP 型の定義について、こう書かれています。

Time zones: Impala does not store timestamps using the local timezone to avoid undesired results from unexpected time zone issues. Timestamps are stored relative to UTC.

Impalaのマニュアル:TIMESTAMP Data Type

Impala は UTC との相対値として timestamp を格納する、とのこと。これを読んで「UNIXタイムスタンプとして格納するのではなくて、UTC??」と疑問を持つ人は多いと思います。私も思いました。ちなみに、このUTCとの相対値は、マニュアルのなかで “UTC timestamp” とも呼ばれています。「UTCタイムスタンプ」? タイムスタンプに、タイムゾーンがあるってどういうことなの……。

例えば、Impala の関係が深いデータベース製品である Hive にも TIMESTAMP 型がありますが、Hive のマニュアルには、以下のように「UNIXタイムスタンプを格納する」と明確に書かれています。これは納得できます。

Supports traditional UNIX timestamp with optional nanosecond precision.

Hiveのマニュアル:LanguageManual Types – Apache Hive – Apache Software Foundation

Impala は時刻の文字列表現を自動的に TIMESTAMP に変換する

また、Impalaのマニュアルには、もうひとつ気になることが書かれていました。

Conversions: Impala automatically converts STRING literals of the correct format into TIMESTAMP values. Timestamp values are accepted in the format YYYY-MM-DD HH:MM:SS.sssssssss, and can consist of just the date, or just the time, with or without the fractional second portion. For example, you can specify TIMESTAMP values such as ‘1966-07-30′, ’08:30:00’, or ‘1985-09-25 17:45:30.005’. You can cast an integer or floating-point value N to TIMESTAMP, producing a value that is N seconds past the start of the epoch date (January 1, 1970).

Impalaのマニュアル:TIMESTAMP Data Type

STRING が許容可能な時刻文字列であれば、Impala はそれを自動的に TIMESTAMP 型に変換する、とあります。この変換の様子を確認するために適当なテーブルを定義し、TIMESTAMP 型で定義された registration_date カラムに値を挿入してみます。

格納後のデータを SELECT 文で確認すると、正しそうに見えます。

しかし、その結果を unix_timestamp 関数に通すと、今度は9時間先のUNIXタイムスタンプ(1448029930 = 2015-11-20 23:32:10 JST)が返されてしまいます。うーん。

Impala では to_utc_timestamp 関数と from_utc_timestamp 関数が必須

結論としては、Impala に TIMESTAMP 型を読み書きする際は、ローカルタイムを UTC に変換するために、to_utc_timestamp 関数と from_utc_timestamp 関数を必ず挟まなければなりません。上で説明した member_data テーブルの例については、以下のようにto_utc_timestamp 関数を使うと、正しく時間を格納できます。

この値を取得すると、以下のように登録した時刻の9時間前のデータになってしまいます。しかし、これは時刻が「UTCタイムスタンプ」として格納されているためで、これが Impala で TIMESTAMP 型を使う場合の正しいやり方になります。

一方、時刻を取得する際は、from_utc_timestamp 関数に、データを利用する環境のローカルタイムを渡す必要があります。

今度は、意図した通りの時刻が取得できていますね。JDBC 経由でレコードを取得する際も、SQL で明示的に from_utc_timestamp 関数を呼びだす必要があります。さもないと、”2015-11-20 05:32:10.497165000″ をUNIXタイムスタンプに変換した値が返されてしまいます。これが、JDBC 経由で TIMESTAMP 型を取得した場合に、9時間前の時間が返されてしまった理由でした。

解決編:unix_timestamp(now()) の正しい呼び出し方

最初に挙げた now 関数の例については、やや状況が複雑なのですが、

  • now 関数はローカルタイムの現在時刻の文字列表現を取得する
  • unix_timestamp 関数は引数に UTC タイムスタンプを取るため、now 関数の値を UTC の文字列表現として解釈する
  • このとき now 関数の返り値(JST = UTC + 9時間)を UTC として解釈するために、結果的に9時間進む

と考えれば理解できます。従って、以下のような SQL を実行すれば、現在時刻のUNIXタイムスタンプを正しく取得できます。

個人的には、「ImpalaのTIMESTAMP型 = UTCとの相対値 = UTCにおけるその時刻の文字列表現」というモデルを頭のなかに持っておけば、TIMESTAMP 型に関する Impala の動作について予想しやすくなる気がします。

最新のImpalaでの対応状況

この unix_timestamp 関数の動作がわかりにくいと思う人は他にも居るようで、Impala の JIRA にいくつかの issue が作られていました。先頭の issue が、今回の話に特に関係しています。

これらの issue について対策が行われて、Impala 2.2 からは impalad の起動時に -use_local_tz_for_unix_timestamp_conversions を指定することで、unix_timestamp 関数の挙動を Hive と同様のものに変更できるようになりました。つまり、unix_timestamp(now()) で現在時刻のUNIXタイムスタンプが返されるようになります。

ただし、Impala 2.2でも、use_local_tz_for_unix_timestamp_conversions はデフォルトで無効です。また、このオプションを有効にしても、Impala の TIMESTAMP 型は UTC タイムスタンプのままなので、to_utc_timestamp 関数および from_utc_timestamp 関数を使い続ける必要があります。今後も、Impala で TIMESTAMP 型を使う際には、まだまだおっかなびっくりになってしまいそうです。

最後に

余談ですが、Impala のマニュアルには、以下の一文が書かれています。

Impala does not store or interpret timestamps using the local timezone, to avoid undesired results from unexpected time zone issues. Timestamps are stored and interpreted relative to UTC. This difference can produce different results for some calls to similarly named date/time functions between Impala and Hive. See Impala Date and Time Functions for details about the Impala functions.

Impalaのマニュアル:SQL Differences Between Impala and Hive

タイムゾーンにまつわる予期せぬ問題による、予期せぬ結果を避けるために(”to avoid undesired results from unexpected time zone issues”)このような設計にした、とあるのですが、余計に問題をややこしくしているように感じるのは私だけでしょうか……。

最近 Cloudera からリリースされた、HDFS とは異なる特性を持つストレージの Kudu も、SQLクエリエンジンとして Impala をサポートしています。そのため、今後も Impala を利用するユーザは徐々に増えていくと思います。そのような方が Impala を触ってみて、TIMESTAMP 型の動作に頭を抱えたときに、この記事が参考になれば幸いです。