[Amazon Athena / Python] parquet形式のデータをPythonで生成し、AWS Athenaから読み込む

# What I would like to do (やりたいこと)

* Amazon Athenaを使う
* データはPythonで生成
* なるべく安く済ませる
    * Parquet形式を試す
    * Gzip圧縮も併用したい

# Amazon Athena pricing (料金)

ここに書いてあるのですが、

https://aws.amazon.com/jp/athena/pricing/

> ファイルを圧縮し、さらに Apache Parquet などの列形式に変換した場合

この場合、圧縮や形式変換をしない場合と比較すると、うまくいけば**料金が1/9程度**まで減らせるようです！！
これは見のがせないよね....？(\*´﹃｀\*)

料金が減るということは、それだけAWS側のリソース（電気）を食わない、つまり、環境にも優しいといえるはず。多分...(ﾟДﾟ；(ﾟДﾟ；


# How to do

## 1. Use Parquet formatted file (Parquet形式のファイルを使う)

### 1-1. Source of Python

まずは単なる`Parquet`形式から行きましょう

```python
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
import pandas as pd

two_dimension_array: np.array = np.arange(12).reshape(4, 3)
df: pd.DataFrame = pd.DataFrame(two_dimension_array)
df.columns = ['one', 'two', 'three']

df.head()

	one	two	three
0	0	1	2
1	3	4	5
2	6	7	8
3	9	10	11
```

今回扱うデータはこんな感じです。
これを`Parquet`形式のファイルとして出力します。

```python
table: pa.Table = pa.Table.from_pandas(df)
pq.write_table(table, './sample_2d_array.parquet', compression='snappy')
```

これで、python実行ファイルと同階層のディレクトリに`sample_2d_array.parquet`という名前のファイルがあるはずです。

### 1-2. Note: What is `compression`?

`compression`は、圧縮形式を表すようです。
ちなみに、`snappy`はデフォルト値。
`help`を使えばそれがわかります。

```python
help(pq.write_table)

Help on function write_table in module pyarrow.parquet:

write_table(table, where, row_group_size=None, version='1.0', use_dictionary=True, compression='snappy', write_statistics=True, use_deprecated_int96_timestamps=None, coerce_timestamps=None, allow_truncated_timestamps=False, data_page_size=None, flavor=None, filesystem=None, compression_level=None, use_byte_stream_split=False, data_page_version='1.0', use_compliant_nested_type=False, **kwargs)
    Write a Table to Parquet format.
    
    Parameters
    ----------
    table : pyarrow.Table

...以下略
```

なので、`snappy`という方式で圧縮するなら、実際は`compression`を指定しなくても大丈夫です。


### 1-3. Upload Parquet file to S3

さて、今度は、作成したParquet形式のファイルをS3にアップロードして、Athenaから読み込めるようにします。

ここからバケットを作成しますが、細かい作成方法は他の記事でご確認ください₍₍ (ง ˘ω˘ )ว ⁾⁾ｽﾔｯｽﾔｯ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/14f7f69e-b9c1-7bcb-ade6-f2bbc1964041.png)

作ったバケットの画面を開き、お好みのフォルダを作成したら、Parquetファイルをアップロードします。
アップロードの方法も他の記事を見てね...(´；ω；｀)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/61f4f6cf-929b-7fe0-8cab-580388b867f3.png)

1-3 で大事なのは以下の部分ですね。
アップロードしたファイルのパスを確認すること。

- まずアップロードしたファイルの名前をクリックして...
![2021-06-19_14h05_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/5c401b7f-3e99-5f60-4d5f-e81ae327dcba.png)
- S3 URI をコピーしておこう！
![2021-06-19_14h06_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/40f0e662-175e-3e34-8f2c-a541c5e80263.png)

### 1-4. Create DB and Table on Athena

今度はS3を離れて、ついにAthenaの操作を行います。
Athenaの画面を開くと、最初はデータベースがありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/85e9951b-7154-a9aa-11b2-462cf1b305a7.png)


以下の公式サイトの通り、データベースをまず作ります。

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/creating-databases.html

`CREATE DATABASE myDataBase`で作成できます。
...が、そのまま作成しようとすると、`No output location provided. An output location is required either through the Workgroup result configuration setting or as an API input.`というエラーが発生します。
以下のとおり。
![2021-06-19_14h19_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/998bab49-55ed-59f6-9b56-6cc3bc7e0720.png)

なので、その前に、赤色の下線を引いたところをクリックして、Queryの実行結果を配置するS3のフォルダを指定しましょう。
クリックするとこんなモーダルウィンドウが表示されるので入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/12055c60-e521-bb16-3860-9cabc9998a1f.png)


場所としては、Parquetファイルを設置したフォルダの近くで、かつ、別のフォルダがよさそう。
Query結果保存先を指定出来たら、今度こそAthena上にDBを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/28c5acec-9518-c04f-a826-9d2bf7a78bd2.png)

"Run query"をクリックするとデータベースができる。

今度はTableを作ります。
"Database"の下にあるセレクトボックスをクリックして、先ほど作成したデータベースを選択。（このデータベース内にTableを作成します）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/1a477c17-4d9b-66fb-da05-cff4462ef268.png)

後は"New query"欄にテーブル作成のクエリを書いて実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/f06b244f-3546-5e52-0324-34ab5909f5ea.png)

```sql
-- 今回のparquetファイル用のテーブル作成クエリ
CREATE EXTERNAL TABLE IF NOT EXISTS sample (
  one INT,
  two INT,
  three INT
)

STORED AS PARQUET
-- 1-3 でコピーしたParquetファイルのあるS3「ディレクトリ」を指定
-- 「ファイル名」ではなく、その1階層上の「ディレクトリ」までのpathを指定する
LOCATION 's3://hoge/sample/'
TBLPROPERTIES ("parquet.compress"="SNAPPY");
```

もし、1-3でコピーされた`S3 URI`が
`s3://hoge/sample/sample_2d_array.parquet`
だったら、
`s3://hoge/sample/`
までを`LOCATION`に指定すればＯＫです。

ここまで実行すると、`Tables`のところに作成したテーブル名が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/81a7ccb8-53ad-fb01-be97-454733db3577.png)


### 1-5. Load Parquet file into Athena

あとはTable名を指定してSQLを実行することができる。
`SELECT * FROM sample;`を入力して"Run query"を実行した結果がこれ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/60bd270b-3e5b-4086-37a4-6787058cf5cb.png)

ひとまずParquet形式のデータをAthenaで読み込むところまではできた！(^ワ^\*)

## 2. Use Parquet formatted and Gzip compressed file on Athena

### 2-1. Source of Python

parquet形式で、GZIP圧縮を行う方法を調べるために、もう一度`help`を実行します。
`compression`引数のところまでスクロールすると、以下のような記述が見つかります。

```python
help(pq.write_table)

...(略)

    compression : str or dict
        Specify the compression codec, either on a general basis or per-column.
        Valid values: {'NONE', 'SNAPPY', 'GZIP', 'BROTLI', 'LZ4', 'ZSTD'}.

...(略)
```

1-1では、`compression='SNAPPY'`を指定しましたが、どうやらこの引数の取りうる値の一つに、`GZIP`があるようです！
これを指定してみましょう！！！

```python
two_dimension_array: np.array = np.arange(12).reshape(4, 3)
df: pd.DataFrame = pd.DataFrame(two_dimension_array)
df.columns = ['one', 'two', 'three']

table: pa.Table = pa.Table.from_pandas(df)
# compression='GZIP' に変更
pq.write_table(table, './sample_2d_array_gzip.parquet', compression='GZIP')
```

今度は、同じ階層に、`sample_2d_array_gzip.parquet`が作成されています。

### 2-2. Create Table on Athena

1-2～1-4の操作のうち、2においても特に変わらないところは、箇条書きでまとめておきます。

1. S3上にフォルダ作成  
※ 1-3とは別のフォルダを作成すること
1. S3へのファイルアップロード
1. 1-4のデータベース作成はもうしなくてもよい  
※ もちろん別のデータベースを作っても問題ないです

1~3が終わったら、2-2で新たにテーブルを作ります。
テーブルを作り直す必要がある理由ですが、以下の通り、テーブルごとに

- **LOCATION**:ファイルの設置箇所のパス
- **TBLPROPERTIES**: データの圧縮形式などのプロパティ

が異なるからです。

```sql
-- Gzip圧縮したparquetファイル用のテーブルを作成するSQL
-- テーブル名、LOCATION、TBLPROPERTIES を変更します
CREATE EXTERNAL TABLE IF NOT EXISTS sample_gzip (
  one INT,
  two INT,
  three INT
)

STORED AS PARQUET

LOCATION 's3://hoge/sample_gzip/'
TBLPROPERTIES ("parquet.compress"="GZIP");
```

このSQLを実行すると、今度は`sample_gzip`というテーブルができているはずです。

### 2-3. Load Gzip compressed Parquet file into Athena

あとは、このSQLを実行するだけでデータを読み込むことができました(^ワ^\*)

```sql
SELECT * FROM sample_gzip;
```

テーブル作成時にデータのフォーマットや圧縮形式を正しく指定しさえすれば、テーブルアクセス時は特に意識する必要がなさそうです。

これで環境負荷も減らせるならやるしかない！(｀･ω･´)きりっ

とはいったものの、本当に効果があるかは調べる必要があるかもしれません！
パフォーマンスにどんな影響があるかは調べていないのですが、関連記事を見つけてあるので、末尾の記事も参考として下さい٩( 'ω' )و

# References (参考資料)

基礎的な情報を上の方に、個別具体的な話はなるべく下の方に配置しました。

https://aws.amazon.com/jp/athena/pricing/

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/data-types.html

https://qiita.com/yoshiyama_hana/items/3d532c7ecc5f08c0d040

# References about performance

https://aws.amazon.com/jp/blogs/news/top-10-performance-tuning-tips-for-amazon-athena/

https://www.techscore.com/blog/2019/12/07/parquet_examine/