さて、ここまではリレーショナルデータベースとHadoopについて比べながら見てきましたが、ここであらためてHadoopのメリットについてまとめてみましょう。Hadoopには以下のようなメリットがあります。
それぞれについて細かく見ていきます。
これは何度か説明してきたことですが、Hadoopは分散処理のフレームワークです。Hadoop登場以前は“神懸かった”エンジニアしか扱えなかった分散処理が、Hadoopの登場によって“普通の”エンジニアであっても扱える技術となったのです。
分散処理が扱えるようになったことで、これまでは持っていても処理することができず捨てるしかなかった大量のデータがきちんと活用されるようになり、そこからビジネスに役立つパターンを発見することもできるようになったのです。もちろん、1台のサーバでは現実的な時間で処理できなかったような大規模なデータ解析が、現実的な時間で処理できるようになり、大幅なコスト削減も可能となりました。
さらに、分散処理を行う際には以下のような点を考慮しないといけないために難しいとされますが、Hadoopを使うことでこういった点についてユーザーが考慮する必要はほとんどなくなりました。そういった処理の本質ではない部分についてはフレームワークであるHadoopが面倒を見てくれるため、ユーザーは本質的な処理であるMapperとReducerというたった2つのスクリプトを作成する部分だけに集中することができるのです。
スケーラブルであるという点も非常に重要です。スケールアウトで処理性能を上げていくのはコスト的にも安価で済むという話はしましたが、Hadoopはスケールアウトを前提として設計されており、サーバ台数を増やしていけば処理性能をほぼ線形に上げていくことができます。そのため、データサイズがどんどん大きくなったとしてもプログラミング側の対応が必要なく、基本的にはサーバ台数を増やして処理性能を上げることで対処することができます。
これはスケールアップと比べてコスト的にもメリットですが、データが今後増えていっても処理できるという安心感があるというのが非常に大きいのではないかと筆者は考えています。例えば今後データが10倍、100倍と増えていくとします。最近のWebサービスはアクセスが急増することも多いため、そういった可能性があるでしょう(図2-9)。
そういった場合のログ解析など、現在はリレーショナルデータベースで処理できているとしても今後はどうでしょうか。メモリが足りなくなってスワップが発生して、現実的な時間では処理できなくなるかもしれません。そういったときにスケールアップして解決することも選択肢の1つですが、それには金銭的に大きなコストがかかります。ほんのちょっとの性能を上げるために値段が倍以上かかるようなことになったら果たしてどうでしょうか。CPUやメモリの値段を考えると分かると思いますが、あるブランドの2GBのメモリに対して、4GBのメモリを買おうとしたら、2倍の値段では買えません。性能を上げていくのは非常にコストがかかるのです(図2-10)。
その点、Hadoopを利用するのであれば、データ量が10倍、100倍と増えていったとしても基本的にはそれほど高価ではないサーバ台数を増やすだけで対応可能なのです。サーバ台数を増やせば処理時間をほぼ一定に保つこともそれほど難しくないでしょう。今後データサイズが大きくなるとしても、スケールアウトを前提とした設計であれば安心して利用できます(図2-11)。
また、例えば検証のためにある数字を追いかけるとします。今後の数字は日時のバッチなどで定期的に取ればいいとして、過去の数字についてはどうしたら良いでしょう。例えば新機能を開発する時など、こういった過去の数字を基準もしくは判断材料として利用することも多いかと思います。そのためにも、こういった数字はできるだけ早く算出する必要があるでしょう。
しかし、Hadoopが使えない環境だと過去のデータは既にアーカイブされていたり、処理にかかる時間やサーバの負荷などの問題であらためて処理するのは難しかったりする場合もあるのではないでしょうか。
そういった場合にHadoopが使える環境があれば、生データさえあればいつでもすぐにデータ分析が可能です。さらに、サーバ台数によって処理性能を向上させることが可能なので例えデータサイズが大きくなったとしてもサーバ台数さえ増やせば短時間で処理することが可能です。これはHadoopの大きなメリットの1つだと言えます。
あまり知られていないかもしれませんが、これがHadoopのメリットの中でも非常に重要なものだと思います。MapReduceはMapフェーズ、Shuffleフェーズ、Reduceフェーズの順で処理されていきます。このうち、Shuffleフェーズで行われているのがここで説明するShuffle & Sortです。
Mapperの出力(keyとvalueがタブ区切り)がShuffleフェーズに渡されてShuffle & Sortされます。その結果、Reducerには同じkeyのデータは必ず同じReducerに、しかも並び替えされた状態で渡されるため、その後の処理がものすごく楽になるのです。
なぜ同じkeyのデータが必ず同じReducerに渡されるのでしょう。実は、Mapperで出力されたkeyのハッシュ値を計算し、それをReducerの数で割った余りでどのReducerに割り振るかを決めています(Shuffle)。これはsrc/mapred/org/apache/hadoop/mapreduce/lib/partition/HashPartitioner.javaで定義されたgetPartitionメソッドで処理されています。
package org.apache.hadoop.mapreduce.lib.partition; import org.apache.hadoop.mapreduce.Partitioner; public class HashPartitioner<K, V> extends Partitioner<K, V> { public int getPartition(K key, V value, int numReduceTasks) { return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks; } }
そしてReducerに集められたデータがReducer内で並び替えられます(Sort)。この一連の処理がShuffle & Sortです。
では、Shuffle & Sortは何がすごいのか具体例で考えてみます。例えば、ユーザーIDのユニーク数を計算するような場合を考えてみましょう。リレーショナルデータベースで簡単には処理できないくらいのデータサイズになっていると想定します※2。
※2:リレーショナルデータベースで扱えるサイズならば、リレーショナルデータベースで扱えば良いですからね
Rubyなどのスクリプトでユニークユーザー数をカウントする処理をしたい場合、毎回ユーザーIDが既に記録されているかどうかをチェックし、記録されていなければそのユーザーIDを記録する、というような処理が考えられます。
サンプルデータとしてこのようなデータを考えます。
1,aaaa,bbb,ccccc 1,aaaa,bbb,ccccc 4,aaaa,bbb,ccccc 5,aaaa,bbb,ccccc 4,aaaa,bbb,ccccc 2,aaaa,bbb,ccccc 1,aaaa,bbb,ccccc 3,aaaa,bbb,ccccc 2,aaaa,bbb,ccccc 2,aaaa,bbb,ccccc 1,aaaa,bbb,ccccc 5,aaaa,bbb,ccccc 2,aaaa,bbb,ccccc 6,aaaa,bbb,ccccc 2,aaaa,bbb,ccccc
このデータに対して、ユニークユーザー数をカウントする処理の例がこちらです。
user_id_list = [] ARGF.each_line do |line| line.chomp! # カンマ区切りで先頭がユーザーIDだとします user_id = line.split(',')[0] unless user_id_list.include?(user_id) user_id_list << user_id end end puts "UU数は#{user_id_list.size}です"
つまり、ユーザーIDをどんどんメモリ上に(上の例で言えばuser_id_listという変数の中に)保持していくわけです。この処理でネックになるのがこの部分です。
user_id_list << user_id unless user_id_list.include?(user_id)
user_id_listの中にたくさんのユーザーIDが保存されていくと、毎回新しいユーザーIDがその中に含まれているかどうかをチェックすることになります。その処理がメモリ内で行えるうちは高速に処理できますが、メモリ内で収まらないようになってくると非常に時間がかかる処理となってしまいます。そうなってくると、どれだけメモリをたくさん積んだサーバを使うかという問題になってしまい、結局はそのサーバのメモリ量に依存してしまいます。
その点、HadoopではShuffle & Sortがすばらしい働きをしてくれます。同じ例を今度はHadoopで処理してみましょう。
例えばMapperでユーザーIDをkeyとして出力します。valueはこの場合利用しないので何でも大丈夫です。そうするとShuffle & Sortによって、同じユーザーIDのデータは必ず同じReducerに渡されます。しかもReducerではそれらのデータが並び替えられている(つまり、同じユーザーIDは必ず連続している)状態です。データは1行ずつReducerに渡され、「このユーザーIDは直前のユーザーIDと同じかどうか」だけをチェックすることでユニーク数がカウントできます。直前と同じだったら無視、違っていたら新たなユーザーIDとしてカウントすればいいのです。これであれば直前のユーザーIDだけをメモリ上に保持すればいいことになり、ほとんどメモリを使用しないことが分かると思います(図2-12)。
要は並び替えがされていることが素晴らしいのですが、単なる並び替えもデータサイズが増えてくるとデータがメモリ上に載りきらなくなるなど、1台で行おうとすると難しくなります。その点、Hadoopであれば並び替えは各Reducerで行っています。Reducerの数を増やせばそれだけ各Reducerが並び替える必要のあるデータサイズが小さくなるため、並び替えにかかるコストが小さく抑えられる点も見逃せません(図2-13)。
オープンソースであることもHadoopのメリットであるといえます。Hadoopはオープンソースで実装されているため、そのソースは誰でも見ることができます※3。そのため、何か問題があってもいざとなればソースを読んだり、問題があった場合には修正したりといったことが可能なのは心強いでしょう。筆者も以前、Clouderaのディストリビューションを使っていたときにはソースを一部分書き換えて使っていました※4。これもオープンソースで公開されているが故にできることです。
※3 そう、あなたも見ることができます
※4 http://blog.livedoor.jp/sasata299/archives/51435212.html
HadoopはJavaで書かれたフレームワークのため、Javaで処理を記述するのが最も一般的です。ただし、Hadoop Streamingという仕組みが提供されており、Java以外にもRuby、Perl、Python、PHP、Bashなど標準入出力を持つ言語であればあらゆる言語でMapper、Reducerの処理を書くことができます(図2-14)。
さらに、Hive、PigといったDSL(Domain Specific Language、Java,C#などの汎用言語とは違い、ある特定の種類の問題に特化した言語)も提供されています。HadoopStreaming、Hive、Pigに関しては次回以降で解説します。
方法がいくつも提供されているので、まずなんといっても取っ付きやすいのではないでしょうか。自分の得意な、扱いやすい言語で処理が書けるというのはうれしいですね。
Copyright © ITmedia, Inc. All Rights Reserved.