Home

blog.katsuma.tv

楽天テクノロジーでLT&デモをしてきました

少し前の情報になるのですが、楽天テクノロジーカンファレンスTokyo-JoggingネタのLT&デモを行ってきました。

当日は、WiiリモコンだけじゃなくてバランスWiiボードと、最近対応したヌンチャクのデモも行いました。やや古いネタではあるものの、大勢の方に実際に体験いただいて、アドバイスや意見などいただけて楽しい時間を過ごすことができました。

ただ1つ残念だったのはデモブースに常駐してないとダメだったので、他の方の発表がまったく聞けなかったこと。。楽しそうな話いっぱいあったのでそこが残念でした。と、思ったら発表内容はUstreamで残っているみたいなので、あとで確認したいとおもいます。

また、ありがたいことにデモ投票では3位(!)の結果をいただけることになりました。LTでは時間切れになっちゃって消化不良だったわけですが、デモで実際に体験いただけたのがよかったのかなとおもいます。

最後に、貴重な場所と機会を提供いただいた楽天株式会社 開発部の方々、どうもありがとうございました!

Hive QL(HQL)でORDER BYするときの注意点

HiveでのSQLことHQLの小ネタ。HQLでは基本的にSQLはほぼ完璧に利用できますが、たまにハマりポイントもあります。その1つが並び替えのORDER BY。

ORDER BYとSORT BY

HQLの文法的にORDER BYは有効ですが、実際は並び替えは行われません。(無視されているような感じ)Hiveでは代わりに「SORT BY [column]」を利用することになります。

ただし、ここでも罠があって、SORT BYは結果がreducerの数に依存します。(各reducerがsort処理をしたものがマージされるものになるので、全体としてはおかしな結果を得ることになります) 通常、reducerは複数走っているはずなので、結局SORT BYを利用してもORDER BYと同等の結果を得ることができません。

では、どうするか?と言うと明示的にreducerの数を1に指定してからSORT BYを実行すればOKです。

set mapred.reduce.tasks=1;
SELECT key, value FROM table_name SORT BY key;

また、Hiveシェルを使わない場合は1ライナーで。

hive -e 'set mapred.reduce.tasks=1;SELECT key, value FROM table_name SORT BY key;'

HQL最適化

ただ、これだとreducerの数がボトルネックになって、SORT BYと条件文などが組合わさると途端に処理が遅くなることがあります。

なので、このような条件文付きSORT BYを実行するときは、

  1. 複数のreducerが条件文つきHQLでデータを整形し、中間テーブルAを作成
  2. 1つのreducerが、中間テーブルAを対象にSORT BYし、条件文は付けない

な、方針でHQLを分割して実行すると、高速に動作できるかと思います。

ちなみに、このテーブルを分ける(カラムを厳選した中間テーブルを作成する)のはHQLの最適化でかなり有効で、SORT BY以外でもかなり有効なケースが多くあります。このあたりの最適化の話は、また別途まとめたいと思います。

HiveのmetastoreをMySQLを使ってLocal Metastore形式で利用する

前回、紹介したHiveについての続き。

Hiveは内部で扱うメタデータを「metastore」というデータで保持しています。テーブルやパーティションなどの情報、またレコードが実際に保持されてある場所などのメタデータは全部このmetastoreにまとまっています。このmetastoreは、次の3種類の方法で保存することができます。

  • Embeded metastore
  • Local Metastore
  • Remote Metastore

Embeded metastore

Embeded metastoreは主にテスト用途に利用されます。テスト用途なので、単一プロセスからの接続しか許可されていません。 そのため、コンソールを複数起動して、それぞれのコンソールから別のMap&Reduceを走らせる...なんてことができません。ただし、Hiveは初期設定がこのEmbededモードになっているので、特に設定することなくすぐに利用するメリットはあります。

また、metastoreはmetastoreディレクトリ(hive-default.xmlのhive.metastore.urisセクションに記述されています)内の 単一のファイルに保存されることになります。メタデータのバックアップをとりたいときは、このファイルをバックアップ対象にすればOKです。

Remote Metastore

1つ飛ばして、Remote Metastoreについて。Remote Metastoreはネットワーク越しにmetastoreを扱いたい場合における設定方法です。Hiveクライアントとデータを保存するMySQLの間にThriftサーバをproxyのような形で挟むことで実現しています。Thriftプロトコルを実現できていれば通信が可能なので、実際はJavaのHiveクライアントだけではなく、異なる言語によるクライアントからも接続が可能です。たとえばPHPでの例があるみたいです。

Local Metastore

Local Metastoreは、上で述べた2つの形式の間の位置づけのものです。 metastoreを単一ファイルで扱わずに、Remote Metastoreの形式にようにMySQLに保存することで、同一ホスト内から同時に複数のセッションを張ることができます。 このLocal Metastoreは簡単に実現できる割にすごく便利なので、紹介しておきます。

DBの設定

まず、MySQLでmetastoreを保存する適当なDB、およびその接続ユーザを定義します。たとえばrootで接続し、次のように定義します。

create database hive;
grant all privileges on hive.* to user_hive@localhost identified by 'hive_password';
flush privileges;

この設定をHiveに反映させるために設定用xmlを編集します。

hive-site.xmlの編集

Hiveの初期設定はhive-default.xmlにまとまっていますが、この設定はhive-site.xmlという名前のファイルを同一ディレクトリに用意してあげることで上書きできます。 上記設定は次の項目を編集、または追加してあげることで反映されます。

<property>
  <name>hive.metastore.local</name>
  <value>true</value>
  <description>controls whether to connect to remove metastore server or open a new metastore server in Hive Client JVM</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:mysql://localhost/hive?createDatabaseIfNotExist=true</value>
  <description>JDBC connect string for a JDBC metastore</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.mysql.jdbc.Driver</value>
  <description>Driver class name for a JDBC metastore</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>hive_user</value>
  <description>metastore mysql user</description>
</property>

<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>hive_password</value>
  <description>metastore mysql password</description>
</property>

<property>
  <name>hive.metastore.warehouse.dir</name>
  <value>/user/hive/warehouse</value>
  <description>location of default database for the warehouse</description>
</property>

どのプロパティ名も名前からどんなものかは凡そ予想はつくと思います。 ちなみにhive.metastore.warehouse.dirはHDFS上でのファイルパスになるので、ここだけ要注意です。

xmlを編集したら、hiveクライアントのシェルを立ち上げ直します。 シェル自体はEmbeded Metastoreのときとメッセージ形式も変わらないですが、同時に複数のHQLを実行することができるので、格段に使いやすくなります。 Hiveの利用を考える場合は、Local Metastoreの利用は検討してみる価値はあるのかな、と思います。

まとめ

Hiveのmetastoreの保存形式について、また、Local Metastoreの形式について、その設定方法をまとめてみました。これらの保存方式については、公式サイトにもまとまっている資料があるので、一度目を通してみるとコンポーネント間の関係が理解しやすいかと思います。

Tokyo-JoggingをSnow Leopardに対応させました

1年ぶりにTokyo-Joggingをupdateさせました。

さぼってたわけではないのですが、抜本的に通信方法を変更しようとあれこれ試行錯誤していた割に根本的に解決できない問題にぶちあたって途方に暮れていたので、路線変更で小さなバグfixとヌンチャク対応したものを一気にまとめあげてリリースしました。

Tokyo-Jogging - Downloads

Snow Leopard対応

Tokyo-Joggingでは内部でBluetoothの信号を扱うためにBlueCoveというライブラリを利用しているのですが、どうもこれがSnow Leopardではうまく動かない模様。原因を探っているとSnow Leopardでは64bit版のJavaがデフォルトで起動するのに対して、BlueCoveのバイナリが32bit用にビルドされていることが原因みたい。インストールされてあるJavaの設定を変更してもいのですが、プログラム単位で対応するためには起動オプションに

-d32

を追加すればいいみたいです。

また、BlueCoveを2.1.0にバージョン上げたら「java.lang.IllegalArgumentException: PCM values restricted by JAR82 to minimum 4097」な例外が出て、落ちてしまってたので、これも対応。起動オプションに

-Dbluecove.jsr82.psm_minimum_off=true

を付けてあげればいいみたいです。

これらの起動オプションは付属のstart-jogging.shに反映させてあるので、最新版にupdateした上で

./start-jogging.sh

で、起動することでSnow Leopard対応したオプション付きで起動できます。

Eclipseから起動する場合は、上記の2つのオプションをRun Configurationsの"VM arguments"に追記してあげればOKです。

ヌンチャク対応

地味なupdateですが、ヌンチャクを利用できるようにしました。Wiimoteを認識させた後にヌンチャクを接続するとアナログスティックで方向を変えることができます。(ヌンチャクを接続させたままWiimoteを認識させてもヌンチャクが認識されないので注意)

これで、左手にヌンチャク、右手にWiimoteなスタイルでスムーズに方向を変えつつどこでも走ることができますね!

今後はどうするの?

結構、作りっぱなしで放置しているように見られがちですが、実際はジョギング自身でやりたいことはもちろん、派生系でもまだまだやりたいことはあって、ゆっくりながらもupdateしていく予定です。あと、ここへきて外でしゃべらさせていただく機会も増えそうなので、そういうタイミングにあわせてupdateしていく予定です。(今回のupdateも実はその流れ)

SQL感覚でMap Reduce処理できるHiveについて

Hive

前回、JavaScriptでMap Reduceのコードが書けるHadoop Streamingについて紹介しました。 標準入出力さえサポートされてあれば、任意のコードでMap Reduuceの処理が書ける、というものでしたが、エンジニアはそもそも面倒くさがり。コードも書くのも面倒です。 と、いうわけで、今回はもうコードすら書かずにSQLライクでMap ReduceできるHiveというプロダクトについて、まとめたいと思います。

Hive

Hiveとは、簡単に言うとHadoop上で動作するRDBのようなものです。 HDFSなどの分散ファイルシステム上に存在するデータに対して、HiveQLというSQLライクな言語で操作できます。 で、面白いのがHiveQLの操作は基本的にMap Reduceのラッパーになっていること。 要するに、SELECT文実行すると裏でMap&Reduceのタスクが走り出して、分散処理されて結果を得ることができます。

そんなHiveは、もともとFacebookが開発を始めたものでした。2008年12月に、正式にHadoopプロジェクトにcontributeされて、いまはHiveの公式サイトもHadoopのサイト内でホスティングされてあります。

では、さっそく導入方法について確認してみます。

ビルド

Hiveのビルドに先立って、Hadoopをインストールしておきます。Hadoopのインストール方法は前回のエントリにも書いてある通りです。

Hiveのビルド自体は、Subevrsionからcheckoutして、antのタスクでビルドできる簡単なものです。1ヶ月ほど前のHiveは、patchを当てないとHadoop 0.20.0用にビルドできなかったのですが、最近のtrunkのものは特に何もせずにビルドできるようです。

svn co http://svn.apache.org/repos/asf/hadoop/hive/trunk hive
cd hive
ant -Dhadoop.version="0.20.0" package

ビルドできたらbuild/distを環境変数$HIVE_HOMEとでも設定しておきます。また、あわせて$HADOOP_HOMEも設定しておきましょう。

セットアップ

ビルドが成功したら、Hive用のデータ保存領域のセットアップをHDFS上で行います。Hadoopを起動した状態で次のようにセットアップします。

$HADOOP_HOME/bin/hadoop fs -mkdir       /tmp
$HADOOP_HOME/bin/hadoop fs -mkdir       /user/hive/warehouse
$HADOOP_HOME/bin/hadoop fs -chmod g+w   /tmp
$HADOOP_HOME/bin/hadoop fs -chmod g+w   /user/hive/warehouse

すると、次のコマンドでHiveが起動されます。

$HIVE_HOME/bin/hive

テーブルの作成

では、早速Hiveで扱うテーブルを作成します。 テーブルの作成方法はMySQLなんかの一般的なRDBMSのそれとほぼ同等です。 たとえば、ユーザのアクセスログをまとめたログがあると仮定します。このログを保存するテーブルは最低限、ユーザIDとそのアクセス時間が保存されればOKなので、次のように定義できます。

CREATE TABLE user_logs(id INT, created_at STRING);

構文については見たまんまなので。理解しやすいかと思います。ちなみに、昔のバージョンだとカラムにDATETIMEなんかの型が定義できたようですが、現在はDATETIME型はなくなっているようです。(Hive/LanguageManual/DDL

また、CSVなどの形式ですでに既存のログファイルがある場合、便利な機能があります。 Hiveではテーブル定義時に(CSVなどの)ある形式のデータを扱うことをあらかじめ指定しておくと、 CSV形式のログデータをそっくりそのままHiveのテーブルのレコードとしてロードさせることが可能です。

たとえばCSV形式のファイルを扱う場合、このような定義方法になります。

CREATE TABLE user_logs(id INT, created_at STRING) row format delimited fields terminated by ',' lines terminated by '\n';

この上で、次のようなコマンドで既存のログデータをロードできます。

LOAD DATA LOCAL INPATH '/path/to/log_20090907.csv' OVERWRITE INTO TABLE user_logs;

これで、CSVデータをHiveQLで扱えるようになりました。この時点でHDFS上にlog_20090907.csvは保存されています。/user/hive/warehouse/以下にテーブル名である「user_logs」ディレクトリが作成されてあり、該当ファイルが保存されてあります。

$ $HADOOP_HOME/bin/hadoop fs -ls /user/hive/warehouse/user_logs/
Found 1 items
-rw-r--r--   1 katsuma supergroup   17800731 2009-09-06 11:01 /user/hive/warehouse/user_logs/20090907.csv

では、ここで早速HiveQLを実行してみましょう。全件取得するときは一般的なSELECT文が利用できます。

SELECT * from user_logs;

ただし、このままだとローカルからデータをロードするだけで、ただのCSVのSQLラッパーでしかありません。 HiveQLが真価を発揮するのはいろいろ条件付けたSQLを発行してから。

ユニークユーザ数をHiveQLで算出

ここで、ユニークユーザ数(UU)を算出してみます。普通のMap Reduceの処理だと、IDごとのHashMapを作成してそのサイズからUUを算出することになりますが、HiveはSQLライクに算出することができます。つまり、こういうかんじ。

select count(distinct id) from user_logs;

すると、いきなりここからMap&Reduce処理が走り出します。 この、「SQL実行でMap&Reduceが実行される」という様子があまりにすごいので、最初これ見たときはかなりウケました。

Total MapReduce jobs = 1
Number of reduce tasks determined at compile time: 1
In order to change the average load for a reducer (in bytes):
  set hive.exec.reducers.bytes.per.reducer=
In order to limit the maximum number of reducers:
  set hive.exec.reducers.max=
In order to set a constant number of reducers:
  set mapred.reduce.tasks=
Starting Job = job_200909061015_0001, Tracking URL = http://hdp-01:50030/jobdetails.jsp?jobid=job_200909061015_0001
Kill Command = /home/katsuma/hadoop/latest/bin/../bin/hadoop job  -Dmapred.job.tracker=hdp-01:9001 -kill job_200909061015_0001
2009-09-06 10:41:27,049 map = 0%,  reduce = 0%
2009-09-06 10:42:27,520 map = 5%,  reduce = 0%
2009-09-06 10:42:28,540 map = 50%,  reduce = 0%
2009-09-06 10:42:33,594 map = 69%,  reduce = 0%
2009-09-06 10:42:34,610 map = 100%,  reduce = 0%
2009-09-06 10:42:55,748 map = 100%,  reduce = 100%
Ended Job = job_200909061015_0001
OK
21509
Time taken: 105.58 seconds

すると、結果が取得できました。上記例だと21509ユーザという結果を取得できていますね。あまりに簡単すぎてウケます。

ちなみにHiveのコンソールはサイレントモードが用意されてあって、Hiveのコンソールを起動させることなく、普段のシェルからも実行させることができます。サイレンとモードは-Sオプションをつけて実行すればよく、

$HIVE_HOME/bin/hive -S -e 'select count(distinct id) from user_logs'

と、すれば、途中経過をすっとばして結果だけ取得できます。もちろんこれはバッチ処理など、Hiveコンソールを利用せずにいろんな値を取得させたいときに利用すればよさそうですね。

Partitionを設定

Hiveの特徴的な機能としてPartitionというものがあります。 これはテーブル自体にバージョン管理の概念を持たせるもので、ログ管理にすごく便利そうなものです。

たとえばデイリーのログファイルをHiveで管理したいと思ったときに、 毎日HDFS上にログファイルを転送することになるかと思いますが、Partitionを利用することで、 ログを追記でテーブルに書き込む(=ファイルにappendする)のではなく、 日付ごとに独立したファイルのままHDFSに保存することができます。 なので、「やっぱりHiveより素のMqp&Reduce書くようなフローに戻そう」と 思ったときに、Partitionを利用してロードしておいたほうが後々都合がよいかと思います。(あと、初期のHadoopはファイルのappendができない、という制約があったので、Hive側はこの制約を回避するために、このような概念を持たせて独立したファイルを透過的に見せるような仕様で実装したんじゃないかな?と推測しています)

Partition自身は、ふだんのテーブルにおけるカラムのように扱えるので、実際は特別に何か意識する必要はありません。Partition付きテーブルはこのように定義できます。

CREATE TABLE user_logs(id INT, created_at STRING) partitioned by(dt STRING) row format delimited fields terminated by ',' lines terminated by '\n';

この場合だと、dailyの情報をpartitionに持たせるために「dt」という名前のpartitionに設定させています。その上で、このテーブルにデータをロードする場合は次のようになります。

LOAD DATA LOCAL INPATH '/path/to/20090907.csv' OVERWRITE INTO TABLE user_logs partition (dt='20090907');
LOAD DATA LOCAL INPATH '/path/to/20090908.csv' OVERWRITE INTO TABLE user_logs partition (dt='20090908');

すると、HDFS上ではバージョン情報をもったまま保存されていることが確認できます。

$ $HADOOP_HOME/bin/hadoop fs -ls /user/hive/warehouse/user_logs/
Found 2 items
drwxr-xr-x   - katsuma supergroup          0 2009-09-06 11:18 /user/hive/warehouse/user_logs/dt=20090907
drwxr-xr-x   - katsuma supergroup          0 2009-09-06 11:17 /user/hive/warehouse/user_logs/dt=20090908

これらのPartitionの情報は、もちろんHiveQL上で扱うことができて、たとえば上記例だと9月8日のUUを算出したい場合は

select count(distinct id) from user_logs where dt='20090908';

で、算出できます。また、9月7日以降のUUを算出したい場合だと

select count(distinct id) from user_logs where dt>='20090907';

な、風に書けます。直感的で便利ですよね。(この比較式はたぶん、JavaのString#compareToに従ってるんじゃないか、と推測)

まとめ

Hiveはまだ立ち上がったばっかりの若いプロジェクトですが、基本的なログ解析をするくらいの分には、かなり使えそうな印象です。上記例のように、基本的に元ファイルはそのままHDFS上に保存されているので、Hiveの不具合で何かおきた場合も自前のMap&Reduce処理に戻すことができるのも安心ですね。

今回は、あまり細かな説明までしませんでしたが、HiveQLはJoinやUnionなど割と複雑なテーブル間処理も行うことができます。このあたりも興味ある方は一度、オフィシャルのマニュアルに目を通してみてはいかがでしょうか?

Hadoop Streamingを利用してJavaScriptでMap Reduce

久々のBlog更新、というわけでリハビリがてらJavaScriptで軽く遊んでみたいと思います。

いま、巷で流行ってるMapReduceのオープンソース実装Hadoopは「Hadoop Streaming」という標準入出力でデータのやりとりができる仕組みを使って、 Hadoopの実装言語であるJavaにとらわれず、RubyやPerlなど他の言語でもMap+Reduceの処理ができることが1つのウリになっています。 で、僕たちwebエンジニアはみんなJavaScript大好きなので、「JavaScriptでもMap Reduceやりたい!」という流れになるのは必然です。 そこで、試行錯誤でいろいろ試してみると割とさっくり出来たのでそのメモを残しておきたいと思います。

環境の整備

Mac OSX上のVMWare FusionにCentOSの仮想マシンを2台立ち上げて、環境セットアップしました。以下のような手順で環境整備しました。

仮想HWの設定

仮想マシンのイメージファイルですが、僕は毎回thoughtpoliceのサイトのものを利用しています。ここからCentOS 5.3のイメージを落としてFusionでロード。

1つハマったのが、同じイメージをコピーして複数台起動すると、MACアドレスがかぶって同時に複数台の仮想マシンがNWに接続できない点。なので、仮想マシンを最初に起動させる前にMACアドレスの設定をしておくことをおすすめします。

MACアドレスは、vmxファイルのuuid.bios値をもとに自動生成されるようで、この値を最初から全仮想マシンでバラバラにしておけばOKです。と、いっても有効な値の範囲があるので、最後の1byte値だけズラしておけばOKだと思います。たとえば、上記サイトからイメージを取得した場合、uuid.bios値の最後は「7d」になっているので、僕はこの値を「7c」「7b」なんかにズラしておきました。(Fusion上から正規の方法?で、マシンをクローンする方法がよくわからなかったので、こういう強引な方法を取っています。綺麗な方法をご存知の方いらしたらぜひ教えてください!)

ネットワーク設定

/etc/hostsを設定して2台のマシンの名前解決。細かな話ですけど、ホスト名には「_(アンダーバー)」は使えないのに要注意。最初「hdp_01」みたいなホスト名にしていて、実行時エラーが出てドはまってたときに全然気づかなかった。

  • 192.168.1.15 hdp-01
  • 192.168.1.16 hdp-02

SSHの設定

マシンはそれぞれ自分自身(localhopst)に対して、パスフレーズなしでSSHでログインできるように公開鍵を設定しておきます。

  • ssh-keygen -t rsa -P ""
  • cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
  • chmod 600 ~/.ssh/authorized_keys

また、MasterのマシンからSlaveのマシンに対しても同様の設定をしておきます。今回はMasterはhdp-01, Slaveはhdp-02という設定です。hdp-01上で、次の設定をします。

  • cp ~/.ssh/id_rsa.pub ~/.ssh/id_rsa_master.pub
  • scp ~/.ssh/id_rsa_master.pub hdp-02:/home/katsuma/.ssh/
  • chmod 600 ~/.ssh/authorized_keys

また、hdp-02上で、次の設定をします。

  • cat ~/.ssh/id_rsa_master.pub >> ~/.ssh/authorized_keys

これで、Masterからlocalhost, およびSlaveに対してパスフレーズなしでログインできることを確認しておきます。

セキュリティの設定

後でハマるのが面倒なので、iptablesやSELinuxは切っておきます。必要であれば適宜設定してください。

  • sudo /etc/init.d/iptables off
  • sudo /etc/sysconfig iptables off
  • sudo vi /etc/sysconfig/selinux
    • SELINUX=disabled に変更

Shellの設定

.bashrcなんかに以下の設定をしておくと便利です。僕はzsh派なので、.zshrcに記述しました。

# hadoop
alias cdh='cd /home/katsuma/hadoop/latest/'
alias dls='/home/katsuma/hadoop/latest/bin/hadoop dfs -ls'       # ls
alias drm='/home/katsuma/hadoop/latest/bin/hadoop dfs -rm'       # rm
alias dcat='/home/katsuma/hadoop/latest/bin/hadoop dfs -cat'     # cat
alias drmr='/home/katsuma/hadoop/latest/bin/hadoop dfs -rmr'     # rm -r
alias dmkdir='/home/katsuma/hadoop/latest/bin/hadoop dfs -mkdir' # mkdir
alias dput='/home/katsuma/hadoop/latest/bin/hadoop dfs -put'     # HDFS に転送
alias dget='/home/katsuma/hadoop/latest/bin/hadoop dfs -get'     # HDFS から転送
alias dcpfl='/home/katsuma/hadoop/latest/bin/hadoop dfs -copyFromLocal'
alias dcptl='/home/katsuma/hadoop/latest/bin/hadoop dfs -copyToLocal'

Javaのインストール

Sunのサイトに行き、 「Java SE Development Kit (JDK)」を選択して、インストーラをダウンロード。その後、以下の手順でインストールできます。

chmod +x jdk-6u7-linux-i586-rpm.bin
sudo ./jdk-6u7-linux-i586-rpm.bin

Hadoopの整備

Hadoopのインストール

Hadoopは今日時点で最新の0.20.0を利用しました。インストール場所はどこでもいいのですが、僕はホームディレクトリ直下に専用のディレクトリ掘って、いろんなバージョン試せるようにこんな感じで設置してます。ここから最新版をDLが可能です。

  • cd path/to/hadoop-0.20.0.tar.gz
  • tar zxvf hadoop-0.20.0.tar.gz
  • mv hadoop-0.20.0 ~/hadoop/
  • ln -s hadoop-0.20.0 latest

他のバージョンを試したくなったら~/hadoop/以下に展開して、シンボリックリンクを付け直すとOKですね。

Hadoopの設定

特に凝ったことはしていません。全ノードともに同じ設定である必要があるので、Masterで設定しちゃって、それをrsyncで他のSlaveと同期をとるのがよいと思います。

conf/hadoop-env.sh
export JAVA_HOME=/usr/java/latest
conf/core-site.xml

<configuration>
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/home/${user.name}/hadoop/latest/tmp</value>
  </property>
  <property>
    <name>fs.default.name</name>
    <value>hdfs://hdp-01:9000</value>
  </property>
</configuration>

conf/hdfs-site.xml

<configuration>
  <property>
    <name>dfs.replication</name>
    <value>1</value>
  </property>
</configuration>

conf/mapred-site.xml

<configuration>
  <property>
    <name>mapred.job.tracker</name>
    <value>hdp-01:9001</value>
  </property>
</configuration>

conf/masters

      hdp-01

conf/slaves

      hdp-02

ここまで設定できたら、masterのイメージをrsyncしておきましょう。

cd ~/hadoop/
rsync -r hadoop-0.20.0/ hdp-02:/home/katsuma/hadoop/hadoop-0.20.0

SpiderMonkeyの導入

さて、やっとHadoopの設定が終わったので、次にJavaScriptの処理系の導入です。 Hadoop Streamingは前述の通り、標準入出力の仕掛けを使って実現されているので、さすがにブラウザの処理系をそのまま利用することができません。

そこで、FirefoxのJavaScriptのエンジンであるSpiderMonkeyを利用することにします。 SpiderMonkeyはファイルを入力としても処理できるし、irbのように対話型シェルとしても利用できるJavaScriptの処理系です。また、ブラウザを利用しないので、標準出力関数print、標準入力関数readlineが実装されてあるので、今回はこれを利用すればうまくいきそうです。

では、SpiderMonkeyを各ノードに導入します。これも最新版を導入します。あらかじめ、make, gccあたりをyumで入れておきましょう。

参考:SpiderMonkeyのインストール

  • wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.8.0-rc1.tar.gz
  • tar zxf js-1.8.0-rc1.tar.gz
  • cd js/src
  • BUILD_OPT=1 make -f Makefile.ref
  • sudo install -m 755 Linux_All_OPT.OBJ/js /usr/local/bin

Map, Reduce処理のJavaScriptを記述する。

では、Map, Reduceそれぞれの処理をJavaScriptで書きます。今回はサンプルとしてよくある、ワードカウントの処理を行ってみます。

map.js

map.jsは標準入力された文章を半角スペースごとに分けて、CSVの形式に整形します。JavaScript1.7相当の機能が利用できるので、Array.prototype.forEachが利用できることもポイントです。

#!/usr/local/bin/js

var line="";
while ((line = readline())!= null){
        var words = line.split(" ");
        words.forEach(function(w){
                print(w + "," + 1);
        });
}

reduce.js

reduce.jsは、map処理されたCSV形式の入力に対して、counterオブジェクトで各単語をカウントしていきます。

#!/usr/local/bin/js

var counter = {};
var line = "";
while ((line = readline()) != null) {
        var words = line.split(",");
        var word = words[0]
        if(!counter[word]) counter[word] = 1;
        else counter[word]++;
}

for(var k in counter){
        print(k + ":" + counter[k]);
}

これらのJavaScriptファイルをhadoop/latest/script/あたりに保存しておき、全ノードで同期させておきます。

Hadoopの起動

最初にHDFSをフォーマットしておきます。Masterで次の処理を行います。

cdh
./bin/hadoop namenode -format

MasterでHadoopを起動します。

bin/start-all.sh

MapReduce用の適当な入力ファイルを作成します。こんな内容のファイルを$HADOOP_HOME/input/file1に作成します。

we are the world we change the world

このファイルをHDFSに転送します。dputは bin/hadoop dfs -putのエイリアスです。

dput input/file1 in/count

転送されてあるかどうかは、dls(bin/hadoop dfs -ls)で確認できます。

katsuma@hdp-01 ~/hadoop/latest
$ dls
Found 1 items
drwxr-xr-x   - katsuma supergroup          0 2009-07-31 02:14 /user/katsuma/in

katsuma@hdp-01 ~/hadoop/latest
$ dls in
Found 1 items
drwxr-xr-x   - katsuma supergroup          0 2009-07-31 02:56 /user/katsuma/in/count

入力用ファイルの存在が確認できたので、これでやっとMapReduce処理ができます。

./bin/hadoop jar ./contrib/streaming/hadoop-0.20.0-streaming.jar -input in/count -output out/count -mapper "js /home/katsuma/hadoop/latest/script/map.js" -reducer "js /home/katsuma/hadoop/latest/script/reduce.js"

すると、ゆったりですけど処理が進んでいきます。

packageJobJar: [/home/katsuma/hadoop/latest/tmp/hadoop-unjar4327209233542314881/] [] /tmp/streamjob4726696067913490494.jar tmpDir=null
09/07/31 10:45:05 INFO mapred.FileInputFormat: Total input paths to process : 1
09/07/31 10:45:06 INFO streaming.StreamJob: getLocalDirs(): [/home/katsuma/hadoop/latest/tmp/mapred/local]
09/07/31 10:45:06 INFO streaming.StreamJob: Running job: job_200907310237_0013
09/07/31 10:45:06 INFO streaming.StreamJob: To kill this job, run:
09/07/31 10:45:06 INFO streaming.StreamJob: /home/katsuma/hadoop/latest/bin/../bin/hadoop job  -Dmapred.job.tracker=hdp-01:9001 -kill job_200907310237_0013
09/07/31 10:45:06 INFO streaming.StreamJob: Tracking URL: http://hdp-01:50030/jobdetails.jsp?jobid=job_200907310237_0013
09/07/31 10:45:07 INFO streaming.StreamJob:  map 0%  reduce 0%
09/07/31 10:45:23 INFO streaming.StreamJob:  map 50%  reduce 0%
09/07/31 10:45:45 INFO streaming.StreamJob:  map 50%  reduce 17%
09/07/31 10:48:49 INFO streaming.StreamJob:  map 100%  reduce 17%
09/07/31 10:49:10 INFO streaming.StreamJob:  map 100%  reduce 100%
09/07/31 10:49:15 INFO streaming.StreamJob: Job complete: job_200907310237_0013
09/07/31 10:49:15 INFO streaming.StreamJob: Output: out/count

HDFS上のout/count/以下に結果が格納されたファイルができているので確認してみましょう。

katsuma@hdp-01 ~/hadoop/latest
$ dls out/count
Found 2 items
drwxr-xr-x   - katsuma supergroup          0 2009-07-31 10:45 /user/katsuma/out/count/_logs
-rw-r--r--   1 katsuma supergroup         43 2009-07-31 10:48 /user/katsuma/out/count/part-00000

katsuma@hdp-01 ~/hadoop/latest
$ dcat out/count/part-00000
are:1
change:1
the:2
we:2
world:2

まとめ

SpiderMonkeyのような処理系を用意することで、JavaScriptでもHadoopを使ってMapReduceできることが確認できました。標準入出力さえサポートされてあれば、理屈的にはどんな言語でもMapReduceできるので、MapReduceには興味あるけどJavaということで敬遠していた方は、ぜひいろんな言語で試していただければと思います。

開発のTips

なんだかんだ言って、最初開発にかなり苦労しました。と、いうのもシンタックスエラー以外は、実行しないとどうなるかよくわからないものなので、エラーで落ちたときにどうデバッグすればいいか悩みました。 ただ、よく考えれば、「標準入力→Map処理→Mapの標準出力→Reduceの入力→Reduceの標準出力」という流れになるので、たとえば今回のJavaScript実装の場合、次のように実行することでHadoopを介さなくとも動作確認は可能です。

cat input/file1 | js script/map.js | js script/reduce.js

まずは、このように手元でパイプでつないで結果を調べてみる、というのが手かと思います。実際は手元でうまく動いてもHadoop上でRuntimeエラーが起きる場合も多いので、そのときはlogディレクトリ以下にできるログファイルを調べるのがいいと思います。

また、エラーが発生するとJavaの例外のStackStraceが表示されるので、敬遠せずにそこからHadoopのソースを直接追いかけるのは何だかんだで早い解決法でした。コメントが割と充実しているので、そこからStreaming用のコードのバグを辿るのも難しくはないと思います。

emacs.el, anything.el, anything-rcodetools.elを導入

前回のemacs導入時にtomoyaさんにコメントいただいたり、negipoさんにもanything.elいれるといいよー!てずっと言われてて、軽く試してもなんかうまく導入できなくて途方に暮れて放置してたところ、この週末に時間とって試してみるとすんなり入りました。得てしてそういうものですよね。。あと、あわせてrails.el, anything-rcodetools.elなんかも入れてみました。その導入メモを残しておきたいと思います。

anything.el

ここからanything.elをDL.ロードパスが通ってるディレクトリにつっこみます。僕はここのサイトの影響で .emacs.d/elisp/ 以下につっこんでます。ロードパスを変えたいときは、.emacs.elに

(setq load-path (cons "~/path/to/loadpath" load-path))

みたいな記述を書けばOKです。

さて、anything.elをロードパスに置いたら、次の内容を.emacs.elに追記します。

(require 'anything-config)
(setq anything-sources (list anything-c-source-buffers
                             anything-c-source-bookmarks
                             anything-c-source-recentf
                             anything-c-source-file-name-history
                             anything-c-source-locate))
(define-key anything-map (kbd "C-p") 'anything-previous-line)
(define-key anything-map (kbd "C-n") 'anything-next-line)
(define-key anything-map (kbd "C-v") 'anything-next-source)
(define-key anything-map (kbd "M-v") 'anything-previous-source)
(global-set-key (kbd "C-;") 'anything)

これで、emacsを再起動させると、編集中にC-;を打つとanythingが起動します。すると、Bufferの中に保存された内容や、最近開いたファイルやら何やらが表示されます。これをC-n,C-p / C-v,M-vで選択できます。

もう、これすごい。みんなすごいって言ってた意味がやっとわかりました。QuickSilverみたいなかんじで編集したいファイルに辿り着けるのがすごく便利。C-x,C-fでファイル選択するの、ちょっと扱いづらいなぁと思ってたけど、これで一気に解決できそうです。

(参考)anything.elが手放せなくなった

rails.el

必要なファイルは「rails.el」一式、「find-recursive.el」「snippet.el」の3種類。これらをロードパスが通ったところに設定します。(上の例だと.emacs.d/elisp/)

その上で、次のような内容を.emacs.elに追加。

;; rails.el
(defun try-complete-abbrev (old)
  (if (expand-abbrev) t nil))

(setq hippie-expand-try-functions-list
      '(try-complete-abbrev
        try-complete-file-name
        try-expand-dabbrev))
(setq rails-use-mongrel t)
(require 'rails)

;; 対応するファイルへの切り替え(C-c C-p)
(define-key rails-minor-mode-map "\C-c\C-p" 'rails-lib:run-primary-switch)
;; 行き先を選べるファイル切り替え(C-c C-n)
(define-key rails-minor-mode-map "\C-c\C-n" 'rails-lib:run-secondary-switch)

(setq auto-mode-alist  (cons '("\\.rhtml$" . html-mode) auto-mode-alist))

対応ファイルへの切り替え

Controller, View上でC-c C-p をタイプ。すると該当のアクション箇所、またはViewにさくっとジャンプ。これすごい。今まで毎回毎回ウィンドウ分割してファイルを選択して、、てやってたのが2キーでジャンプ。やばい!

行き先を選択するメニューの表示

C-c C-nでメニューがポップアップ表示され、ここからHelperやpartialにジャンプできます。これもすごい。。

view間の移動

<%= render :partial => 'news' %> みたいになってる箇所で C-Enterをタイプ。すると_news.rhtmlのpartialファイルに一気にジャンプ。これも便利すぎてウケます。

まとめると、とにかく、これでもかというくらいに移動系が便利になってます。 Railsは、あちらこちらに移動してファイルいじることが多いので、rails.elは確かに必須。 今まで入れてなくて相当損してました。。

(参考)rails.elまとめ

anything-rcodetools.el

Rubyそのものを書くときに、補完周りなんかで便利になるelispです。これインストールかなりハマりました。次の手順でインストールをすすめます。anything.elはあらかじめ入れておきましょう。

  1. gem install rcodetools
  2. gem install fastri
  3. ここからanything-rcodetools.elを導入
  4. ~/.gem/ruby/1.8/gems/rcodetools-0.x.x/にあるrcodetools.elをロードパスの通った場所にコピー

ハマりどころは4.のrcodetools.elを持ってくるところ。ずっとこのファイルのロードエラーが出て困ってましたが、自分で持ってこないといけないみたい。ちなみにRubyに慣れてる人なら当然かもしれませんが、gemでインストールするときに、sudoでインストールすると上記ファイルは.gem/以下に作られないので、そこもあわせて注意です。(これもハマった)

その上で、次のような内容を.emacs.elに追加しておきます。

(require 'anything)
(require 'anything-rcodetools)
;; Command to get all RI entries.
(setq rct-get-all-methods-command "PAGER=cat fri -l")
;; See docs
(define-key anything-map "\C-e" 'anything-execute-persistent-action)

これで"def"と入力した後に、スペースを入力すると、自動的に"end"が挿入されるなど、Rubyに特化した補完が効いてきます。これもタイプ数さぼるために便利。

ただ、anything-rcodetoolsは、明らかにまだまだもっと便利そうな機能がいっぱいありそうなんですけど、まだ何をどうやってどうなれば便利なのか、自分の中でそのメリットが確認できていません。ここは要研究。

(参考)(solved) anything-rcodetools.el が動かない (Ubuntu Studio 8.04)

github

また、いつものようにここまでの内容をgithubにpushしています。興味ある方はご参考ください。あと「こうすればもっと便利になるよ!」みたいな意見は相変わらずどんどん募集中です。

More...

Home

Page Top