2016.04.08

TIPS:Hiveテーブルを本番環境とそっくり同じ状態で開発環境にコピー

こんにちは。次世代システム研究室のデータベース・Hadoop(Hive, HBase)まわり担当のM.K.です。

よく本番環境で実行する前にSQLクエリの性能を検証したい、ということがあると思います。
今回はHiveテーブルを本番環境とそっくり同じ状態で開発環境にコピーするTIPSを書きます。

データの中身が同じであれば良いと考えて、ツールでエクスポート/インポートをしたり、別テーブルにINSERTしたり、生データがある場合にまとめてロードし直すなどでコピーすることはよくある話ですが、しかし実はこうしたやり方は、テーブルのデータをキレイな状態にしてしまうため、データベースやテーブルの種類によっては、SQLを投げても正しく性能が計れない場合があります。
※もちろん、開発環境と本番環境のインフラ構成や性能が違いすぎない前提です。

実行したいSQLクエリが、データをコピーした開発環境や検証テーブルでは性能が問題なかったのに、本番では大幅に遅延して大問題になる、という経験をされた方は多いのではないでしょうか。

特にHadoopでHiveを利用している場合、ログなどのテキストデータを比較的リアルタイムで格納して分析で利用するため、INSERT INTO文を使って差分データだけ素早く追記することが多いです。
INSERT OVERWRITE文だと常にデータファイルがキレイな状態になる反面、毎回既存データと差分データをあわせて書き戻す必要があり、処理コストが高くつくからです。

しかし、INSERT INTOを繰り返すと既存のデータファイルには手を加えないので細かいファイルが増えていき、クエリで参照するときの性能が劣化していきます。
このような細かいデータファイルが増えた状態のままテーブルをコピーすることが、本番実行したいSQLクエリを性能検証する上で大事になってきます。

方法其の一:EXPORT/IMPORT文を使う

バージョン0.8以降のHiveで利用できる、便利な Export・Import文 があります。EXPORT文はそのままのデータファイルの状態で指定のHDFSの場所にファイルコピーするものです。
ずっとデータを溜めるようなHiveテーブルはほとんどの場合、テーブルに幾つものパーティションを作って運用します。長く運用すればするほど、パーティションの数が多くなります。
EXPORTすると、パーティション定義を含めたテーブル定義をEXPORT先に自動的に保持してくれるので、別環境にあるコピー先にIMPORTしたときにテーブル定義を復元してくれるのでとても便利です。
  1. 本番環境の対象のHiveテーブルを、HDFSの/tmp配下などにEXPORT
  2. use xxx;
    EXPORT TABLE xxxxx TO /tmp/xxxxx;
    
  3. EXPORT先をローカルにコピー
  4. hadoop fs -copyToLocal /tmp/xxxxx ./
  5. 開発環境にデータファイルを移動
  6. 開発環境のHDFSの/tmp配下などにローカルからコピー
  7. hadoop fs -copyFromLocal ./xxxxx /tmp/xxxxx
  8. 任意のテーブル名でImport
  9. IMPORT TABLE new_xxxxx FROM /tmp/xxxxx;

Importのバグ?

が、しかし、不思議な現象(バグ?)を発見してしまいました。
3階層のパーティション(cid、mid、log_month)をもつHiveテーブルでEXPORT/IMPORTを試したところ、IMPORTした後のディレクトリ構造が何故かテーブル定義と異なるという現象が・・。
テーブル定義のパーティションキー順序は元と同じなのに、何故かHDFSディレクトリ上では2番目のmidと3番目のlog_monthディレクトリの順序が入れ替わりました。
hive> show create table xxxxx;
OK
CREATE TABLE `xxxxx`(
  ...
)
PARTITIONED BY (
  `cid` int,
  `mid` int,
  `log_month` int)
ROW FORMAT SERDE
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde'
STORED AS INPUTFORMAT
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
  'hdfs://namenode/apps/hive/warehouse/xxx.db/xxxxx'
...
テーブル定義は”PARTITIONED BY (cid int, mid int, log_month int)”となっていますが、
hive> IMPORT TABLE xxxxx FROM '/tmp/tmp_xxxxx';
Copying data from hdfs://namenode/tmp/tmp_xxxxx/contract_id=1/media_id=10/log_month=201602
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000000_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000001_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000002_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000003_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000004_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000005_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000006_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000007_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000008_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000009_0
Copying file: hdfs://namenode/tmp/tmp_xxxxx/cid=1/mid=10/log_month=201602/000010_0
Loading data to table xxx.xxxxx partition (cid=1, log_month=201602, mid=10)
IMPORTの結果では、”Loading data to table xxx.xxxxx partition (cid=1, log_month=201602, mid=10)”と入れ替わっています。

HDP2.3.2(2016/3/31時点)で確認した現象ですが、何か怪しいですね・・。

EXPORT/IMPORTのデメリット

上記の現象で既に問題ありますが、一旦その点は置いておくとして、EXPORT/IMPORTのデメリットはメタデータを抽出したりHDFSに書き出したりしているからか、データファイルコピーがかなり遅いです。
細かいデータファイルだらけになったHiveテーブルの一つのパーティションにある、4422ファイル(合計2.4GB)をExportしたら、試した環境では20分弱くらいかかりました。
なお、これらのデータファイルをhadoopコマンドのローカルコピーすると、20秒くらいでした。
IMPORTにかかる時間もEXPORTと同じくらいで20分弱くらいでした。
データファイルのサイズや数がそれほど大きくないテーブルでは、ディレクトリが入れ替わるのがなくなれば、EXPORT/IMPORTを使うと比較的楽ちんです。

方法其の二:原始的にローカルコピー

もう一つのやり方は、原始的ですが、HiveテーブルのLOCATIONディレクトリをローカルコピーして開発環境に持って行くというものです。
  1. 本番環境の対象のHiveテーブルのLOCATIONを確認
  2. use xxx;
    show create table xxxxx; -- 表示されるテーブル定義のLOCATIONを確認
    
  3. show create tableの結果からCREATE TABLE文を作成
  4. 開発環境の都合でデータベース(スキーマ)が本番環境と異なる場合は、LOCATION句を除外すること
  5. 上記LOCATIONのデータファイルの状況を確認 ※先ず確認しましょう
  6. hadoop fs -du -h  /apps/hive/warehouse/xxx.db/xxxxx/*
  7. データファイルをローカルにコピー
  8. hadoop fs -copyToLocal /apps/hive/warehouse/xxx.db/xxxxx/* ./
  9. 本番環境でテーブル定義を確認し、パーティション作成のALTER TABLE文を作る
    ※以下はAlter文をさっと作るためのサンプルシェル
  10. #!/bin/sh
    TABNAME="xxxxx"
    TARFILE="${TABNAME}.tar"
    OUTPUT="make_partition_${TABNAME}.hiveql"
    
    echo "ALTER TABLE ${TABNAME} ADD" > ${OUTPUT}
    
    tar tvf ${TARFILE} |\
    awk -F"/" {$NF == 5 {print " PARTITION("$2","$3","$4")"}} |\
    sort -u |\
    awk -F"/" {print " PARTITION("$2","$3","$4")"} SQ="'" >> ${OUTPUT}
    
    echo ";" >> ${OUTPUT}
  11. 開発環境にデータファイルを移動
  12. 開発環境で上記のCREATE TABLE文とALTER TABLE文を実施
  13. 作成したHiveテーブルがあるデータベース(スキーマ)のLOCATIONにローカルからコピー
  14. hadoop fs -copyFromLocal ./xxxxx /apps/hive/warehouse/xxx.db/

原始的なローカルコピーのメリット

HiveのEXPORT/IMPORT文に比べて、人の手間はかかりますが、処理時間は速い。

原始的なローカルコピーのデメリット

パーティションがとても多いテーブルの場合、自分で本番環境とそっくりそのまま開発環境に再現しないといけなくて、ミスしやすいのがちょっと難点です。

まとめ

Hiveテーブルでデータファイルが細かくなりすぎて性能劣化したときの調査・検証や、ORCのHiveテーブルでALTER TABLE … CONCATENATEによるマージ処理時間を見積もりたい、といったときは上記のようにデータファイルをそのまま持っていくことをオススメします。
実際、TAXEL byGMO というサービスのシステム開発で、ORCファイルフォーマットを利用してHive(Hive on Tez)を使っていて、上記の方法でALTER TABLE … CONCATENATEの性能見積りを行ったりしました。

最後に

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ