Redshift SpectrumでParquetのMIN/MAXプルーニングの効果を確認する

# Redshift SpectrumでのParquetのMIN/MAXプルーニング

Redshift SpectrumではS3上に置かれたファイルをクエリすることができる機能です。
サポートしているファイルフォーマットはいくつかありますが、有名なフォーマットとしてParquetがあります。
Parquetではカラムごとに、いくつかのグループでまとまって管理が行われ、そのグループごとに最小値、最大値の統計情報を持っています。Redshift Spectrumではその統計情報を確認することで、不要なグループを読み飛ばすMIN/MAXプルーニングを行うことができます。

# データの準備

Redshiftに格納されているTPC-Hのlineitem表からS3にParquetのデータを2種類作成します。

- lineitem表のデータをそのままS3に出力するクエリ
```
CREATE EXTERNAL TABLE lineitem_spectrum 
 STORED AS parquet 
 LOCATION 's3://my-bucket/lineitem-spectrum/' 
 AS (SELECT * FROM lineitem)
```
- lineitem表のl_partkeyでソートしてS3に出力するクエリ
```
CREATE EXTERNAL TABLE lineitem_sort_spectrum
 STORED AS parquet 
 LOCATION 's3://my-bucket/lineitem-sort-spectrum/' 
 AS (SELECT * FROM lineitem ORDER BY l_partkey)
```

これでS3上に２つのParquet形式のデータが出力されました。

# データの確認

Parquetファイルは[parquet-tools](https://pypi.org/project/parquet-tools/)というコマンドでファイル内の統計情報などを確認することができます。
作成した２つのParquetデータについてparquet-toolsを使って内容を確認します。

- lineitem表のデータをそのままS3に出力したデータの場合
```
# parquet-tools meta 20220424_223803_666831_1957505_0000_part_00.parquet

row group 1:     RC:1117179 TS:29948957 OFFSET:4 
--------------------------------------------------------------------------------
l_orderkey:       INT64 SNAPPY DO:4 FPO:453763 SZ:2787792/3092728/1.11 VC:1117179 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 197, max: 5999910, num_nulls: 0]
l_partkey:        INT64 SNAPPY DO:2787891 FPO:3406779 SZ:3480255/5024003/1.44 VC:1117179 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 1, max: 200000, num_nulls: 0]
...

row group 2:     RC:385449 TS:10091968 OFFSET:29950219 
--------------------------------------------------------------------------------
l_orderkey:       INT64 SNAPPY DO:29950219 FPO:30255537 SZ:942922/1253641/1.33 VC:385449 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 197, max: 5999586, num_nulls: 0]
l_partkey:        INT64 SNAPPY DO:30893238 FPO:31256452 SZ:1171679/1435303/1.22 VC:385449 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 2, max: 199999, num_nulls: 0]
...
```

グループである`row group`単位で統計情報が表示されています。`l_partkey`に関しては`row group 1`は`min: 1, max: 200000`であり、`row group 2`は`min: 2, max: 199999`なのでほぼ均等に同じようなデータが含まれていることが確認できます。

- lineitem表をl_partkeyでソートしてS3に出力したデータの場合

```
# parquet-tools meta 20220512_234059_265253_2462149_0000_part_00.parquet

row group 1:     RC:1483366 TS:31228049 OFFSET:4 
--------------------------------------------------------------------------------
l_orderkey:       INT64 SNAPPY DO:4 FPO:475158 SZ:3629214/3905445/1.08 VC:1483366 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 197, max: 5999910, num_nulls: 0]
l_partkey:        INT64 SNAPPY DO:3629313 FPO:4153852 SZ:1670375/4615475/2.76 VC:1483366 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 1, max: 197431, num_nulls: 0]
...

row group 2:     RC:19262 TS:460646 OFFSET:31229299 
--------------------------------------------------------------------------------
l_orderkey:       INT64 SNAPPY DO:31229299 FPO:31253144 SZ:55213/68978/1.25 VC:19262 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 896, max: 5998243, num_nulls: 0]
l_partkey:        INT64 SNAPPY DO:31284608 FPO:31293372 SZ:19103/30147/1.58 VC:19262 ENC:RLE,PLAIN_DICTIONARY,PLAIN ST:[min: 197431, max: 200000, num_nulls: 0]
...
```
`l_partkey`に関しては`row group 1`は`min: 1, max: 197431`、`row group 2`は`min: 197431, max: 200000`となっています。`l_partkey`でソートして出力したため、それぞれのグループで担当する値の範囲が異なっています。

# MIN/MAXプルーニングの効果を確認する

S3に出力されたlineitemのデータは600万件となっています。
このデータに対して以下のクエリを実行しMIN/MAXプルーニングの効果を確認します。
```
SELECT * FROM lineitem_spectrum WHERE l_partkey > 197431
SELECT * FROM lineitem_sort_spectrum WHERE l_partkey > 197431
```
結果は以下となります。
| lineitem_spectrum | lineitem_sort_spectrum | 
|:-----------|:------------|
| 1842ms       | 890ms        |

`l_partkey`でソートされているテーブル（lineitem_sort_spectrum）の方がMIN/MAXプルーニングにより不要なグループ(`row group 1`)を読み飛ばすことで２倍以上高速になることがわかりました。