もうすぐリリースされる parquet-1.11.0 には、ColumnIndex という機能が追加されています。

そこで、この ColumnIndex について調べてみました。

これまでの問題

Parquet にはセグメント毎に Statistics として (max, min) の値を持っています。これらの値を持つセグメントは以下の2つです。

  1. ColumnMetaData の ColumnChunks
  2. Page Header

1.は Row Group 内の各 Column の Statistics で、Parquet ファイルの Footer にあるメタデータで保持しています。2.は ColumnChunks をさらに分割したページ単位の Statistics で、各ページヘッダで保持しています。

これまでは、2.の Statistics を read する為に、Column の全てのページにアクセスする必要がありました。したがって、ほとんどのデータが Disk から read されることになります。

Goal

ページ毎に持っている Statistics を Row Group の メタデータに格納したものが ColumnIndex です。Filter を使った Selective な Scan を実行する場合、各ページの Statistics の代わりにこの ColumnIndex を read することにより、不要なページを read することを回避できます。

効果

この ColumnIndex によってどのくらいパフォーマンスが改善されるか調べてみたのですが、公式な結果は見つけられませでした。しかし、なぜか StackOverflow にテスト結果がアップされています。

https://stackoverflow.com/a/40714337/1352781

計測

parquet-mr が提供している API を使って、Parquet ファイル内にあるデータをフィルタして取り出すコードを書いてみました。

https://github.com/masayuki038/parquet-column-index-benchmark

現在の parquet-mr の master branch のコードで、ColumnIndex を使うケース (Bench.filteringScanWithColumnIndex) と使わないケース (Bench.filteringScanWithoutColumnIndex) を実行すると、以下のような結果になります。(Score は小さいほど良い性能を表します)

1Benchmark                              Mode  Cnt     Score    Error  Units
2Bench.filteringScanWithColumnIndex     avgt   20    88.439 ±  3.803  ms/op
3Bench.filteringScanWithoutColumnIndex  avgt   20  6591.767 ± 51.250  ms/op

ColumnIndex を使った場合は 88.439 なのに対して、ColumnIndex を使わない場合は 6591.767 になっています。この結果から、ColumnIndex を使うことでかなり性能が向上することが分かります。

比較対象として、ColumnIndex を実装していない parquet-1.10.1 (V10) を使って同様の計測を実行してみました。

1Benchmark               Mode  Cnt     Score    Error  Units
2Bench.filteringScanV10  avgt   20  6586.017 ± 51.117  ms/op

V10 では 6586.017 とかなり遅い結果となりました。V10 までの実装では、ページの Statistics を使って不要なページ read を抑える実装になっているのではないかと思ったのですが、以下のページを見るとそうではないようです。

その結果、Bench.filteringScanWithoutColumnIndexBench.filteringScanV10 の結果がほぼ同じになっています。

Conclusion

parquet-1.11.0 の新機能である ColumnIndex を使うことにより、Parquet ファイルを Filtering しながら Scan する処理が、従来よりも速くなる可能性があります。ColumnChunks 内に多数のページを持つような場合、より効果が出るはずです。

今回行った計測の結果ではかなり性能が出るように見えますが、これは parquet-mr の API がこれまでページの Statistics を見ていなかったのが大きな要因です。独自にページの Statistics を見て Filtering しているような実装では、今回のような極端な差は出ないと思います。それでも、ページの代わりに ColumnIndex の Statistics を read することによって、性能が向上する可能性があります。(本当はそれを確認したかった…)

また、V10 までの実装でも ColumnChunks の Statistics を見て Row Group 単位で Skip できるので、Parquet ファイルの ColumnChunks 内のページ数が多くない場合は効果が出にくくなります。

そもそも read する際に Filter していない場合は ColumnIndex があっても変わりません。その場合は、read する時に ColumnIndex を見ない方が良いでしょう。