【AWS Glue】ETLジョブでファイル拡張子をtsvからparquetへ変換する際の注意点

## はじめに

tsvファイルを参照元としているAthenaテーブルを、parquetファイルへ変換し新たなテーブルを作るETLジョブをGlueで作成する場合の個人的な注意点について書きました。

AWS Glue Studioだと
`Source : AWS Glue Data Catalog → Target: Amazon S3`
![](https://storage.googleapis.com/zenn-user-upload/f4638734ca6c-20230917.png)

な時のシチュエーションです。

主には**型変換**の話になります。
設定により**意図しない型になったりデータが消えたり**します。


## 事前準備

### tsvファイル作成

tsvファイルは以下の内容になっています。

```:sample.tsv
tinyint_column	smallint_column	int_column	bigint_column	double_column	boolean_column	varchar_column	string_column	date_column	timestamp_column
0	1	2	3	0.1	true	short_text	hello	2021-01-01	2021-01-01 01:23:45
1	2	3	4	1.1	false	medium_text	world	2021-01-02	2021-01-02 12:34:56
2	3	4	5	2.2	true	another_text	apple	2021-01-03	2021-01-03 01:23:45
3	4	5	6	3.3	false	more_text	banana	2021-01-04	2021-01-04 12:34:56
4	5	6	7	4.4	true	text_here	cherry	2021-01-05	2021-01-05 01:23:45
5	6	7	8	5.5	false	even_more	dog	2021-01-06	2021-01-06 12:34:56
6	7	8	9	6.6	true	text_again	elephant	2021-01-07	2021-01-07 01:23:45
7	8	9	10	7.7	false	more_here	frog	2021-01-08	2021-01-08 12:34:56
8	9	10	11	8.8	true	texting	grape	2021-01-09	2021-01-09 01:23:45
9	10	11	12	9.9	false	example	horse	2021-01-10	2021-01-10 12:34:56
10	11	12	13	10.1	true	more_text	india	2021-01-11	2021-01-11 01:23:45
11	12	13	14	11.1	false	last_one	jackal	2021-01-12	2021-01-12 12:34:56

```

[Amazon Athena のデータ型](https://docs.aws.amazon.com/ja_jp/athena/latest/ug/data-types.html)に記載されている型の一部をテストします。

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


これを`s3://target-bucket/sample_tsv/log_date=YYYY-MM-DD/sample.tsv`へアップロードします。
`log_date=YYYY-MM-DD`はAthenaでのHive形式のパーティションを想定しています。

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/partitions.html#partitions-preparing-data

### 変換前テーブル作成
AthenaでCREATE TABLEを実行します。

```sql
CREATE EXTERNAL TABLE IF NOT EXISTS `default`.`sample_tsv` (
  `tinyint_column` TINYINT,
  `smallint_column` SMALLINT,
  `int_column` INT,
  `bigint_column` BIGINT,
  `double_column` DOUBLE,
  `boolean_column` BOOLEAN,
  `varchar_column` VARCHAR(255),
  `string_column` STRING,
  `date_column` DATE,
  `timestamp_column` TIMESTAMP
)
PARTITIONED BY ( 
  `log_date` DATE)
ROW FORMAT DELIMITED 
  FIELDS TERMINATED BY '\t'
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://target-bucket/sample_tsv/'
TBLPROPERTIES (
  'serialization.null.format'='NULL', 
  'skip.header.line.count'='1'
  )
```

パーティションをロードします。
```sql
MSCK REPAIR TABLE `sample_tsv`;
```

```
SELECT * FROM "default"."sample_tsv" limit 10;
```

変換前のテーブルを作ることができました。
![](https://storage.googleapis.com/zenn-user-upload/b294fb5edefc-20230917.png)



## parquetへ変換
AWS Glue Studioを使って`sample_tsv`テーブルの参照元データをparquetに変換し、`sample_parquet`テーブルを作ります。

https://docs.aws.amazon.com/ja_jp/glue/latest/ug/creating-jobs-chapter.html


### そのまま変換
まずは[Change Schema](https://docs.aws.amazon.com/ja_jp/glue/latest/ug/transforms-configure-applymapping.html)を用いないでそのまま変換します。(Change Schemaは下記画像の事です)
![](https://storage.googleapis.com/zenn-user-upload/6c62cea6dd5d-20230917.png)


Glue Studioでは以下のような設定になります。
![](https://storage.googleapis.com/zenn-user-upload/eba39c1c04d1-20230917.png)

設定は**default.sample_tsvテーブルを用いてdefault.sample_parquetテーブルを作る**というシンプルな流れです。

実行してテーブルができました！
![](https://storage.googleapis.com/zenn-user-upload/435b6c21bec0-20230917.png)

ですが、**一部の型がstringになっています**。
![](https://storage.googleapis.com/zenn-user-upload/3efef2d5ff12-20230917.png)

stringになってしまっている型は
- tinyint
- smallint
- varchar
- date
- timestamp

です。

### ChangeSchemaを挟んで変換
一部の型が適切に変換されなかったので、Change Schemaを処理の間に挟みます。

![](https://storage.googleapis.com/zenn-user-upload/b777b5c5f97a-20230917.png)

自動でData typeの項目を埋めてくれますが、**BIGINTはLONGとして入れられます**。このまま実行すると型が対応していないとエラーを吐くのでBIGINTに戻します。

また、この状態で自動生成されるScriptのChange Schemaのコードを見ると

```py
# Script generated for node Change Schema
ChangeSchema_node1694928138274 = ApplyMapping.apply(
    frame=DataCatalogtable_node1,
    mappings=[
        ("tinyint_column", "byte", "tinyint_column", "byte"),
        ("smallint_column", "short", "smallint_column", "short"),
        ("int_column", "int", "int_column", "int"),
        ("bigint_column", "long", "bigint_column", "bigint"),
        ("double_column", "double", "double_column", "double"),
        ("boolean_column", "boolean", "boolean_column", "boolean"),
        ("varchar_column", "string", "varchar_column", "varchar"),
        ("string_column", "string", "string_column", "string"),
        ("date_column", "date", "date_column", "date"),
        ("timestamp_column", "timestamp", "timestamp_column", "timestamp"),
        ("log_date", "date", "log_date", "date"),
    ],
    transformation_ctx="ChangeSchema_node1694928138274",
)
```

となっています。
[ソースタイプの部分](https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue-api-crawler-pyspark-transforms-ApplyMapping.html)はこちらから設定する必要が有るので、`Edit script`から編集します。

https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue-api-crawler-pyspark-transforms-ApplyMapping.html


ソースタイプ・ターゲットタイプ共に[変換前テーブル作成](#変換前テーブル作成)で実行したときの`EXTERNAL CREATE TABLE`の型に変更しました。
```py:create tableのスキーマに倣って設定
    mappings=[
        ("tinyint_column", "tinyint", "tinyint_column", "tinyint"),
        ("smallint_column", "smallint", "smallint_column", "smallint"),
        ("int_column", "int", "int_column", "int"),
        ("bigint_column", "bigint", "bigint_column", "bigint"),
        ("double_column", "double", "double_column", "double"),
        ("boolean_column", "boolean", "boolean_column", "boolean"),
        ("varchar_column", "varchar(255)", "varchar_column", "varchar(255)"),
        ("string_column", "string", "string_column", "string"),
        ("date_column", "date", "date_column", "date"),
        ("timestamp_column", "timestamp", "timestamp_column", "timestamp"),
        ("log_date", "date", "log_date", "date"),
    ],
```


これで実行します。

型変換の部分でエラーが発生します。
![](https://storage.googleapis.com/zenn-user-upload/28d33a570fae-20230917.png)

#### 正しい例

[そのまま変換](#そのまま変換)にてstringに変換されていた型のソースタイプを全て**string**に直します。

```py:そのまま変換でstringに変換されていた型のソースタイプをstringに
    mappings=[
        ("tinyint_column", "string", "tinyint_column", "tinyint"),
        ("smallint_column", "string", "smallint_column", "smallint"),
        ("int_column", "int", "int_column", "int"),
        ("bigint_column", "bigint", "bigint_column", "bigint"),
        ("double_column", "double", "double_column", "double"),
        ("boolean_column", "boolean", "boolean_column", "boolean"),
        ("varchar_column", "string", "varchar_column", "varchar(255)"),
        ("string_column", "string", "string_column", "string"),
        ("date_column", "string", "date_column", "date"),
        ("timestamp_column", "string", "timestamp_column", "timestamp"),
        ("log_date", "string", "log_date", "date"),
    ],
```

実行して成功しました！型もバッチリです。
![](https://storage.googleapis.com/zenn-user-upload/d6b67a62bcff-20230917.png)
![](https://storage.googleapis.com/zenn-user-upload/75252a40a27f-20230917.png)

というわけで、**Change Schemaを挟まずに変換した場合に型がstringになってしまうカラムに対してソースタイプをstringにする**と、適切な型の状態で拡張子を変換しテーブルを作成できます。


### ソースタイプを全てstringにして変換

「型を見てソースタイプをstringにするの面倒！最初から全部stringでいいんじゃない？」と思ったのでやってみます。

```py:ソースタイプを全てstringに
    mappings=[
        ("tinyint_column", "string", "tinyint_column", "tinyint"),
        ("smallint_column", "string", "smallint_column", "smallint"),
        ("int_column", "string", "int_column", "int"),
        ("bigint_column", "string", "bigint_column", "bigint"),
        ("double_column", "string", "double_column", "double"),
        ("boolean_column", "string", "boolean_column", "boolean"),
        ("varchar_column", "string", "varchar_column", "varchar(255)"),
        ("string_column", "string", "string_column", "string"),
        ("date_column", "string", "date_column", "date"),
        ("timestamp_column", "string", "timestamp_column", "timestamp"),
        ("log_date", "string", "log_date", "date"),
    ],
```

実行自体は成功しましたが、**一部カラムはデータが無くなって**しまいました。
![](https://storage.googleapis.com/zenn-user-upload/a77cedd3db33-20230917.png)

[そのまま変換](#そのまま変換)で変換できていた部分が見事に空になっていますね。
元データを確認しても**データが無くなって**います。

![](https://storage.googleapis.com/zenn-user-upload/bcfeafe3f29f-20230917.png)


## まとめ
tsvファイルを参照しているAthenaテーブルを用いて、parquetへ拡張子変換し、新たなAthenaテーブルをAWS Glue ETL ジョブで作る場合は

- [Change Schemaを間に挟む](成功例)
- かつ、[Change Schemaを間に挟まなかった場合にstringに変換されてしまう型](#そのまま変換)のソースタイプを`string`にする
	- 今回の検証では`tinyint,smallint,varchar,date,timestamp`
- その他のソースタイプは**Athenaテーブルで定義されている型を書く**
	- 書かないと変換後データが消える可能性あり([stringだと少なくとも消える](#ソースタイプを全てstringにして変換))

## 追記(20231130)
- decimalはstringに変換しなくても問題なかったです

decimal(6,5)の変換例

```
mappings = [
        ("decimal_column", "decimal", "decimal_column", "decimal(6,5)")
[
```