hadoop基盤上のetl構築実践例 ~多様なデータをどう扱う?~

Post on 15-Apr-2017

503 Views

Category:

Engineering

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Hadoop 基盤上の ETL 構築実践例

~多様なデータをどう扱う?~

2016/05/26 D&S Data Night vol.02株式会社ドワンゴ

共通基盤開発部 数値基盤セクション木村宗太郎( @kimutansk )

https://www.flickr.com/photos/nanoprobe67/5761031999/

自己紹介

• 木村 宗太郎( Sotaro Kimura )• 株式会社ドワンゴ• 共通基盤開発部 数値基盤セクション• Hadoop を核としたビッグデータ基盤の開

発運用• Twitter : @kimutansk

2

アジェンダ

1. niconico の概要2. サービスの特徴3. ETL に求められる要件4. niconico におけるデータ活用方針5. niconico の ETL における課題と対処6. 対処 : 内製バッチジョブフレームワーク7. 対処実施結果8. 今後の改善方針

3

1. niconico の概要

4

MAU 約 900 万ID 発行数 約 5541 万有料会員 約 256 万

ユーザアクション- コンテンツ視聴- コンテンツ投稿- コメント- マイリスト- お気に入り- タグ編集

ユーザアクション

ファミリーサービス

アクセスデバイス

動画静画マンガ電子書籍生放送チャンネルアプリブロマガ

立体大百科市場ニコニ広告実況コモンズコミュニティニュースニコナレ

PCiOSAndroidSP モードXbox One3DSPS4Vii UPS Vita

光 Box+PS Vita TVビエラブラビアTV BoxFire TVLG TVGear VR

1. niconico の概要

5

MAU 約 900 万ID 発行数 約 5541 万有料会員 約 256 万

ユーザアクション- コンテンツ視聴- コンテンツ投稿- コメント- マイリスト- お気に入り- タグ編集

ユーザアクション

ファミリーサービス

アクセスデバイス

動画静画マンガ電子書籍生放送チャンネルアプリブロマガ

立体大百科市場ニコニ広告実況コモンズコミュニティニュースニコナレ

PCiOSAndroidSP モードXbox One3DSPS4Vii UPS Vita

光 Box+PS Vita TVビエラブラビアTV BoxFire TVLG TVGear VR

- 多様なユーザアクション - 多様なファミリーサービス - 多様なアクセスデバイス

 これらが同一ユーザ ID 体系の上で行われる。

2. サービスの特徴

• 概要より、下記の特徴を持つことがわかる。

1. 事業の成長が早い2. 事業の変化展開が速い3. 施策の実施サイクルが早い

6

3. ETL に求められる要件

7

スモールスタートで、将来は巨大データへの対応

1. 事業の成長が早いデータ活用で必要な要素 ETL に求められる要件

高速な分析

各事業内で自前でデータ活用

常にスケールアウト可能

高速なジョブ実行

使いやすい統一されたデータ形式

3. ETL に求められる要件

8

サービスの仕様変更に対する素早い追従

2. 事業の変化展開が早いデータ活用で必要な要素 ETL に求められる要件

多様なデータ間の整合性確保

多様なデータを統一的に活用

仕様変更に容易に追従可能

バージョン/サービス間の差分を吸収

データの一元管理

3. ETL に求められる要件

9

定期的/リアルタイムの様々なタイミングで分析

3. 施策の実施サイクルが早いデータ活用で必要な要素 ETL に求められる要件

多様なツールの活用

リードタイムの短縮

適切な SLA 設定

汎用的なインタフェースでのデータ提供

4. niconico におけるデータ活用方針

10

1. データを一か所に集約• 多様なファミリーサービスとデバイス展開• 多様なユーザアクション• ユーザの行動を捉えるには横断的な分析が必要

2. 誰もがデータ分析• 分析部署だけで全部署のニーズを満たすことは不

可能• 業務で必要な全社員が自前でデータ集計・分析• 分析できる環境や API の提供と教育体制の拡充• データの「民主化」(※)

(※)データを一箇所に集めることでデータ活用の民主化が進んだ話 http://chezou.hatenablog.com/entry/2016/05/05/222046

5. niconico の ETL における課題と対処

11

• 求められる要素は多い

常にスケールアウト可能

高速なジョブ実行

使いやすい統一されたデータ形式

仕様変更に容易に追従可能

バージョン/サービス間の差分を吸収

データの一元管理

リードタイムの短縮

適切な SLA 設定

汎用的なインタフェースでのデータ提供

5. niconico の ETL における課題と対処

12

• 求められる要素は多い

常にスケールアウト可能

高速なジョブ実行

使いやすい統一されたデータ形式

仕様変更に容易に追従可能

バージョン/サービス間の差分を吸収

データの一元管理

リードタイムの短縮

適切な SLA 設定

汎用的なインタフェースでのデータ提供

- 「何か一つを行えば OK」にはならない - 対処を各々実施し、 積み上げていくことで徐々に改善が可能

5. niconico の ETL における課題と対処

13

• 対処(一部現在進行中)その1

常にスケールアウト可能

高速なジョブ実行

使いやすい統一されたデータ形式

常時 Hadoop/Spark ジョブで実施

Spark / Tez に移行

フォーマットをTsv Lzo > Parquet Snappy

非正規化して活用時のJoin を低減

5. niconico の ETL における課題と対処

14

• 対処(一部現在進行中)その2

仕様変更に容易に追従可能

データの一元管理

バージョン/サービス間の差分を吸収

内製バッチジョブフレームワーク

データをカテゴリ分けして集約し、ユーザ毎に権限管理

ジョブをテンプレートから生成する内製ジョブ実行アプリ共通ログ形式を複数提示し、それ以外の管理レベル低減

5. niconico の ETL における課題と対処

15

• 対処(一部現在進行中)その3

リードタイムの短縮

適切な SLA 設定

汎用的なインタフェースでのデータ提供

リアルタイムクラスタ( fluentd & Kafka )の構築

サービスレベルの明確化&文書化

ジョブ実行用 UI / RestAPIの提供

JDBC インタフェースの限定開放

5. niconico の ETL における課題と対処

16

• 今回のお話しする対象

仕様変更に容易に追従可能

データの一元管理

バージョン/サービス間の差分を吸収

内製バッチジョブフレームワーク

データをカテゴリ分けして集約し、ユーザ毎に権限管理

ジョブをテンプレートから生成する内製ジョブ実行アプリ共通ログ形式を複数提示し、それ以外の管理レベル低減

5. niconico の ETL における課題と対処

17

• 今回のお話しする対象

Norikraf luentd& Kafka

scp

Pig / Hive MapReduce

Hive / Impala

内製バッチジョブフレームワーク

MapReduce → Spark

6. 対処 : 内製バッチジョブフレームワーク

18

• 実施したいこと• 仕様変更に容易に追従可能• バージョン/サービス間の差分を吸収

• 現実• ファミリーサービス毎にログ内容が異なる• アクセスデバイス毎にログ内容が異なる• ユーザアクション毎にログ内容が大きく異

なる条件分岐で区切って作成!

結果・・・

https://www.flickr.com/photos/kenyee/2817511001/

6. 対処 : 内製バッチジョブフレームワーク

20

• ログ変換は下記の流れに概ね抽象化可能Input(Read)

Decode

ParseConvert/

FilterFormat

EncodeOutput(Writ

e)

ファイル入力

圧縮ファイルの解凍

入力ファイルパース

変換/フィルタ

出力ファイルフォーマット

ファイルの圧縮

ファイル出力

6. 対処 : 内製バッチジョブフレームワーク

21

• 各パーツをコンポーネント化し、設定で差し替え/組み合わせ可能にすれば?• 設定で処理が決まるので仕様変更に追従可

能• データの名前やフォーマットの差分に対応

可能• データの内容の差分も設定で吸収可能

• MapReduce / Spark両バージョンは必要• MapReduce は遅いが設定が少なく安定• Spark は早いが設定が多くデータ増加で不安定• 共通のドメインロジック部/ユーティリティは共

ただし・・・

6. 対処 : 内製バッチジョブフレームワーク

22

• 下記のように実現• 設定は起動時の引数で指定(例)

• 実行メインクラス (App) は処理モデル毎• ファイルを読み込んで変換するのみのジョ

ブ• 重複除去を行うジョブ• 他データとの Join を行うジョブ

• MapSideJoin 、 ReduceSideJoin は別個作成

$ /opt/spark/bin/spark-submit --master yarn-client --class LogApp --conf spark.input_compression_codec=lzo --conf spark.converter_classes=TimeRangeFilterConverter, ReqTimeUnifyConverter ......

6. 対処 : 内製バッチジョブフレームワーク

23

• 起動時引数は下記のように組み合わせるNo フェーズ 設定項目例

1 Input HDFS 上のパス(数は任意)

2 Decode 入力時の圧縮形式( uncompress 、 Lzo 、 BZip2 etc )

3 Parse 入力フォーマット( Ltsv 、 Tsv )入力スキーマ定義パス(独自 Json 形式)

4 Convert&Filter

適用する Converter (数は任意)Converter 例:時刻範囲のフィルタ、必須カラム補足、時刻形式統一 …

5 Format 出力フォーマット( Parquet 、 Tsv )出力スキーマ定義パス( AvroSchema )

6 Encode 出力時の圧縮形式( Snappy 、 Lzo etc )

7 Output HDFS 上のパス出力ファイル数

6. 対処 : 内製バッチジョブフレームワーク

24

• MapReduce / Spark 共用のクラス例• 入力スキーマを読み込み&オブジェクト化• スキーマを用いて入力データをパース• AvroSchema を用いて出力データ生成• ローカル/ HDFS の透過的なファイル読み込み

• 起動時引数のパーサ• テスト用ユーティリティ• 他ドメイン固有ロジッククラス

6. 対処 : 内製バッチジョブフレームワーク

25

• MapReduce の設定適用例• Driver で

Input/Decode/Format/Encode/Output を適用// 起動時引数から各種設定値を取得(各種変数に設定)

~~~~~~~~~~~~~~~~~~~~~~~~

// 実行用ジョブを生成String jobName = conf.get(ArgKey.JOB_NAME, "mapreduce");Job job = new Job(conf, jobName);

// 出力ファイル数を設定job.setNumReduceTasks(numReduceTasks);

// Mapper クラスと関連クラスを設定job.setMapperClass(mapperClass);job.setMapOutputKeyClass(Text.class);job.setMapOutputValueClass(NullWritable.class);

// Reducer クラスと関連クラスを設定job.setReducerClass(reducerClass);job.setOutputKeyClass(Text.class);job.setOutputValueClass(Text.class);

6. 対処 : 内製バッチジョブフレームワーク

26

• MapReduce の設定適用例• Driver で

Input/Decode/Format/Encode/Output を適用// 入力時圧縮形式、入力パスを設定

job.setInputFormatClass(inputFormatClass);FileInputFormat.addInputPaths(job, inputPaths);

// 出力時圧縮形式、出力パスを設定job.setOutputFormatClass(outputFormatClass);FileOutputFormat.setOutputCompressorClass(job, compressOutputCodec);FileOutputFormat.setCompressOutput(job, true);FileOutputFormat.setOutputPath(job, new Path(outputPath));

6. 対処 : 内製バッチジョブフレームワーク

27

• MapReduce の設定適用例• Mapper で Parser/Convert&Filter/Format

を適用// 初期化メソッドでスキーマ、 Parser 、 Converter群を生成public void setup(Context context) throws InterruptedException, IOException { Configuration conf = context.getConfiguration(); // Converter を続けて適用する親 Converter を生成 converter = new MapReduceMessageConverter(conf); // Parser を生成 this.lineParser = ParserFactory.createParser(conf); // 入力スキーマを生成 String inputSchemaStr = context.getConfiguration().get(ArgKey.INPUT_SCHEMA, ""); this.inputSchema = MappingSchemaCreator.createSchema(inputSchemaStr); // 出力スキーマを生成 String outputSchemaStr = context.getConfiguration().get(ArgKey.OUTPUT_SCHEMA, ""); this.outputShema = new Schema.Parser().parse(outputSchemaStr);}

6. 対処 : 内製バッチジョブフレームワーク

28

• MapReduce の設定適用例• Mapper で Parser/Convert&Filter/Format

を適用// Map メソッドで Converter 、 Format を適用public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 入力データを Parser でパース Map<String, String> recordMap = this.lineParser.parseWithSchema(value.toString(), this.inputSchema);

// データに順次 Converter を適用 recordMap = converter.convert(recordMap, conf); if (recordMap == null) { return; }

// 出力スキーマを適用して出力 String output = AvroSchemaApplier.format(recordMap, this.outputFormat, this.outputShema); context.write(new Text(output), NullWritable.get());}

6. 対処 : 内製バッチジョブフレームワーク

29

• Spark の設定適用例• 実行アプリケーション (App)中で

各種起動時設定を読み込み、適用// 起動時引数から各種設定値を取得(設定 Mapオブジェクトを生成)val sparkConf = new SparkConfvar customConf = SparkConfigAdapter.createBaseAppConf(sparkConf)

// SparkJobContext を生成val sparkContext = new SparkContext(sparkConf)val sqlContext = new SQLContext(sparkContext)val appConf = sparkContext.broadcast(customConf )

// パス、コーデックを指定して変換対象ファイルを読み込むval readLineRDD = SparkInputAdapter.readCompressedText(inputPaths, sparkContext, appConf.value)

// 重複除去、出力ファイル数を設定val distinctRDD = readLineRDD.distinct(appConf.value("output_file_num").toInt)

// 読み込んだファイルを変換するval convertedRDD = parseConvertRDD(distinctRDD, appConf.value)

6. 対処 : 内製バッチジョブフレームワーク

30

• Spark の設定適用例• 実行アプリケーション (App)中で

各種起動時設定を読み込み、適用// Parquet出力の場合は DataFrame を介して出力、それ以外の場合は RDD のまま出力if (appConf.value.getOrElse("output_file_format", "csv").toLowerCase().equals("parquet")) { val dataFrame = SparkMessageFormatter.convertMapRddToDataFrame(sqlContext, convertedRDD, appConf.value) SparkOutputAdapter.writeParquetDataFrame(outputPath, dataFrame)} else { val outputRDD = SparkMessageFormatter.convertMessageToText(convertedRDD, appConf.value) SparkOutputAdapter.writeCompressedText(outputPath, outputRDD, appConf.value)}

7. 対処実施結果

31

• 内製バッチジョブフレームワークに組み込んだ結果• 上手くいった点

• 複数パターンのデータ変換が引数のみで構成出来た

• 内製ジョブ実行アプリと組み合わせて一部の設定値更新のみで複数パターンのジョブを発行できた

• 残っている課題• 引数としてタブ文字などの一部文字が指定できな

い• 引数に複雑さが移行しているため、引数と実行機能に対するドキュメントが別途必要

8. 今後の改善方針

32

• 今後は下記の改善対応を行う方針• チューニング自動化

• ジョブ実行前にリソース量/出力ファイル数を算出

• カテゴリ毎に作成しているジョブの共通化• 起動時引数の指定方法の簡略化• ドキュメント整備自動化

• 入力/出力スキーマファイルからドキュメント自動生成

まとめ

33

• ETL に求められる要件は多い• 何か一つを行えばいいということにはなら

ない• 対処を一つ一つ実施し、積み上げていく必

要有• 「銀の弾丸」は欲しいが、多分ない。

• 対処:内製バッチジョブフレームワーク• ログ変換の各フェーズを起動時引数で構成• 柔軟性は増したが、

特殊文字やドキュメントなど課題は残る• チューニングやドキュメントの自動化など

今後も改善対応を継続予定

ETL 改善の積み重ねは高く険しい

We have yet to finish improving ETL.

https://www.flickr.com/photos/nanoprobe67/5746249953/

top related