Orc ファイル試行錯誤

# まえがき

諸事情によりお蔵入りになっている、執筆完了から半年以上塩漬けの記事から私見と言える部分を雑に一般化して抜粋する。

GCP BigQuery をこれから学ぶ上での振り返りという意味も込めて。

本記事の性質は HDFS という処理方式を選択した場合におけるバックエンド設計の **留意点**、あるいはこれはやっちゃいかんなーという **アンチパターン** を挙げる事になるだろう。
特に Orc というファイル形式に出力データを落とす時の設計に焦点を当てる。

# AWS EMR とバックエンドの集計処理

## ストレージサービスとしての RDS 以外の選択肢

ビッグデータを扱うシステムでは MySQL のような RDS ではスケーラビリティと可用性に制約が生じるのでそれ以外の選択肢を探る必要がある。

[AWS EMR](https://aws.amazon.com/jp/emr/) は Hadoop や Hive、Spark、Presto といった複数種の HDFS を提供する。
参照データとして、それ自体がクラウドストレージサービスである [AWS S3](https://aws.amazon.com/jp/s3/) 上のファイルを扱う事ができる。

EMR を選択する事で得られるメリットとして考慮すべきなのはサービス毎にアクセスするクラスタを設定・スケール可能な事。
必要な時間帯にマスタ・スレイブのインスタンスを調整することで従量に対しするパフォーマンスの制御が一定見込める。
処理によって有効なスケールの方法というのを理解はしておくべきだが、札束で殴る事が許されるなら非常時には割と無茶も効く。

## HDFS を集計処理に利用したバックエンド設計

ある程度具体的なイメージを持ってもらうための事例としてのバックエンドサービスの特性をざっと記載する。

### 役割

マルチチャネル [^マルチチャネル] から取得したデータを設定系の入力と照合、加工し統一規格に落とし込む事。

### 要件

- 出力データ

  - システム全体を構成する他のプロダクト (あるいはマイクロサービス) から随時参照される。
  - 利用側で共通するスキームに関してはバックエンド側で集計処理を行う。
  - 利用側独自の集計処理はそれぞれのプロダクトで担当する。[^プロダクトサイド]

-  更新頻度

  - 完全にリアルタイムなデータの更新は求められていないが、半日よりは高い頻度でインプットを反映させる必要がある。

- データの修正

  - 設定系との関わりがあるため設定誤りを修正した場合に再処理が発生する場合がある。

### 責務

- パフォーマンスの維持

  - クライアントの欲しいタイミングで最低限必要な時点までの収集データの処理が完了している事

- 出力データの整合性の担保 [^整合性]

  - 既存データと無矛盾に集計結果を更新する事
  - 更新・修正に際しては他のプロダクトにクリティカルな悪影響を及ぼさない事 [^悪影響] 

# Orc ファイル試行錯誤

## What's Orc？

Orc は HDFS 向けに最適化された CSV のようなデータ形式の一つ。
AWS EMR で利用可能な Hive は HDFS を SQL のようなクエリ形式で複数ファイルに跨ったデータの参照、集計ができる (HiveQL)。
テーブルには AWS S3 上に配置したファイルへの参照を LOCATION というパラメータに持つ。
さらにパーティションという情報によって LOCATION で指定したパスの配下にあるファイルの可視性を集計時に切り替え参照不要なファイルを対象から除外する。[^パーティション]

Orc ファイルを上書き、置き換えは HDFS の処理対象としてファイル単位で入れ換えを行う事ができるため、うまく活用する事ができれば EMR のスケーラビリティと組み合わせて大規模なバッチ処理に要求される CRUD のような処理を効率的に運用する事ができる。

CRUD と言ったが事実としては HDFS は UD を持たない。
ファイル単位で更新できる反面、ファイルの中の特定のレコードを削除・更新する事を得意としていない (Hive は機能自体を持っていない) 。

RDS と HDFS は根本的に異なるものなのでデータをどのような粒度で Orc のようなファイルに落とし込むかというデータ設計が後々のパフォーマンス改善や運用の難易度に直結する事になる。

## IPO 設計

一度に処理するデータ量の関係で HiveQL からデータを取得するよりも Orc ファイルを直接読み書きする方が勝るという事をある程度前提として、設計レベルであーでもないこーでもないと検討する過程で出た注意するべきパターンやそれに対する対策を大きく3点挙げる。

### 冪等性を担保しよう

Orc ファイルを作成する IPO には特別な事情がない限りべき等性を持たせるべきで、既存の出力ファイルそれ自体を更新するようなものにすべきでない。

プロセスの冪等性についてのざっくりとした理解としては入力に対して同じ出力が得られること、で良いと思うのだけれどこれを Orc ファイル単位で適用できる事が望ましい。
言い換えると、入力として前回の処理の出力、既存の出力ファイルを含めて結果に影響を及ぼさない方が良いということ。

繰り返しになるが HDFS はファイル単位で更新できる反面、ファイルの中の特定のレコードを削除・更新する事を得意としていない。
やるとすれば既存データを読み込んで差分データとマージする処理を書く
事になるので、前回実行時に作成したファイルに差分を INSERT するような仕様にしない方がパフォーマンスが出る。[^INSERT]
そもそも出力ファイルを分けて複数ファイルにできる事が利点なのだからそれを損なうような出力の設計が望ましくない。[^複数ファイル]

重要なのは **処理する入力の単位と出力ファイルの単位を揃える事**。
既存の出力を参照する必要があるという事はその出力を作った元データを入力に含めず代替にしているという事に思えるので、プロセスに冪等性があれば不必要という事になる。
集計処理というよりもデータを 1:1 で変換するのに近い処理だと適用しやすいだろう。

冪等性を持たせる事で一番嬉しいポイントだと思うのだけれど、何らかの障害があり出力ファイルに不整合が生じてもリランだけで誤ったデータを上書き訂正できるなら運用が楽。

### 更新・削除を想定するデータは抜き出そう

将来的に更新・削除する事が想定されるデータはそうでないデータとは区分してファイル・パーティションを分ける方が良い。

一度の集計処理のパフォーマンスを考えればプロセスの冪等性で担保できる範囲は決して広くない。
極端な話サービスの全期間の取得データの集計結果などを作ろうとすると毎回データをかき集めるなんてやってられないので。
集計処理を行う上ではそうしたってうまい具合に中間テーブルを組む必要が生じてくるし、その中には集計期間を満たしていない途中集計などがあると考えられる。
もしそういうレコードがあるのなら、それは他のレコードとは別ファイルに出力する事を検討した方が良い。

別ファイル・パーティションに分けられていれば、**そのファイルそのものやパーティションの可視性を消す事でHDFS から見てデータが削除された状態** にする事が簡単にできる。

データを修正したい場合に変更するべき範囲がファイルやパーティションレベルで明確になるよう IPO を設計したい。

### LOCATION の変更を活用しよう

Hive テーブルのスキーマ修正やデータの修復などで大規模に書き換えを行う場合には、 S3 への LOCATION そのものにバージョンを与えるとよい。

直接書き出した Orc ファイル群のアップロードは一瞬では終わらないけれど、Hive の LOCATION を変更してしまえば AWS EMR から参照するデータは一括で変更されるので参照側の影響を低減できる。
裏側で普段行っている処理よりも大きな範囲のデータを一気に書き換えたい場合に有効な手段になる。

処理のユースケースが異なる場合の **出力先と Hive テーブルからの可視性があるファイルパス (オブジェクトキー) の設計** をきちんとしたい。

# あとがき

Hive が読み書きしているデータは単なる S3 上に配置したファイルにすぎない。
データを上書きする場合にはロールバックできないし、上書きする複数ファイルをトランザクショナルに反映する事はできない。
もし、データのアップロードが複数ファイルの一部で終わってしまったらどうなるか、順次アップロードしている最中に他のプロダクトが Hive から参照しようとしたらどうなるかを想定しないと意図しない影響を生む。
プロダクト側から見た整合性を維持する上での仕組みづくりを考えると考慮しなければならない事は決して少なくない。

とはいえこうしてまとめてみると、そもそもの難しさは HDFS や Orc ファイルの性質というよりも、ユースケース分析とそれに対する IPO の設計最適化というシステム開発の普遍的な課題にあるのでぶつかり甲斐のある壁ではあるだろう。


[^マルチチャネル]: マーケティング用語とは別だがいい言葉が浮かばなかった。ここでは単に取得対象のデータが複数経路発生しているという程度の意味。

[^プロダクトサイド]: みんなが使いたいデータまではこちらで作るから、それを基にして自分が使いたいデータは自分で作ってねという役割分担。当たり前の話ではあると思う。

[^整合性]: 入力データ自体の誤り訂正は原則行わず、仕様に基づき可能な範囲で出力データに落とすものとする。

[^悪影響]: クライアントにとって参照タイミングに対して十分妥当な更新状況でデータを提示できて、事実誤認をもたらさない事。少なくとも為替取引のようなリアルタイム性が最重要になる事は想定しない。

[^パーティション]: 参照時の HiveQL にパーティションの制約を含めないとデフォルトで全ての対象ファイルを索引してしまうのでパフォーマンスの観点で地味に重要。

[^INSERT]: Orc ファイルの個数や一つ一つの Orc ファイル内のデータに制約が加わるとこういう需要が生じ得る。

[^複数ファイル]: Hive の場合は一応、一定以上の大きさを持ったファイルにまとめた方が微細なファイルを並べるよりもパフォーマンスが出るという検証があったはず。。。