pyarrowのデータ型timestamp[ns]をparquetに出力する際のversionによる挙動の違いを検証

背景

PyArrowで出力したparquetをAWS Glue Crawlerでクローリングしているのですが、ある日を境に、以前までtimestampで定義されていたGlue Tableのカラムがbigintに代わってしまいました
よくよく調べるとPythonのライブラリPyArrowのversionを11.0.0から15.0.0にupdateしてからこの事象が発生しました

やったこと

  • pyarrow.timestamp('ns')で定義した値をparquetに出力する場合
    • PyArrow 11.0.0では「自動でtimestamp(micro)にキャストして、timestamp(micro)としてparquetに定義される」
    • PyArrow 15.0.0では「timestamp(nano)のデータ型はそのままtimestamp(nano)としてparquetに定義される」
  • この仮説が正しいかを検証しました

先にまとめ

仮説通り下記の結果でした

  • PyArrow 11.0.0では「自動でtimestamp(micro)にキャストして、timestamp(micro)としてparquetに定義される」
  • PyArrow 15.0.0では「timestamp(nano)のデータ型はそのままtimestamp(nano)としてparquetに定義される」

備考

ms, us, ns

ミリ秒、マイクロ秒、ナノ秒を保持するtimestampの値を載せておきます

ms –> milli: 2024-03-11T08:06:11.527
us –> micro: 2024-03-05T07:04:16.202328
ns –> nano: 2024-03-05T22:04:26.923230000

検証する環境

  1. PyArrow 11.0.0
    • 実行環境 python = 3.9.5
  2. PyArrow 15.0.0
    • 実行環境 python = 3.11.0

poetryで2つの環境を用意し、asdf でversion違いのPythonを使う環境を準備しました

検証内容

動かすコード

import pyarrow
import pyarrow.parquet
from datetime import datetime

# マイクロ秒までしか持たないtimestamp文字列
us_str = '2024-03-13 11:48:05.923230'
col_datas = []
col_data = [datetime.fromisoformat(us_str)]
col_names = ['timestamp_micro']

# ここでtimestamp('ns')を指定
col_datas.append(pyarrow.array(list(col_data), type=pyarrow.timestamp('ns')))
pa_table = pyarrow.table(col_datas, col_names)

# pyarrow.tableの中身を出力してみる
print(pa_table)

pyarrow.parquet.write_table(pa_table, "./output.parquet")

実行結果

print(pa_table)の結果

pyarrow.tableの中身を除いたところ両方同じ結果で、元はマイクロ秒までしか持たないtimestampをnano秒を付与してtableに保持している
データ型もtimestamp[ns] なので、今のところ違和感がない

pyarrow 11.0.0

pyarrow.Table
timestamp_micro: timestamp[ns]
----
timestamp_micro: [[2024-03-13 11:48:05.923230000]]

pyarrow 15.0.0

pyarrow.Table
timestamp_micro: timestamp[ns]
----
timestamp_micro: [[2024-03-13 11:48:05.923230000]]

出力したparquetを比較

parquet-cliを使って、parquetに定義されているデータ型を確認する

pyarrow 11.0.0

%parquet meta output.parquet

File path:  output.parquet
Created by: parquet-cpp-arrow version 11.0.0
Properties:
  ARROW:schema: 
  ...省略
Schema:
message schema {
  optional int64 timestamp_micro (TIMESTAMP(MICROS,false));
}
...省略
--------------------------------------------------------------------------------
                 type      encodings count     avg size   nulls   min / max
timestamp_micro  INT64     S _ R     1         96.00 B    0       "2024-03-13T11:48:05.923230" / "2024-03-13T11:48:05.923230"

optional int64 timestamp_micro (TIMESTAMP(MICROS,false));でmicro秒まで保持しないtimestampにキャストされていることがわかる
pyarrow.tableに保持している間はtimestamp[ns] として扱えるが、parquetに出力する時点のみデータ型が変わる

pyarrow15.0.0

%parquet meta output.parquet 

File path:  output.parquet
Created by: parquet-cpp-arrow version 15.0.0
Properties:
  ARROW:schema:
  ...省略
Schema:
message schema {
  optional int64 timestamp_micro (TIMESTAMP(NANOS,false));
}
...省略
--------------------------------------------------------------------------------
                 type      encodings count     avg size   nulls   min / max
timestamp_micro  INT64     S _ R     1         96.00 B    0       "2024-03-13T11:48:05.92323..." / "2024-03-13T11:48:05.92323..."

optional int64 timestamp_micro (TIMESTAMP(NANOS,false));
こちらはpyarrow.timestamp('ns')で定義した通りNano秒を保持したtimestampが定義されている

まとめ

やはりparquet出力するタイミングのみversion違いによる挙動の違いがありました

ちなみにPyArrowのドキュメントを漁ってみるとそれらしい記述があったので載せておきます

By default (when writing version 1.0 Parquet files), the nanoseconds will be cast to microseconds (‘us’).
デフォルトでは(バージョン1.0のParquetファイルを書き込む場合)、ナノ秒はマイクロ秒(’us’)にキャストされます。

Reading and Writing the Apache Parquet Format — Apache Arrow v11.0.0

Arrow timestamps are stored as a 64-bit integer with column metadata to associate a time unit (e.g. milliseconds, microseconds, or nanoseconds), and an optional time zone.

アロー・タイムスタンプは、時間単位(ミリ秒、マイクロ秒、ナノ秒など)、およびオプションのタイムゾーンを関連付けるカラム・メタデータとともに、64ビットの整数として格納される。

Timestamps — Apache Arrow v15.0.1


15.0.1と11.0.0ではドキュメントの構成が違っているため、比較しずらいですが、15.0.1の方にはナノ秒のキャストについて記載されている箇所が見当たらなかったので、やはりbugではなく、「仕様変更」のようでした