Hive ACID Mergeを試してみる

今回は、HDP2.6から公式にサポートされた「Hive ACID Merge」を試してみたいと思います。あまり日本語で紹介された記事がなかったのがこのネタを選んだ理由です。今回使ったHDPのバージョンは2.6.3です。前提として、Ambariを使用しており、Hiveサービスが既にインストールされているものとします。

# Hive ACID Mergeとは

Hiveでは、INSERT, UPDATE, DELETEステートメントをサポートしてますが、これらはレコードレベルのオペレーションなので、すこし非効率です。そこで、Hive ACID Merge(MERGEステートメント)が使えます。HiveのMERGEステートメントはANSIの標準SQLに準拠しています。

MERGEステートメントの使い方のイメージとしては、実データのテーブル(マージするターゲットテーブル)と、更新を格納するテーブル(マージするソーステーブル)を作ります。そして、更新が発生した場合にはソーステーブルにその更新をためていきます。この時点ではターゲットテーブルは更新されません。そしてMERGEステートメントによって、ターゲットテーブルとソーステーブルをマージし、ターゲットテーブルに更新を反映させるという感じになります。

以下、実際の例をあげます。

# セットアップ

まずはAmbariからHiveの「ACID Transaction」をオンにします。Ambari上で、Hiveサービスを選択し、Configsタブを選択します。そこで、ACID Transactionの設定画面が見えるので、以下のように設定します。

<img width="315" alt="Screen Shot 2017-12-11 at 11.54.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/8576/589d4c20-6f93-4035-5611-341ea186d21f.png">

設定後、Hiveサービスを再起動すれば、セットアップ完了です。

# テーブルの作成

次に、以下のように2つのテーブルを作成します。

```
CREATE DATABASE merge_data;

CREATE TABLE merge_data.transactions(
	ID int,
	TranValue string,
	last_update_user string)
PARTITIONED BY (tran_date string)
CLUSTERED BY (ID) into 5 buckets 
STORED AS ORC TBLPROPERTIES ('transactional'='true');
 
CREATE TABLE merge_data.merge_source(
	ID int,
	TranValue string,
	tran_date string)
STORED AS ORC;
```

1つはマージのターゲットテーブルとして、もう1つはマージのソーステーブルとして作成します。ターゲットテーブルはバケッティングされていて、トランザクションが有効に設定され、orc形式で格納されている必要があります。

# データを入れる

次にターゲットテーブルにデータを入れます。

```
INSERT INTO merge_data.transactions PARTITION (tran_date) VALUES
(1, 'value_01', 'creation', '20170410'),
(2, 'value_02', 'creation', '20170410'),
(3, 'value_03', 'creation', '20170410'),
(4, 'value_04', 'creation', '20170410'),
(5, 'value_05', 'creation', '20170413'),
(6, 'value_06', 'creation', '20170413'),
(7, 'value_07', 'creation', '20170413'),
(8, 'value_08', 'creation', '20170413'),
(9, 'value_09', 'creation', '20170413'),
(10, 'value_10','creation', '20170413');
```

IDが1から10までのレコードを入れています。

そして、ソーステーブルにデータを入れます。これはターゲットテーブルにマージする更新を表しています。

```
INSERT INTO merge_data.merge_source VALUES 
(1, 'value_01', '20170410'),
(4, NULL, '20170410'),
(7, 'value_77777', '20170413'),
(8, NULL, '20170413'),
(8, 'value_08', '20170415'),
(11, 'value_11', '20170415');
```
ID=1のレコードに対しては更新をしていません。ID=4のレコードに対してはTranValueに対してNULLを入れていますが、これはこのレコードを削除することを示しています。ID=8のレコードに対しては、パーティションとなっているtran_dateに対して変更している例となります。ただし、現在のところHive ACID Mergeには、パーディションのカラムを直接変更する機能はないので、この例では、まずID=8のレコードを削除してから、新しいID=8のレコードをtran_date='20170415'で入れるということしています。ID=11のレコードは、シンプルに新しいレコードとして挿入されます。

# マージしてみる

それでは、実際にマージしてみましょう。

```
MERGE INTO merge_data.transactions AS T 
USING merge_data.merge_source AS S
ON T.ID = S.ID and T.tran_date = S.tran_date
WHEN MATCHED AND (T.TranValue != S.TranValue AND S.TranValue IS NOT NULL) THEN UPDATE SET TranValue = S.TranValue, last_update_user = 'merge_update'
WHEN MATCHED AND S.TranValue IS NULL THEN DELETE
WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.TranValue, 'merge_insert', S.tran_date);
```

このMERGEステートメントのWHEN句に注目すると、

* ソーステーブルのTranValueがNULL以外の値になっていたら、該当するターゲットテーブルのレコードのTranValueを更新する。
* ソーステーブルのTranValueがNULLになっていたら、該当するターゲットテーブルのレコードを削除する。
* ソーステーブルのレコードに対して、該当するターゲットテーブルのレコードがなかったら新しいレコードを挿入する。

となっています。

結果は、以下のようになり、期待したものになっていることがわかります。

```
SELECT * FROM merge_data.transactions order by ID;

+------------------+-------------------------+--------------------------------+-------------------------+--+
| transactions.id  | transactions.tranvalue  | transactions.last_update_user  | transactions.tran_date  |
+------------------+-------------------------+--------------------------------+-------------------------+--+
| 1                | value_01                | creation                       | 20170410                |
| 2                | value_02                | creation                       | 20170410                |
| 3                | value_03                | creation                       | 20170410                |
| 5                | value_05                | creation                       | 20170413                |
| 6                | value_06                | creation                       | 20170413                |
| 7                | value_77777             | merge_update                   | 20170413                |
| 8                | value_08                | merge_insert                   | 20170415                |
| 9                | value_09                | creation                       | 20170413                |
| 10               | value_10                | creation                       | 20170413                |
| 11               | value_11                | merge_insert                   | 20170415                |
+------------------+-------------------------+--------------------------------+-------------------------+--+
```

# まとめ

今回は、Hive ACID Mergeを実際に試してみました。この例はシンプルで、実際のユースケースはもっと複雑なものになると思いますが、Hive ACID Mergeを使えば、ETL系の処理をシンプルで効率的に出来るということがわかりました。

# 参考
https://hortonworks.com/blog/apache-hive-moving-beyond-analytics-offload-with-sql-merge/
https://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.6.3/bk_data-access/content/new-feature-insert-values-update-delete.html
https://community.hortonworks.com/articles/97113/hive-acid-merge-by-example.html