Transcript

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2013

JAVA TECH

49

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer /

ペタバイト規模のデータを処理するプログラムを記述

したい場合に良い方法があります。Apache Hadoop は、複数の汎用コンピュータで構成されるクラスタを記憶域として利用し、大量のデータ・セットの大規模パラレル処理を実行するための、Javaで記述されたプラットフォームです。記憶域の側面については、Hadoop 分散ファイル・システム(HDFS)が担当します。HDFS は、Google File System(GFS)をベースとした分散ファイル・システムです。本記事ではもう一方のパラレル処理に着目します。パラレル処理はMapReduce内で実行されます。MapReduceは、コードを分散して実行するためのプログラミング・モデルおよび実装であり、分散システムの詳細を

意識しないプログラミングを実現します。Javaプログラマは、Hadoop のMapReduce API を利用してペタバイト規模のデータ・セットの処理や変換を行い、そこから洞察を導くようなプログラムを記述できます。

MapReduce パラダイムMapReduceプログラミング・パラダイムはシンプルで、処理を2つの関数に分割し、それぞれの関数において、データのサブセットに対する処理をクラスタ

全体にわたって多数回パラレルで実行するというものです。マップ関数は、キーと値のペアの形式で入力データを受け取り、0組以上のキーと値のペアを設定できます。このマップ関数がそれぞれの入力レコードに対して呼び出された後、キー

別にソートされたマップ関数の出力値がリデューサに送信されます。次に、リデューサのリデュース関数が、一意のマップ出力キーごとに1回呼び出され、そのキーに対応するマップ関数の出力値のリストが渡されます。その後、リデュース関数は、独自に作成した 0組以上のキーと値のペアを出力できます。

ユースケースカリフォルニア州交通局(Caltrans)の Performance and Measurement System(PeMS)は、州の高速道路に配備されたセンサーから収集した詳細な交通量データを提供するシステムであり、更新情報を30 秒ごとに受信します。ロサンゼルス地域だけでも、地域内の全高速道路に計 4,000 個を超えるセンサー装置があります。明らかに膨大な量のデータになりますが、MapReduceでクラスタを利用することで、妥当な時間内にデータを処理できます。

本記事では、週内のある時間帯における各センサー装置の平均交通量をそれぞれ計算する、単純なMapReduceプログラムを作成します。このプログラムにより、その地域の交通量に関する統計データや、もっとも交通量の多い場所の情報を得ることができます。また、このデータに基づいて、別の統計情報(標準偏差など)の確認や、ある時刻と場所の交通量と典型的なパターンとの相関性の調査など、さらに詳細な分析を行うこともできます。

入力データPeMSでは 30 秒ごとのデータを利用できますが、本記事のサンプルではそのような細かい粒度のデータは不要です。したがって、サンプルでは、PeMSでも公開されている5分ごとの集計結果を使用します。4,370 個の装置があるため、計算する平均値の個数は 1週間で、4,370 * (60/5) * 24 * 7 = 8,809,920 件に

交通量データの解析による MapReduceプログラミングの学習ペタバイト規模のデータを処理するプログラムを記述する

SANDY RYZA

スケーラビリティMapReduceを使用すれ

ばするおとで、5件の入

力レコードを扱いうプロ

グラムに、さらに及ぶ入

力レコードにも対応でき

るスケーラビリティを持

たせることができます。

Sandy Ryza:Clouderaのプラットフォーム・チームに属するソフトウェア・エンジニア。同チームではMapReduceおよびリソース管理を担当。ブラウン大学を最近卒業したが、大学在籍中は、MapReduceを使用した車両の経路決定に関する研究に従事

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2013

JAVA TECH

50

COMMUNITY

JAVA IN ACTION

ABOUT US

blog

//polyglot programmer /

なります。各入力データ・ファイルには、全装置に関する1か月分の計測データが含まれています。入力データ・ファイルの各行は、装置 ID、時刻、装置に関する情報、およびその期間にその装置から取得した計測データで構成されます。リスト1にデータ行の例を示します。関係するフィールドは、1列目(時刻)、2列目(装置 ID)、および 10 列目(その期間のその装置における正規化された車両数)です。

ジョブサンプルのマッパーでは、入力行を解析し、各行についてキーと値のペアを出力します。ここで、キーは装置IDと週のはじめからの経過秒を連結した ID、値はその特定の時間にそのセンサーの下を通過した車両数です。リデュース関数は、呼び出されるたびに、1つの装置、1つの週のはじめからの経過秒、およびすべての週の車両数の値を受け取って、車両数の値の平均を計算します。リスト2にマッパーの例を示します。ジェネリック型の引数には、マッパーの入力に使用するキーのクラスと値のクラス、およびマッパーの出力に使用するキーのクラスと値のクラスを指定します。ここで、引数のクラスが String 型でも、Integerや Double などの一般的な

Javaプリミティブ型のラッパーでもない点に注意してください。この理由は、キーと値を表すクラスではWritable インタフェースを実装する必要があるためです。Writable インタフェースには、キーと値のペアをネットワーク経由でバイト・データとして送信するためのシリアライズ用のメソッドが定義されています。 サンプルでは、出力のキーとして、センサーの装置 IDと週のはじめからの経過秒とを連結した IDを保持するText を使用します。また、出力の値として、通行車両数を保持するIntWritable を使用します。入力用のクラスは、後で使用する InputFormat(サンプルでは TextInputFormat)により、部分的に影響を受けます。TextInputFormat は入力ファイルを各行に分割し、その各行を1レコードとして渡します。このレコードは、ファイル内のバイト・オフセット値を含む

LongWritable キーと、その行の内容を含む Text値により構成されます。リスト3にリデューサの例を示します。サンプルのリデューサの入力型は、マップ関数の出力型と一致している必要があります。また、リデューサはマップ関数と同じキーを出力しますが、各キーにDoubleWritable 型の平均値を付加します。最後に、サンプル・プログラムを実際に実行す

る必要があります。そのためには、ジョブをセットアップするドライバを実行し、そのジョブをクラスタに登録します。サンプル・コードは多数のマシン上で実行されるため、クラスタ全体にコードを分散配置できるように、コードが含まれる JARファイルについてHadoop に通知する必要があります。

setJarByClassメソッドに、JARファイル内に含まれるクラスを渡すことで、この JARファイルを指定できます(リスト4)。JARファイルの指定以降、MapReduce はマッパーに送信する入力データの分割処理を行い、クラスタ内の各マシン上でマッパーの実行を開

すべてのリストのテキストをダウンロード

01/01/2012 00:00:00,312346,3,80,W,ML,.491,354,33,1128,.0209, 71.5,0,0,0,0,0,0,260,.012,73.9,432,.0273,69.2,436,.0234,72.3,,,,,,,,,,,,,,,01/01/2012 00:00:00,312347,3,80,W,OR,,236,50,91,,,,,,,,,91,,,,,,,,,,,,,,,,01/01/2012 00:00:00,312382,3,99,N,ML,.357,0,0,629,.0155,67,0,0,00,0,0,330,.0159,69.9,299,.015,63.9,,,,,,,,,,,,,,,,,,01/01/2012 00:00:00,312383,3,99,N,OR,,0,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,01/01/2012 00:00:00,312386,3,99,N,ML,.42,0,0,1336,.0352,67.1,0,0,0,0,0,0,439,.0309,70.4,494,.039,67.4,403,.0357,63.4,,,,,,,,,,,,,,,

リスト1 リスト2 リスト3 リスト4

データからの洞察Javaプログラマー

は、Hadoop の

MapReduce API を

利用して、の処理や変換を行い、そこから洞察を導くようなプログラムを記述できます。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2013

JAVA TECH

51

COMMUNITY

JAVA IN ACTION

ABOUT US

blog

//polyglot programmer /

始し、リデューサに対してマッパーの出力データの取得場所を通知し、最後にリデューサの出力をファイル(通常はHDFS)に書き込みます。

付加的なデータの受け渡し分割してマッパーに送信する入力データのほかにも、何らかのジョブ固有の設定オプションをマップ関数に渡して、付加的なタスクを減らしたい場合があります。MapReduceでは、このような設定情報の受け渡しが非常に容易です。作成するドライバ内で、取得した設定オプションに対して任意のオプションを設定できます(リスト5)。その後、マッパーやリデューサ内で、リスト6のようなコードを使用してその設定値を取り出すことができます。

カウンタ現実世界のデータは混沌としています。たとえば、交通量センサーのデータにはフィールドが欠落しているレコードが含まれることも多く、現場のセンサーは時折故障するものです。そのため、サンプルのMapReduceジョブの実行中に、このジョブの実行状況に関するメトリクスを測定し収集できれば便利です。 1 台のコンピュータで稼働するプロ

グラムであれば、カウント変数を追加して対象のイベントが発生するたびに増分し、最後にその値を出力するだけ

で済みます。しかし、コードが分散環境で実行される場合、そのような変数を使用したカウント値の集計は非常に困難です。幸いなことに、Hadoopにはカウンタを使用してこのような集計処理を行うメカニズムが用意されています。MapReduce には多数の組込みカウンタがあり、これらのカウンタはMapReduceジョブの完了

時にコンソールに出力されます。

ジョブごとおよびタスクごとの両方において、このカウンタ情報をWeb UI で確認することもできます。また、カウント対象の値を含まないレコードが検出されるマッパー内の位置にリスト7のような行を追加するだけで、独自のカウンタを使用できます。これにより、ジョブの完了時に、組込みカウンタとともに独自のカウンタによるカウント値も出力されます。

通常は、マップ関数またはリデュース関数の全体を try/catchブロックで囲み、catchブロック内でカウンタを増分するという方法が便利です。この

場合、例外クラスの名前を、発生するエラーの種類を表すカウンタ名として使用します。

テストプログラミングやデバッグの最中にクラスタにアクセスしていては、クラスタでのMapReduceプログラムの実行に時間がかかります。しかし、基本的なロジックの動作を確認する目的では、クラスタ全体にコードを分散して実行するためのマシンをすべて用意する必

要はありません。 Apache MRUnitというオープンソースのプロジェクトでは、MapReduceプログラム用の JUnit テストを極めて簡単に記述できます。Apache MRUnitを利用することで、作成したマッパーやリデューサを個別にテストすることも、全体のフローの中でテストすることもできます。リスト8に、サンプルのマッパーが期待どおりに動作することを検証するためのテストを示します。

Map-Reduce Framework Map input records=10 Map output records=7 Map output bytes=175 …

Averager Counters Missing vehicle flows=2329

すべてのリストのテキストをダウンロード

conf.set("my.made.up.configuration.option", "the.value");

リスト5 リスト6 リスト7 リスト8

シンプルなパラダイムMapReduce は、処理を

に分割し、それぞれの関

数において、データのサ

ブセットに対する処理を

クラスタ全体にわたって

多数回パラレルで実行す

るというものです。

ORACLE.COM/JAVAMAGAZINE ///////////////////////////// JULY/AUGUST 2013

JAVA TECH

52

COMMUNITY

JAVA IN ACTION

ABOUT US

blog

//polyglot programmer /

まとめ本記事ではMapReduceを使用して、Caltrans PeMS の高速道路の交通量データ・セットを調査し、ある週内のある時間における平均交通量を計算するアプリケーションを作成しました。アプリケーション固有の設定オプションをタスクに渡す機能や、実行時のグローバル統計情報を収集する機能など、MapReduce API の高度な機能についてもいくつか取り上げました。最後に、MRUnit を使用してサンプルのMapReduceプログラム用の単体テストを記述する方法について学習しました。 MapReduceを使用することで、5件の入力レコードを扱うプログラムに、5兆件に及ぶ入力レコードにも対応できるスケーラビリティを持たせることができます。Hadoop のMapReduce API により、そのようなプログラムをPure Javaで容易に開発できます。MapReduce は非常に強力な機能で

すが、低レベルに感じられることもあり、結合処理やグループ別の集計処理などの一般的な操作のためにコードを記述する必要もあります。また、1つのタスクを複数のMapReduceジョブの順次処理で構成し、あるジョブの出力を次のジョブの入力とすることが必要にな

る場合もたびたびあります。Apache Crunchプロジェクトは、MapReduceジョブのパイプラインの記述を非常に容易にする高度な Javaライブラリを提供しています。Crunch はタプル指向のため、複雑なデータ型の操作がはるかに容易になり、複雑なデータ型の受け渡しも容易です。</article>

LEARN MORE

• Google のオリジナルのMapReduce に関する論文:『MapReduce: Simplified Data Processing on Large Clusters』

• Apache Hadoopで単純なMapReduceジョブを記述、コンパイル、実行する方法を説明したブログ記事

• Apache Crunch: A Java Library for Easier MapReduce Programming』

今後のヒントは、MapReduceジョブのパイプラインの記述を非常に容易にする高度な Javaライブラリを提供しています。


Recommended