Parse error can be caused due to NUL character included in records with Sqoop

hadoop
この記事は約6分で読めます。

Sqoopを使ってRDBMとHadoop間でデータ転送を行なっている。Sqoopは、MapReduceジョブを実行し複数のMapタスクで並列にインポートやエクスポートを行なう。また、Sqoopはコマンドラインからsqoopコマンドを実行するだけで、データ転送に必要なテーブルのメタ情報の取得やRDBMとHadoop間でデータのやり取りをするインターフェースプログラムの生成やコンパイル、各Mapタスクへのデータスプリット計算などを内部で行ってくれるので、実行も非常に容易である。コマンドラインオプションも豊富に用意されており、多くの場合はそれらの組み合わせで十分対応できる。

Parse Error with Export

で、今回通常どおりSqoopを使ってデータ転送処理を実行していたところ、Sqoop Exportの実行時に以下のようなエラーになってしまった(以下のエラーはローカルで再現させたもの)。成功したMapタスクのデータは転送に成功しているが、失敗したMapタスクのデータは完全に転送されないためデータが不完全な状態になってしまう。parseに失敗しているデータを調べたところ、対象レコードにNUL(\00)文字が含まれていたようだった。そこで、Sqoopの内部動作を追いながら回避方法を模索してみた。

テスト環境

今回の動作テストの環境は以下の通り。

  • sqoop-1.4.5-cdh5.3.3
  • ローカル環境

Sqoop Process Overview

まず、Sqoopの実行プロセス概要は以下のようになっている。RDBMSとHDFS間のデータ転送はMapReduceジョブで(Mapタスク)並列実行される。Mapタスクの数などはオプションで調整することができる。

sqoop-proess

Generated Sqoop ORM Class

Sqoopは、実行時にデータ転送のためのORMクラスを生成する。Sqoop Exportを実行してMySQLへデータ転送する例を挙げ、どのようなORMクラスが生成されるのかを見てみる。

まずは、以下のようなテーブルをMySQLに準備する。

続いてexportコマンドでORMクラスの生成及びexport処理を実行してみる。今回は、ローカル環境でコマンドラインでなくIDE上でJavaから直接Sqoopクラスをコールする形で実行した。

実行するとoutputディレクトリに以下のファイルが生成される。

employee.javaがORMクラスになり中身は以下のとおり。

Export処理でのエラーを探る

今回エラーとなったNUL文字を含むデータとして以下のようなサンプルデータを用意した。^A^@は制御コードを示している。

入力データ

レコードのparse

ソースの中でExceptionがスローされたのは以下の箇所。ソース中のコメントにも書いているが、レコードを行単位で処理していく(入力データは標準的なテキストファイルを想定)際にレコードをparseしてフィールドのリストとして返すところで、メソッドから返されたリストの要素数がテーブルメタ情報から取得したカラム数と一致していないことでエラーとなっていたもよう。__loadFromFieldsメソッドでIteratorを使って要素にアクセスするが、リストのフィールド数がカラム数より小さいため、リストの終端を超えた要素アクセスが発生しjava.util.NoSuchElementExceptionがスローされている。

さらに、RecordParser#parseRecordを追ってみる。parseRecordはステートマシンで実装されており、行レコードをパースしてフィールドのリストに分解する。以下は、メソッドの処理を簡略化したものになる。

上記のステートマシンの状態遷移図を書くと以下のようになるだろうか。

この状態遷移図をもとに、今回ParseErrorとなったレコードの遷移を見てみる。

NUL文字はenclosingCharのデフォルト値として使われているため、今回のようにレコードにNUL文字が含まれていた場合、本来FILED_START -> UNENCLOSED_FIELDと遷移することを期待していたが、FIELD_STARTの状態時にENCLOSED_FIELDに遷移する入力(enclosingCharトークン)が出現したため期待と異なる状態へ遷移してしまっていた。この結果、最終的にListとして返されたデータは以下のようになっていた。

不正文字を含むレコードのParseErrorへの対処

ORMクラス内で文字列置換

なかなか難儀だが、自動生成されたORMクラス内でparseRecordされる前に置換する方法。修正したソースを事前にコンパイルして、exportやimportコマンド実行時に--jar-fileオプションで渡してやる。

enclosingChar、escapeCharの変更

もう1つは、極力sqoopの範囲で対応してあげる方法があるだろうか。デフォルトではNUL文字となっているenclosingCharやescapeCharを処理上影響のない文字に変更する。Exportの場合は、--input-escaped-by--input-optionally-enclosed-byで指定できる。例えば以下のようになる。

実行するとmysqlには以下のようにデータが入っていることが確認できた。

例えば、RDBMSからHDFSへ--hive-drop-import-delimsオプション付きでhiveインポートして、さらに別のサービスで同テーブルデータを使いたくexportするような場合には、--hive-drop-import-delimsでドロップされた文字を--input-escaped-byなどのオプションに渡してあげれば、export時にドロップされた文字が出現しないことが保証されるだろう。

https://sqoop.apache.org/docs/1.4.5/SqoopUserGuide.html#_importing_data_into_hive

ということで、レコード内にsqoopのパースに影響のある文字列が含まれる場合は注意する。事前に不正な文字列が除去できているのが望ましいと思う。

参考リンク

  • https://sqoop.apache.org/docs/1.4.5/SqoopUserGuide.html
Hadoop 第3版

Hadoop 第3版

posted with amazlet at 16.07.17
Tom White
オライリージャパン
売り上げランキング: 246,670

コメント

タイトルとURLをコピーしました