HbaseとPigで遊んでみる。その2

http://d.hatena.ne.jp/goth_wrist_cut/20110529/1306695210
前回の続きでする。
前回までのあらすじ。

  • EasyMode上等
  • OpenJDKではなく、OracleJDK
  • conf-pseudo便利
  • .outではなく.logをつかうべし
  • ディレクトリは勝手に作らせるが吉
  • Webインターフェースが素敵
  • 円周率はおよそ3.6。


今回はHBaseの導入と起動、そんでshellで遊ぶ。
実際にTwitterのTLをいれよう、とか思ったのだけど、思った以上に長くなったのでここまで。
次回はTwitterからデータを入れてみます。

起動

インストールは前回でまとめてやってしまったので特にやることは無し。


設定に関しても疑似分散モードなら無設定で動きます。
完全分散モードの場合には、完全分散で動かすよ!という指定やらデータの保存先(HDFSのnamenodeとパス)の指定が必要になります*1
また、Zookeeperと呼ばれる多数決で意志決定する、要はエヴァのMagiみたいなのが必要となるのですが、疑似分散モードではHBaseのmasterサーバがZookeeperを持っていてよしなにしてくれます。
ただ、このHBase masterの持っているZookeeperは他のZookeeperと干渉するので、Zookeeperをインストールし場合には明示的に止めておく必要があります。

$ sudo /sbin/chkconfig --list hadoop-zookeeper
hadoop-zookeeper	0:off	1:off	2:on	3:on	4:on	5:on	6:off
$ sudo /sbin/chkconfig hadoop-zookeeper off
$ sudo /sbin/chkconfig --list hadoop-zookeeper
hadoop-zookeeper	0:off	1:off	2:off	3:off	4:off	5:off	6:off
$ sudo /etc/init.d/hadoop-zookeeper stop
JMX enabled by default
Using config: /etc/zookeeper/zoo.cfg
Stopping zookeeper ... 
STOPPED

今回は疑似分散でテストしているのでしばらく黙っていてもらいます。


Zookeeperを止める代わりにHBase側のZookeeperのポート番号を変えてもよし。
/etc/hbase/conf/hbase-site.xmlのconfigurationにプロパティを追加します(デフォルトポートは2181番)。

<configuration>
        <property>        
                <name>hbase.zookeeper.property.clientPort</name>
                <value>2182</value>
        </property>       
</configuration>  

こっちの方がお行儀がいいのかな?
まぁ、でも使いもしないZookeeperが動いてても、というのはあるので、疑似分散でテストしている時には自動起動を切っておいても、という感じ。
というか、インストールしなければよかった、という話か……。ついうっかり。


とりあえず、Zookeeperの問題が片付いたのでHBaseを起動してみましょう。
他のZookeeperが起動していないのを確認したらhbase-masterとhbase-regionserverを起動します。

$ sudo /etc/init.d/hadoop-hbase-master start
starting master, logging to /var/log/hbase/hbase-hbase-master-vCentOS.out
$ sudo /etc/init.d/hadoop-hbase-regionserver start
starting regionserver, logging to /var/log/hbase/hbase-hbase-regionserver-vCentOS.out

例のごとく死活管理はWebインターフェースがおすすめ。
Hadoop徹底入門(初版第1刷)」には60011と書いてますが、60010がWebインターフェースのポートです。
regionserverは下の方に並んでます*2

Region Servers                                                                                                                              
                                                                                                                                            
             Address     Start Code                        Load                                                                             
          vCentOS:60030 1306776614039 requests=0, regions=5, usedHeap=28, maxHeap=997                                                       
   Total: servers: 1                  requests=0, regions=5     

なんかデータ入ってますが、テストしてたときの残りです。気にせんといてください。

HBaseについて

HBaseについてちょっとだけ。
HBaseの説明はすんごいわかりやすいサイトがごまんとあるのでさっくりと。


HBaseはいわゆるNoSQLのデータベースで、HDFSを使ってデータを保存してる。
HDFSは基本的に大きいデータをシーケンシャルに、というものなので、データベースみたいにこまい変更がちょくちょく、というのには向いてない。
そこで、HBaseではデータベースに対する「操作を」ひたすら保存していく、という形でこまい変更を処理しているらしい。
HBaseがひとつのセルに複数ヴァージョン値をもつ、っていうのはたぶんこの辺が理由なんだと思う。ソース読んだ訳じゃないからわからんけど。


この実際にデータが入るセルへのアクセスは行(Row)と列(Column)で行う*3


RowはSQLでいう主キーみたいな感じ。HBaseだと基本的にRowキーでしか検索ができない。
もうちょっというと、Rowキーでデータがソートされていて、完全一致ないし前方一致したものを範囲で拾ってこれる。
中に入ってるデータで検索したい、ってときにはMapReduceしてね、という感じ。


Columnは主キー以外のいわゆるカラム。カラムファミリ(ColumnFamily)と修飾子(Qualifier)をコロン(:)でくっつけたもの。
Qualifierのほうは完全に自由で、いわゆるスキーマレス。空でもok。
で、ColumnFamilyはというと、いわばカラムのグループみたいな感じ?
同じColumnFamilyに属するColumnは同じファイルに保存されるらしい。
なので、常に一緒に使うデータは同じColumnFamilyに、別々にアクセスするデータは別ColumnFamilyに、というのが基本的な運用らしい。
ColumnFamilyはテーブル作成時に指定する必要があるので、この辺はちゃんと設計せんとまずそう。
まぁ、最悪metaとdataみたいな大雑把な分け方でいいと思うのだけど。


HBaseの提供するデータ操作は基本的には3種類、putとgetとscan。


putは読んで字のごとくデータの書き込みをするんだけど、書き込みだけでなく削除もput。だから編集とか更新と呼ぶのが正解かも。
そんなわけで、かどうかは知らないけどThriftだとmutateになっている。
1つのRowに対して複数のColumnへの編集をAtomicに行える。
複数のRowに対するAtomicな操作は提供されていないらしい。ふぅむ。


getとscanがデータの読み込み。
getはRowとColumnと、あと場合によってはTimestampとで、ほしいデータにアクセスできる。
要はふつーのデータ読み込み。
scanはRowキーのprefixを使った範囲アクセス。
例えばRowキーがhogeから始まるやつを順番に見ていく、というような操作。
データがRowキーでソートされているHBaseならでは、なのかな?


以上!
という非常にシンプルな操作系。
SQLと比べるとあまりにアレで、何ができるの?という気分になるけど、要はMapReduceでかけ、ってことなのかな?
あるいはデータの入れ方を工夫してscanをうまく使うとかかなぁ。
PigとかからHBaseを触れるとしちめんどくさいMapReduceJavaで書く、という苦行からエスケープできるんでしょうけど、日本語情報がほとんどない!というね。
TODO: 調べる。

HBase Shell

そんなわけで実際にHBaseにデータをいれてみる。
HBaseで遊ぶならなんといってもhbase shell。
お手軽簡単に、HBaseとキャッキャウフフできちゃいます。
前回も少しふれたけど、hbase shellはjRubyirbなので、Rubyのコードやらなんやらが動いちゃいます。


とりあえずはテーブルの作成と削除から。

$ hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.90.1-cdh3u0, r, Fri Mar 25 16:10:51 PDT 2011

hbase(main):001:0> create 'test_table', 'cf1', 'cf2', {NAME => 'cf3', VERSIONS => 1}
0 row(s) in 6.8170 seconds

hbase(main):002:0> list
TABLE                                                                                                                                             
test_table                                                                                                                                        
1 row(s) in 0.1100 seconds

hbase(main):003:0> describe 'test_table'
DESCRIPTION                                                                                ENABLED
 {NAME => 'test_table', FAMILIES => [{NAME => 'cf1', BLOOMFILTER => 'NONE', REPLICATION_SC true
 OPE => '0', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65
 536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'cf2', BLOOMFILTER => 'NONE',
  REPLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', B
 LOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'cf3', BLOOMFI
 LTER => 'NONE', REPLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL =>
 '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0870 seconds

hbase(main):004:0> disable 'test_table'
0 row(s) in 2.6040 seconds

hbase(main):005:0> describe 'test_table'
DESCRIPTION                                                                                ENABLED
 {NAME => 'test_table', FAMILIES => [{NAME => 'cf1', BLOOMFILTER => 'NONE', REPLICATION_SC false
 OPE => '0', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65
 536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'cf2', BLOOMFILTER => 'NONE',
  REPLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', B
 LOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'cf3', BLOOMFI
 LTER => 'NONE', REPLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL =>
 '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0870 seconds

hbase(main):006:0> drop 'test_table'
0 row(s) in 1.5820 seconds

hbase(main):007:0> describe 'test_table'

ERROR: Failed to find table named test_table

Here is some help for this command:
Describe the named table. For example:
  hbase> describe 't1'

テーブルの作成はcreate。
第二引数以降はColumnFamilyの指定。連想配列を使うとColumnFamilyのプロパティも指定できる。
要はRubyなので['cf1', 'cf2']とか配列渡してみたり、(0..10).map{|i| "cf#{i}"}と連番で作ってみたりも。
createでこういう事をするのはあまりないケースかもだけど、適当なサンプルデータ作ったりするのには便利。


テーブル一覧がlist、詳細情報を見るのにはdescribe。
describeの表示がびみょーに見づらくてwirbleとか使えたらなぁ、と思ったけど、よくよく考えると使えてもカラーリングされないじゃん。返値じゃないんだし。
ぐぬぬ


テーブルの削除はdrop、なんだけど、先にdisableで無効化する必要あり。
disableしたテーブルはenableで再び有効化する事ができる。


データのput/get

$ hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.90.1-cdh3u0, r, Fri Mar 25 16:10:51 PDT 2011

hbase(main):001:0> create 'test_table', 'cf1', 'cf2', {NAME => 'cf3', VERSIONS => 1}
0 row(s) in 3.0080 seconds

hbase(main):002:0> put 'test_table', 'row1', 'cf1:col1', 'Hello'
0 row(s) in 0.1080 seconds

hbase(main):003:0> put 'test_table', 'row1', 'cf1:col2', 'World'
0 row(s) in 0.0680 seconds

hbase(main):004:0> get 'test_table', 'row1'
COLUMN                                CELL
 cf1:col1                             timestamp=1306824912123, value=Hello
 cf1:col2                             timestamp=1306824920915, value=World
2 row(s) in 0.1790 seconds

hbase(main):005:0> put 'test_table', 'row1', 'cf1:col2', 'HBase!'
0 row(s) in 0.1780 seconds

hbase(main):006:0> get 'test_table', 'row1'
COLUMN                                CELL
 cf1:col1                             timestamp=1306824912123, value=Hello
 cf1:col2                             timestamp=1306824944683, value=HBase!
2 row(s) in 0.1490 seconds

hbase(main):007:0> get 'test_table', 'row1', {COLUMN => 'cf1', VERSIONS => 2}
COLUMN                                CELL
 cf1:col1                             timestamp=1306824912123, value=Hello
 cf1:col2                             timestamp=1306824944683, value=HBase!
 cf1:col2                             timestamp=1306824920915, value=World
3 row(s) in 0.2040 seconds

hbase(main):008:0> put 'test_table', 'row1', 'cf2:', 'old data'              
0 row(s) in 0.4210 seconds

hbase(main):009:0> put 'test_table', 'row1', 'cf3:', 'old data'
0 row(s) in 0.0510 seconds

hbase(main):010:0> put 'test_table', 'row1', 'cf2:', 'new!'            
0 row(s) in 0.0290 seconds

hbase(main):011:0> put 'test_table', 'row1', 'cf3:', 'new!'
0 row(s) in 0.0430 seconds

hbase(main):012:0> get 'test_table', 'row1', {COLUMN => ['cf2', 'cf3']}
COLUMN                                CELL                                                                                                        
 cf2:                                 timestamp=1306825154941, value=new!                                                                         
 cf3:                                 timestamp=1306825157611, value=new!                                                                         
2 row(s) in 0.0740 seconds

hbase(main):013:0> get 'test_table', 'row1', {COLUMN => ['cf2', 'cf3'], VERSIONS => 2}
COLUMN                                CELL                                                                                                        
 cf2:                                 timestamp=1306825154941, value=new!                                                                         
 cf2:                                 timestamp=1306825097674, value=old data                                                                     
 cf3:                                 timestamp=1306825157611, value=new!                                                                         
3 row(s) in 0.1650 seconds

hbase(main):014:0> delete 'test_table', 'row1', 'cf2:'
0 row(s) in 0.0650 seconds

hbase(main):015:0> delete 'test_table', 'row1', 'cf3:'
0 row(s) in 0.0560 seconds

hbase(main):016:0> get 'test_table', 'row1', {COLUMN => ['cf2', 'cf3']}               
COLUMN                                CELL                                                                                                        
0 row(s) in 0.0540 seconds

hbase(main):017:0> get 'test_table', 'row1', {COLUMN => ['cf2', 'cf3'], VERSIONS => 2}
COLUMN                                CELL                                                                                                        
0 row(s) in 0.0470 seconds

hbase shellはputとdeleteを同時にできないっぽいなぁ。
VERSIONSを設定するとそれより古い情報が見られなくなる。
けどまぁ、普通は最新のを使うだけで困らなそうだし、VERSIONSは常に1でもよいかも。
各コマンド*4の引数は結構バリエーションがあるのでhelpで叩くなり、引数無しで叩くなりすると幸せになれるかも。


あと基本的にカラムはColumnFamilyだけを指定するとそのColumnFamilyに属するカラムがまとめて取得できる、のだけど、APIによっては挙動違うかも。
少なくともThriftのmutate(put)では、ColumnFamilyまるごと消そうとしてColumnFamilyだけを指定してみたけど消えなかった記憶が……。
この辺もあとでまとめておきたいね。


最後にscan。
最後ぐらいすこし実用っぽそうな雰囲気のしそうな感じのexampleを。

$ hbase shell                                                                                                                                               
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.90.1-cdh3u0, r, Fri Mar 25 16:10:51 PDT 2011

hbase(main):001:0> create 'inits', {NAME => 'data', COMPRESSION => 'GZ', VERSIONS => 1}, 'meta'
0 row(s) in 2.1140 seconds
hbase(main):002:0> Dir.glob("/etc/init.d/*").each{|f| base=File::basename f; stat=File::stat f; put 'inits', base, 'data:', (File::read f); put 'inits', base, 'meta:name', f; put 'inits', base, 'meta:size', (stat.size); put 'inits', base, 'meta:mode', (stat.mode.to_s(8)); put 'inits', base, 'meta:ctime', (stat.ctime.to_s); put 'inits', base, 'meta:mtime', (stat.mtime.to_s); }
0 row(s) in 0.0620 seconds

0 row(s) in 0.0220 seconds

〜〜〜略〜〜〜

0 row(s) in 0.0200 seconds

0 row(s) in 0.0230 seconds

=> ["/etc/init.d/krb524", "/etc/init.d/anacron", "/etc/init.d/irqbalance", "/etc/init.d/single", "/etc/init.d/hadoop-0.20-jobtracker", "/etc/init.d/iptables", "/etc/init.d/sendmail", "/etc/init.d/hadoop-zookeeper", "/etc/init.d/oddjobd", "/etc/init.d/acpid", "/etc/init.d/hidd", "/etc/init.d/hadoop-hbase-master", "/etc/init.d/hadoop-hbase-regionserver", "/etc/init.d/hadoop-0.20-tasktracker", "/etc/init.d/atd", "/etc/init.d/sshd", "/etc/init.d/netplugd", "/etc/init.d/avahi-daemon", "/etc/init.d/netconsole", "/etc/init.d/svnserve", "/etc/init.d/firstboot", "/etc/init.d/irda", "/etc/init.d/nfslock", "/etc/init.d/haldaemon", "/etc/init.d/avahi-dnsconfd", "/etc/init.d/dund", "/etc/init.d/readahead_later", "/etc/init.d/yum-updatesd", "/etc/init.d/syslog", "/etc/init.d/rdisc", "/etc/init.d/hadoop-hbase-thrift", "/etc/init.d/iscsi", "/etc/init.d/restorecond", "/etc/init.d/network", "/etc/init.d/netfs", "/etc/init.d/functions", "/etc/init.d/ypbind", "/etc/init.d/smartd", "/etc/init.d/xfs", "/etc/init.d/isdn", "/etc/init.d/killall", "/etc/init.d/readahead_early", "/etc/init.d/pcscd", "/etc/init.d/kudzu", "/etc/init.d/iscsid", "/etc/init.d/multipathd", "/etc/init.d/bluetooth", "/etc/init.d/ip6tables", "/etc/init.d/capi", "/etc/init.d/auditd", "/etc/init.d/rpcidmapd", "/etc/init.d/mdmpd", "/etc/init.d/autofs", "/etc/init.d/NetworkManager", "/etc/init.d/wpa_supplicant", "/etc/init.d/gpm", "/etc/init.d/mcstrans", "/etc/init.d/tcsd", "/etc/init.d/ntpd", "/etc/init.d/rawdevices", "/etc/init.d/cpuspeed", "/etc/init.d/crond", "/etc/init.d/pand", "/etc/init.d/hadoop-0.20-namenode", "/etc/init.d/lvm2-monitor", "/etc/init.d/microcode_ctl", "/etc/init.d/nfs", "/etc/init.d/saslauthd", "/etc/init.d/dnsmasq", "/etc/init.d/hadoop-0.20-datanode", "/etc/init.d/mdmonitor", "/etc/init.d/conman", "/etc/init.d/halt", "/etc/init.d/messagebus", "/etc/init.d/nscd", "/etc/init.d/rpcsvcgssd", "/etc/init.d/psacct", "/etc/init.d/portmap", "/etc/init.d/hadoop-0.20-secondarynamenode", "/etc/init.d/rpcgssd", "/etc/init.d/jexec"]

hbase(main):003:0> scan 'inits', {COLUMN => 'meta:name'}
ROW                                   COLUMN+CELL
 NetworkManager                       column=meta:name, timestamp=1306828398781, value=/etc/init.d/NetworkManager
 acpid                                column=meta:name, timestamp=1306828391435, value=/etc/init.d/acpid
 anacron                              column=meta:name, timestamp=1306828389790, value=/etc/init.d/anacron
 atd                                  column=meta:name, timestamp=1306828392440, value=/etc/init.d/atd

〜〜〜略〜〜〜

 wpa_supplicant                       column=meta:name, timestamp=1306828398901, value=/etc/init.d/wpa_supplicant
 xfs                                  column=meta:name, timestamp=1306828396047, value=/etc/init.d/xfs
 ypbind                               column=meta:name, timestamp=1306828395777, value=/etc/init.d/ypbind
 yum-updatesd                         column=meta:name, timestamp=1306828394506, value=/etc/init.d/yum-updatesd
81 row(s) in 0.8350 seconds

hbase(main):004:0> scan 'inits', {STARTROW => 'hadoop-0.20', COLUMN => 'meta:', LIMIT => 5}
ROW                                   COLUMN+CELL                                                                                                 
 hadoop-0.20-datanode                 column=meta:ctime, timestamp=1306828401092, value=Tue May 31 09:33:28 JST 2011                              
 hadoop-0.20-datanode                 column=meta:mode, timestamp=1306828401072, value=100222                                                     
 hadoop-0.20-datanode                 column=meta:mtime, timestamp=1306828401112, value=Sat Mar 26 09:08:27 JST 2011                              
 hadoop-0.20-datanode                 column=meta:name, timestamp=1306828401024, value=/etc/init.d/hadoop-0.20-datanode                           
 hadoop-0.20-datanode                 column=meta:size, timestamp=1306828401045, value=2919 
 hadoop-0.20-jobtracker               column=meta:ctime, timestamp=1306828390763, value=Tue May 31 09:33:28 JST 2011

〜〜〜略〜〜〜

 hadoop-0.20-tasktracker              column=meta:mtime, timestamp=1306828392393, value=Sat Mar 26 09:08:27 JST 2011                              
 hadoop-0.20-tasktracker              column=meta:name, timestamp=1306828392263, value=/etc/init.d/hadoop-0.20-tasktracker                        
 hadoop-0.20-tasktracker              column=meta:size, timestamp=1306828392284, value=2958                                                       
5 row(s) in 0.2560 seconds

hbase(main):005:0> scan 'inits', {STARTROW => 'hadoop', ENDROW => 'hadooq', COLUMN => 'meta:size'}        
ROW                                   COLUMN+CELL                                                                                                 
 hadoop-0.20-datanode                 column=meta:size, timestamp=1306828401045, value=2919                                                       
 hadoop-0.20-jobtracker               column=meta:size, timestamp=1306828390658, value=2945                                                       
 hadoop-0.20-namenode                 column=meta:size, timestamp=1306828400192, value=2919                                                       
 hadoop-0.20-secondarynamenode        column=meta:size, timestamp=1306828402297, value=3036                                                       
 hadoop-0.20-tasktracker              column=meta:size, timestamp=1306828392284, value=2958                                                       
 hadoop-hbase-master                  column=meta:size, timestamp=1306828392010, value=4248                                                       
 hadoop-hbase-regionserver            column=meta:size, timestamp=1306828392146, value=4272                                                       
 hadoop-hbase-thrift                  column=meta:size, timestamp=1306828394973, value=4248                                                       
 hadoop-zookeeper                     column=meta:size, timestamp=1306828391137, value=4622                                                       
9 row(s) in 0.1820 seconds

途中やたら横長なRubyコードがありますが、改行するとこんな感じ。

Dir.glob("/etc/init.d/*").each{|f|
	base=File::basename f;
	stat=File::stat f;
	put 'inits', base, 'data:', (File::read f);
	put 'inits', base, 'meta:name', f;
	put 'inits', base, 'meta:size', (stat.size);
	put 'inits', base, 'meta:mode', (stat.mode.to_s(8));
	put 'inits', base, 'meta:ctime', (stat.ctime.to_s);
	put 'inits', base, 'meta:mtime', (stat.mtime.to_s);
}

なんてことはなくて、単に/etc/init.d/以下のファイルの内容と情報をひたすらputしているだけ。
ファイル数が思った以上に多かったのと、あとputを各6回叩いているのでやたら出力が出ます。
うーん。本当なら一回/ファイルのputで済むんだけどなぁ。hbase shellで複数カラムいじる方法がわからん。
まぁ、hbase shellはお手軽に、ってことでシンプルに作ってあるのかも。


で、本題のscanとしては、基本的には順番に見ていくだけ。
Thriftの方にはwith prefixという、prefixに前方一致するものだけをscanするAPIがあるんだけど、なぜかhbase shellではSTAETROW/ENDROWしかない。
PREFIX => 'hadooop'と指定できればいいのに。


あとLIMITはいつもつける癖をつけといた方がいいかも。
この状態でなにも考えずscan叩くと、延々とスクロールする画面を眺めるはめに。
Ctrl-Cでhbase shellごと落としてもいいのだけど、irb上で関数とかいろいろ定義した後だとすごくやるきが萎える。


基本的にRowキーを工夫して、一番「よくやる」動作をscan一発でできるようにしておくと楽なのかも。
今回は適当にbasename使ったけど、pathにしておけば、あるディレクトリの中のファイルを全部scan、とかできそう。


hbase shellのだいたいの用法はこんな感じかなぁ。
なんども書いているのだけど要はirbなので、ちょっとした処理やら簡単なバッチ処理なら書けます。

時間切れ

とりあえず今回はhase shellの使い方まで。
次回は実際にtwitterデータをいれようかと思います。
さすがにhbase shellでたたき込むのはアレなので、C++で書いたんですが、OAuthをC++でやるきは起きない。
そんなわけで、Twitter -- Ruby --> XML -- C++ -- Thrift --> HBase、ってな感じです。


# 今回載せるつもりだったC++のコードがあまりにきたないから次回までにきれいにしておこう……。

*1:書いていて気がついたけど、もしかして完全分散モードでも外部のZookeeperいらないやも。まぁ、けど、Zookeeperがmaster兼用でしかも1台だけ、ってのはアリエナイだろうので……。

*2:並んでいるといっても、疑似分散モードだと一つだけですが……。寂しい画面だなぁ。やっぱり完全分散で動かしたいね

*3:あとヴァージョン(Timestamp)も。

*4:というか、rubyの関数