Home > 日記 > 日記2012前期 Archive

日記2012前期 Archive

HiveServerでファイルディスクリプタがリークする件について

@tagomorisさんがこんな事を言っていたので、調べてみました。

まずは、.pipeoutファイルについて。
結論から言うと、Hiveの不具合です。
((追記 2012/06/23 14:00)Hiveのプログラムの書き方は良くないんだけど、Leakはしていないと思われます.)

この.pipeoutファイルはSessionStateのstartメソッドでOpenされます。startメソッドはHiveServerの場合、クライアントがコネクションをはった時にコールされます。(HiveSourceCodeReadingの発表資料にもちょっとだけ書いてあります。)

.pipeoutファイルはHiveServerではpipeInという変数によって、BufferedReader -> FileReaderを通して参照されます。 ↓のような感じ。
pipeIn = new BufferedReader(new FileReader( .pipeoutファイル ))
後始末の処理として、pipeIn = null としています。これがNGです。closeしていません。 

GCによってcloseされないのか?と思うかもしれません。が、されません。
GCによってcloseされるのはFileInputStreamだけ(あとはFileOutputStream)です。
FileInputStreamはfinalizeでcloseする事を仕様として定めていますが、FileReaderは仕様として定めていません。FileReaderの親クラスであるInputStreamReader, Readerともに仕様にありません。
JDK6のソース(OracleJDK, OpenJDK)を見ても、finalize処理は実装されていません。
※ちょっと嘘書きました。末尾に追記しました。(FileReaderはコンストラクタでnew FileInputStreamしていますのでGCでcloseされます)

ちょっと横道にずれますが、.pipeoutファイルってdeleteOnExitしているんですよね。このファイルは毎回異なるファイル名で作成しているので、コネクションをはる度に解放されないオブジェクトが溜まっていきます(まぁメモリリークしているわけですが、定義的にメモリリークと言って良いのかわかんない)。 HiveServerのclean処理で.pipeoutファイルをdeleteしても、deleteOnExitのリスト(正確にはLinkedHashSet)からは消えないままです。

というか、openしたままdeleteできるかどうかってプラットフォーム依存だった気がしますが、もう直ってるんでしたっけ? BugID忘れた&調べる気ない(・x・)

次に.txtファイルについて。
これってLog4jで出力しているファイルの事なのかな?(デフォの設定だと、.logなので違うファイル?)
こっちはまだ調べていませんが、Log4jのFileAppenderの場合、ファイル名を設定する度に1つ前に開いていたFile(というかWriter)をcloseする処理が入っているところまでは確認済み。HiveのExevDriverで以下のように書いていて、これってFileAppenderのreset処理呼ばれるっけ・・。と疑問に思ったところで、こっちはいったん調査終了。

    System.setProperty(HiveConf.ConfVars.HIVEQUERYID.toString(), HiveConf.getVar(conf,
        HiveConf.ConfVars.HIVEQUERYID));
    LogManager.resetConfiguration();
    PropertyConfigurator.configure(hive_l4j);

たぶんこっちの事かな・・。 hive_job_log_(sessionId)_(乱数).txt
こっちの場合は、HiveHistoryというクラスが担当で、PrintWriterを介してファイルをOpenしており、close処理がfinalizeで呼ばれています。このHiveHistoryはSessionStateのstartメソッドでインスタンス化されます。

SessionStateはThreadLocalに保存されます。HiveHistoryはSessionStateが保持しています。HiveHistoryは様々な場所から参照されていますが、他のどこにも保持はしていません(インスタンス生成時にSessionStateを受け取っているので、一見循環参照か!? と思ったけど保持はしていないです。)。Thriftがどういうモデルなのか分からないですが、仮に接続毎にスレッドを生成しているのであれば、接続がきれた時にSessionStateがThreadLocalから消えて他のどこからも参照がない場合(これはまだ確認してない)、SessionStateはGCの対象になります。その時にHiveHistoryへの参照が全て無くなるので、GC対象となるはずです。

仮定が多くて調査になっていないですね(=w=;)
jmapとjhatでどういう状態になっているか確認すれば良いだけなのですけどね。

お腹すいたのでいったん調査終了(・ω・)

※(追記)
当たり前の事なのでわざわざ書いてませんでしたが、一応捕捉しておきます。
(少なくともJavaでは)openしたものはきちんとcloseすることを明示すべきですし、
別にGCまかせにすることを推奨とも良しともしていないです。(そんな事一言も書いてないですよね(>ω<))
原因となりうるかと、それを良しとするかは軸が違うと思うのです。

(さらに追記)

FindBugsで解析をすると分かるらしいです。
今度試してみます。

ふむふむ。tagomorisさんのtweetによると、CDH3u2との事なので丁度ぶちあたってしまうのですね。
あと、確認したソースのバージョンを明記してなかったです。trunkとCDHu4で見てました。

(追記)2012/06/23 13:00

FileReader(File)はコンストラクタでsuper(new FileInputStream(file)) としていました。
という事で、GC走ればcloseされます。 
簡単な確認テスト: https://gist.github.com/2976972 

とあるサービスの技術資料まとめ の目次

せっかくなのでとあるサービスの技術的な事をまとめていこうかなと思ってます。
(作り直しているはずだし、消えるものなら片鱗を残しておこうかなと)

抜け漏れがまだまだあるので、思い出したら随時こっそりと更新していきます。画像も多くなるし、BlogじゃなくてGoogleDocとかのが良いのかな(自分のWikiにまとめる事にしました)。こういう内容は書かないの?とか、このサービス使って書くと良いよとかあったら教えてください。お願いします。
基本的にまったり進行。公開が遅かったらお尻をペシペシと叩いてください。

部署やプロジェクトを兼任していたので、やった事はこれ以外にも沢山ありますが、そのうちの1つとして記録を残します。併せて、なぜそうしたのかという所も覚えている範囲で書いていきます。また、数年前に開発が始まったものですので、今だったらこうするなという事も。
ぼかして書くので抽象的になるかも。
こういう事を去年のCEDECに公募してみようと思ったのですが、作業の遅い自分は間に合いませんでした。

なお、以下の内容はほぼ一人でやった事になります(´;ω;`)ウッ・・

書かないこと

  • 数字に関するもの
  • 内部的な作業フローなど
  • 日々の運用作業について
  • 課金やロビーサーバ、ゲームサーバ、パッチなどの話
    Webから外れるところもばんばん書くことはできますが、このBlogを見ている人はMMOGに興味なさそうだし。興味があれば直接会ったときにでも聞いてください。(言えない事は話しませんけど)
  • 所謂ビジネスロジックと言われるところ
  • 毒・悪口

目次

  • 開発環境について
    • プログラマとプログラマ以外の作業環境
    • レポジトリについて
    • 構成図
    • 継続的インテグレーションについて
    • 開発系のツールについて
    • 通知の話
  • 環境の種類
    • 所謂ステージングとかプロダクション環境とか。全部で5種類。
    • データの共有など。
  • サーバ構成について
  • ミドルウェアについて
    • ミドルウェア一覧と採用した理由など
  • コンポーネントについて
    • HTTPD, APS, Cache, KVS, RPC, Scheduler, MQ, Solr, RDB, API, Watch, ...
  • アプリケーションについて
    • 設計について
    • フレームワークについて
      • どういう構成になっていて、どういうフローで実行されていくか。
    • 機能
      • フレームワークとしての機能
        • 各種ユーティリティ系クラス
        • 多言語、共通基盤、
        • その他
          • BBCodeの実装
          • キーワードハイライタ
      • アプリケーションの機能
        • アカウント(ID/課金、キャラクタ周り)
        • マイページ、通知、日記、グループ機能、フォーラム、画像、イイネ、タグ、フォロー、アクティビティ、プレイログ、検索(4種類)、ランキング
        • バナーやメニュー制御については書かない
      • ニュース・トピックスの話
      • ゲーム連動
      • デバッグの機能
    • テンプレートエンジン
      • 機能の紹介
      • 2,30個以上はあるので他所のエンジンにないものを重点的に。
  • キャラクタのアバター画像について
    • 仕組みについて
    • 夏にやった企画について
  • 検索について
    • 構成の話
    • 拡張した機能について
    • その他
  • i18N対応について
    • ロケールについて
    • リージョンについて
    • タイムゾーンについて
    • アプリ本体
      • 3(4)リージョン
      • 5(6,7)言語
      • Viewレイヤーの話
    • ツール
      • テキスト管理のツールについて
      • 変換ツール
  • JavaScriptについて
  • CDNについて
  • ログについて
    • ログの種類
    • トレースログについて
    • ユーザーログについて
    • その他
  • ログの解析について
  • 監視について
    • 監視の仕組み
    • 通知について
  • RPCサーバについて
    • プロジェクト間共有の仕様でトランスポート層とかの仕様まできっちり決めてあるが、細かい事は言えないので概要。
    • プロトコルの概要
    • 実装について
  • APIサーバについて
  • IRCDの活用について
  • 管理ツールについて
    • 運用をするためのツール
    • モデレーション
  • 運用ツールについて
    • FTPを使わない話
    • NFSを使わない話
    • 作ったツールの話
  • デプロイの話
  • メンテナンスについて
  • 各種障害の内容について(書ける範囲で)

むかしむかし、とある会社で3日で作ったツールについて

むかしむかし、とある会社で3日で作ったツールについてのお話。

昔の社内勉強会用の発表資料が出てきたので、せっかくなので晒してみようと思います。(健全な男子が喜ぶ系のシステムの資料も出てきたのですが、これはまたの機会に・・・)
3年くらい前のもの(ソースみたら2009年でした)なので、そのあたりを考慮していただけると助かります。

なお、Web系の企業では動画とか全然だと思いますが、コンシューマ系の開発だと動画でやりとりできると便利ですよね。

さすがに資料をそのまま載せるわけにはいかないので、所々塗りつぶして、末尾にページを何枚か追加しました。
そういえば、CEDECである人が発表でちょこっと触れたみたいで、4gamerにもスライドが載っていたのですが、名前伏せられてるし(ツール名にプロジェクト名が入っていたので広報チェックでNGになったんだと思います)、モザイクかかってるしでちょっと悲しかった記憶を思い出しました。

(追記 2012/06/28 10:00)
・資料中のRAIDの説明がなんかおかしいです(RAID""二重化)。二重化+パリティのRAIDでNetAppだったらおそらくRAID4の事だと思いますが、詳細は私にはわかりません。
・資料の31ページが見れないという事でしたが、今見たら見れるようになってました。うーん・・?

ツールの概要を一言で言うと、ニコニコ動画のぱくりです。
プロトタイプを1日で、その後2日で作り直したものです。その後細かい修正はちょこちょこ入れてましたが、基本的には放置運用でした。(ごめんなさい・・)

本来の業務を放り出して、これのために3日みっちり作業しました。
一番時間がかかったのは、デザインをニコニコ動画っぽくする事とメンテ画面のトロがナイスボートって言ってる画像を探す時間です。単体テストのコードがほとんどありませんし、正直ソースコードはかなりひどいです。
いくつかのプロジェクトから、欲しいクレクレと来ましたがソースコードを見て絶望したと思います。

作った理由は、スライドにもありますが、
・単純にそこらへんに動画を置かれるとファイルサーバが死んでしまう事
それと、
・ローカルで動画をエンコードするとその間他の作業が出来なくなってしまう
・その動画、他の人全員が見れる形式とは限らない&codecによってはライセンスの購入が必要
という事です。

当時の心境を思い出すと、現実逃避も含まれていたと思います。(煮詰まっているときに他の作業すると楽しいですよね:p)

データセンターに置かなかった理由は、
・ネットワーク帯域がやばい事になる
・データセンターに置く機材の話とか発注とか予算とか
それを正攻法で解決しようとすると、いろんな人といろんな調整をしないといけなくなり、それには膨大な時間がかかります。(これは組織の問題かもしれませんけどね)
余ってるPCなら稟議とか要らないし、ソフマップでHDD買えば普通に経費で落とせるし。
※こんなやり方、真似しちゃダメですよ。いろんな人のヘイトも上がるし、後々面倒な事になります。(当時の状況として大人の事情ってやつがいっぱいありまして.. アルヨネ?そーいうこと。)

MP4をコンテナにしたのは、その当時FlashでMP4/H264を再生できるようになったからです。みんなが見れる環境(つまりブラウザで再生できるもの)であれば何でも良かったのですが、H264画質がすばらしいですし。(画質にうるさい人が多いというわけではなく、画質が悪いと動画中の文字がつぶれてしまう)

一番問い合わせが多かったのが動画がエンコードできないというもの。
みんなFrapsで撮影しているものかとてっきり・・。

アップロード部分をFlashにしているのは、アップロードの進捗状況を分かりやすくするため(アップするファイルが未圧縮なので巨大ですし)と、ブラウザのアップロード2GBの制限(これはブラウザによりけり)を避けるためです。
それなのに、Apacheのビルドをミスって2GB以上アップロードするとContentTypeがオーバーフローして、アップロード出来ない状態が残念です・・。(ajp_proxyもtomcatも確認しましたが、apacheのレイヤーでNGになってました。)

というわけで、いくつか画面をキャプチャしました。私の家で動かしたものになりますので、サンプル動画として私のコレクションをアップしています。当たり前の事ですが会社でこういう動画を共有していたわけではありません。(と、当たり前の事を書いておかないと時々勘違いをした人が騒いだりするので)

TOPページ。
社内(プロジェクト)共通のLDAPがあったので、それを使ってユーザー識別してました。

アップロード画面。
アップロードするファイルのサイズが大きいので、簡単なプログレスを付けてあります。解像度は縮小無し~HD、SD等のサイズを選択できます。有効期限は無制限、1年などなど。これはエンコードした動画を削除する期限ではなく、オリジナルの期限です。基本的にエンコードした動画は削除しない方針でした。

アップロード完了
すると、こんな感じの画面になります。

アップ後
動画をアップすると、キューもどきに入ってサムネイル生成待ちの状態になります。

サムネイル生成
エンコードサーバで動いているデーモンがキューもどきを見ていて、最初にサムネイルを作ります。マウスカーソルを合わせるとパラパラと表示されます。海外の動画サイトに良くあるアレです。

エンコード
サムネイルの生成が終わったら動画のエンコードを開始します。
エンコードの状況をヘッダで表示できます。今どれくらいまで変換しているのか確認する事ができます。
なお、失敗しても(しなくても)リテイクしたい場合はフラグを立てるだけです。

動画の再生ページ。
全画面表示とかもできます。右上のアイコンからメタ情報の編集とか、ダウンロード(MP4 or オリジナル)ができます。

動画の編集画面。
まぁだからなんだって話ですが。。。スライドにも書きましたが、ブラケットで括るとタグとして認識します。

検索画面。
タグを押すとこの画面に飛びます。画像だと1件しかなくて見栄えがあれですね。

簡単なログ画面。
何が起こっているのか知りたい人向け。

RSSは、サイト全体のもの、ユーザー毎や動画毎にあるので、自分が投稿した動画にコメントがついたみたいなものが取れます(たぶん、つけた気がする)

何かを導入したり、使って欲しいものがある場合、一番手っ取り早い方法は本来の趣旨を主張するのではなく、メリットを主張するのが良いと思っています。例えば、Subversionのようなものを使って欲しい場合に、リソースを集中管理して履歴が追えて~とかじゃなくて、レポジトリに入れる事で裏で自動でリソースを変換します、HTMLファイルだったら文法チェックしてくれます、誰が何をやっているのかわかるようになります。とか。このツールもHDDが~とかじゃなくて、ローカルで変換しなくてもアップロードするだけで、あとは良い感じに全部やってくれます。とかとか。
まぁ管理者の幸せとユーザーの幸せはまったく異なる事がほとんどですので、それを押しつけても説得なんてできませんよね。

ツールを作るときは、他のシステムとも連携できるようにAPIを用意したり、何かのプロダクトにべったり依存しないようにするのが理想だと思いますが、当時の私はまだまだ未熟でそこまで気配り出来ませんでした。今なら似たようなプロダクトがあると思うのでそれを使うのが良いと思います。

世の中、他社には面白いツールがいっぱいあるんだろうなぁとイロイロ気になります。
こっそり教えてください(・ω・)

GrowthForecastが便利な件について

定期的に処理を実行するものがあって、その結果や中間結果を可視化したいなーと思うときがあるわけです。表示したい値は簡単に出せますが、それを見やすい形で出すのってけっこう面倒なものです。ログファイルをgrepしろやーはちょっと嫌です。グラフ表示する場合はCactiあたりが有名だと思いますが、グラフの追加に手間がかかるのがツライのです。とりあえずサクッと表示したいのです。

噂によるとkazeburoさんのGrowthForecastというものが便利という事なのでさっそく試してみました。

今回cpanmも初めて入れました。これも便利です。

まずはcpanmのインストール。Fedora17くらいだとyumにあります。CentOSだと無かったので手動で入れます。

cd ~/
mkdir bin && cd bin
curl -LOk http://xrl.us/cpanm
chmod +x cpanm

環境変数の設定。みんなのBLOGを見ると~/perl5を指定するのがお作法みたいですが、home直下にlsで見えるディレクトリが増えるの嫌だったので適当に変更。

export PERL_CPANM_OPT="--local-lib=/home/libido/.perl5_extlib"
export PERL5LIB=/home/libido/.perl5_extlib/lib/perl5:$PERL5LIB
export PATH="/home/libido/.perl5_extlib/bin:$PATH";

RRDToolのインストール

wget http://search.cpan.org/CPAN/authors/id/G/GF/GFUJI/Alien-RRDtool-0.03.tar.gz
cpanm Alien-RRDtool-0.03.tar.gz

GrowthForecastのインストール

git clone git://github.com/kazeburo/GrowthForecast.git
cd GrowthForecast
cpanm --installdeps .

GrowthForecastの起動

# ForeGroundで起動
perl ./growthforecast.pl
# BackGroundで起動する場合はこんな感じ?
perl ./growthforecast.pl > /home/libido/log/growthforecast/gf.log 2>&1 &

後は以下のようにHTTPのPOSTで値を送りつけます。実際はURLのservice_nameとsection_nameとgraph_nameの部分をそれっぽい名前で指定してあげます。
これだけです。
事前にグラフの設定とかは要りません。POSTすると勝手に作ってくれます。
パラメータのmodeは、count, gauge(省略するとこれになる), modifiedを指定できます。numberが送りつける実際の値です。

$ curl -X POST -F number=10 -F mode=count "http://tamsvr03:5125/api/service_name/section_name/graph_name"
{
  "data": {
    "adjust": "*",
    "adjustval": "1",
    "color": "#9933cc",
    "created_at": "2012/06/05 17:06:10",
    "description": "",
    "gmode": "gauge",
    "graph_name": "graph_name",
    "id": 31,
    "llimit": -1000000000,
    "md5": "c16a5320fa475530d9583c34fd356ef5",
    "meta": "",
    "mode": "count",
    "number": 10,
    "section_name": "section_name",
    "service_name": "service_name",
    "sllimit": -100000,
    "sort": 0,
    "stype": "AREA",
    "sulimit": 100000,
    "type": "AREA",
    "ulimit": 1000000000,
    "unit": "",
    "updated_at": "2012/06/05 17:06:10"
  },
  "error": 0
}

これでブラウザから見えるようになります。↓こんな感じ。クローラーのログを可視化してみました。一部ぼかしてあります。

綺麗な構成としては、ログファイルをFluentで~みたいになるのかもしれませんが、今回はプログラムが小さかったのでクローラーが定期的にGrowthForecastに直接投げるようにしました。

APIの注意点としては、numberは整数じゃないとダメです。floatはNG。
ちなみに、時間を指定(後から集計して○時のデータはこれだぜーみたいな)してグラフを描画したい場合は、HRForecast というものがあって、そっちを使うと良いみたいです。

インストールも、使い方も簡単なので、とりあえずさくっと見えるようにする場合にすごく使えるツールです。

参考にしたページ
http://blog.nomadscafe.jp/2011/12/growthforecast.html
http://d.hatena.ne.jp/oranie/20120517/1337225732
http://www.omakase.org/perl/cpanm.html

RubyのURIがホスト名にアンダースコアがあるとparseできない件

Ruby(1.9.3)のURI.parseはホスト名にアンダースコアが入っているとURI::InvalidURIErrorが発生します。

require 'uri'
p URI.parse('http://hoge_fuga.example.com/page?id=xxx#yyy')
`initialize': the scheme http does not accept registry part: hoge_fuga.example.com (or bad hostname?) (URI::InvalidURIError)

仕様として、URIについては、RFC-2396とRFC-3986, IRIとしてRFC-3987があります。
アンダースコアはRFC-2396ではNGだったけど、RFC-3986ではOKになっているのですが、どうやらRubyのURIライブラリの対応が追いついていない(?)ようです。ちょっとRuby界隈には疎いのでどういう状況なのかは正確には把握していません。

代替案を探していたら、addressableというライブラリが対応しているという事なので、さっそく使ってみました。

さくっとgemでインストールします。addressableのバージョンは2.2.8でした。

gem install addressable

簡単な確認。

require "addressable/uri"
uri = Addressable::URI.parse("http://hoge_fuga.example.com/page?id=xxx#yyy")
p uri.host
p uri.port
p uri.request_uri
"hoge_fuga.example.com"
nil
"/page?id=xxx"

パースできました。
ただ、標準のURIだと上記の場合でもportは80を返してくれるのですが、addressableは返してくれないみたいです。しょんぼり。

参考:
http://d.hatena.ne.jp/kitamomonga/20100316/ruby_gem_addressable_howto

 

Hadoop SourceCode Reading 9で発表しました

先週熱が39.3度まで上がって、夜と朝方に未だに咳が止まらない虚弱なタムタムです。

5/30にHadoop Source CodeReading#9がありまして、「Introduce to Hive undocumented feature」というタイトルで発表させていただきました。

完全にネタセッションになってしまいました。
とはいえ、それなりにHiveは使っていたつもりなので何か困ったことがありましたらご相談ください。

HTTPのサーバは、いったん書き直してそのうちGitHubに公開するようにします。

質問があったtimestampの話ですが、
使わない方が良い理由として、
・Timezoneの扱いがちょっと微妙
・UDFのfrom_utc_timestmap, to_utc_timestampがバグってるってレベルじゃない
・一部の処理でメタ情報が正しく扱えない(JDBCからMeta情報がStringになってしまう等)
あたりがあります。

ユースケース的に上記を回避できるのであれば使っても問題ないと思います。
気がついたものはパッチを投げてありますが、コミュニティ純正の0.8, 0.8.1, 0.9, 0.9.1ではひとまず避けた方が良いです。CDH4ではHive0.8系ベースにいくつかのパッチを当てたものを提供してくれるので、Clouderaさんに期待&素直にCDH使った方が良いと思います。

発表スライドは日本語です。英語じゃないです。ごめんなしあ。。
自己紹介の部分と、見せられないよのページは抜いて、ご指摘があった部分は一部資料を修正してあります。
Slideshareにアップしました。

ここから感想

Hive Tools in NHN Japan (@tagomoris)
NHNでHiveをどういう構成、用途で使っているかのお話。Hiveは集計に特化して使っている感じで、そのためのフロントエンドツールは自作している。GitHubにもそのツールは公開されていて、やっぱりどこも同じようなものを作るよねと思いました。(LD界隈のツールのクォリティが高くて恐れおののきます・・)
ゲームのようにキャンペーンを頻繁にうつサービスは別ですが、そうでないサービスの内容が安定している場合、アドホッククエリを流す事ってほとんどないし一度流したものは定常的に流す事が多いので、そういう意味では理にかなっているツールだなと思いました。

Hive Source Code Reading (@wyukawa)
Hiveのソースコードを読んで、最適化まわりについてのお話です。Hiveの内部処理がどうなっているかの説明といくつかの最適化のポイントの解説でした。結論から言うと、Hiveでの最適化はあまりする必要がないという感じ。というのも、HiveのM/R処理は実はMapReduceデザインパターンに書いてあるような事はだいたい実装されていて、それなりに最適なM/R処理を行うようになっています。逆を言えばHiveで処理できるような単純な集計はM/Rを直接書かないでHiveで処理させた方が効率が良い場合が多いです。
個人的には、Explainを流してStage数がいくつになるのかを確認する程度です。

あとがき
勉強会を主催するということ、会場を提供するということは、とても大変な事だと思います。
自分だったら同じように出来ないです。
@shiumachiさん、@hamakenさん をはじめ、素敵な発表をしていただいた@tagomorisさん, @wyukawaさん、そして会場の準備をしていただいた方々にとても感謝しています。
(挨拶回りしなくてごめんなさい。対面で咳こんじゃうの嫌だったので...)

自分の発表は、内容、スライド、発表の仕方、もろもろ反省点が多いのでみんなの内容を参考にお勉強しますです。

AvocadoDBの概要説明とJavaからの扱い方

今回はAvocadoDBの簡単な概念とJavaからの扱い方を説明します。そういえばAvocadoDBはmrubyの人柱に名乗りを上げています。mrubyを使ってみる理由は彼らのBlogに書いてあります。前回の記事でマルチスレッドですと書きましたが、V8をマルチスレッドで扱うのめんどくね?とちょっと思っていたのですが、彼らも思っていたようですw またAvocadoDBのQueryLanguageはRFC化を目指しているようです。 とっても意欲的ですね。

※Version0.3.12を元に記載していますので、今後変わる可能性があります。

さて、まずはAvocadoDBの用語説明をします。全部は解説しません。一部だけです。

用語 説明
Collection RDBで言うところのテーブルです
Collection Identifier コレクションを識別するための内部ID(整数型です)
Collection Name コレクションを識別するための名前です(文字列です)。Collection Identifierと内部的に1:1に対応しています。命名規則は先頭が英字で他は英数かアンダーバー、ハイフンで構成されます。ハイフンを除けば常識的なプログラミング言語の変数名の規則と同じです。
Document RDBで言うところのレコードです
Document Identifier Documentを識別するための内部ID(整数型です)。これを直接使うことはほとんどありません。↓のDocumentHandleを通常は使います。
Document Handle Documentを識別するためのHandle名(文字列です)。書式はCollectionID + "/" + DocumentIDとなります。
Document Revision MVCCのMV(Multi Version)のバージョン番号にあたるものです。整数型です。これとDocument Etagを併せて説明しないといけないのですが、今回は省略します・・。

HTTP-RESTのInterfaceではCollectionNameもCollectionIDも両方サポートしています。またDocumentの実際の値はJSONになります。JSONObjectでも、JsonArrayでも大丈夫ですし、AvocadoDBはKVSでもあるのでJSONの要素(文字列、整数、Boolean、NULL)も受け付けます。

ちょっとだけGraphDBとしてのAvocadoDBも説明します。
Graph構造はVertexとEdgeで構成されます。VertexはDocumentに相当します。Edgeは専用で作るAPIが存在します。有向グラフか無向グラフ(要はエッジの向きがあるか無いか)はEdgeを作るときには指定できません。基本は有効グラフとしてEdgeを作ります。Edgeを取得する時にany, in, outを指定して取ります。VertexはDocumentなので任意の属性を保存する事ができ、Edgeも同様に任意の属性を保存する事ができます。 

Indexの種類は今は3つあります。Hash Index, Geo Index, Skip Listsです。(B-treeが無い理由はVideo見ればわかったりするのかな?)。複合インデックスに出来ますが、Geo Indexは要素は2つまでの制約があります。Indexはユニーク制約を付けることができます。HashとSkipListsの特性の違いの説明は必要ありませんよね。

CollectionやDocumentを作るときに、Diskへの書き込みを同期化するかのオプションがあります。InnoDBのFlushパラメータみたいなのですが、これは基本的にAsyncにしないと性能が出ません。(※性能テストはRoadMapの1.0betaで行う事になっているので、今の段階で性能云々を言うのはちょっとお門違いです)

さて、次はJavaで扱う方法です。Javaのドライバはこちらにあります。JARファイル作っていません。作った方がいいかな?
※prototypeですので、サーバが落ちている時のハンドリングを入れていません。

今できる事は、HTTP-RESTを叩いてCollection, Document, Cursor, Index, Edgeの操作ができます。

使い方はREADMEかExampleに書いてあります。単体テストを見るのが良いと思います。
以下はDocumentを1000個適当に作ってクエリで検索するサンプルです。Cursorを扱いやすくするためにResultSetを作ってみました。

public class Example1 {

  public static class ExampleEntity {
    public String name;
    public String gender;
    public int age;
  }

  public static void main(String[] args) {

    AvocadoConfigure configure = new AvocadoConfigure();
    AvocadoDriver driver = new AvocadoDriver(configure);

    try {
      for (int i = 0; i < 1000; i++) {
        ExampleEntity value = new ExampleEntity();
        value.name = "TestUser" + i;
        switch (i % 3) {
        case 0: value.gender = "MAN"; break;
        case 1: value.gender = "WOMAN"; break;
        case 2: value.gender = "OTHER"; break;
        }
        value.age = (int) (Math.random() * 100) + 10;
        driver.createDocument("example_collection1", value, true, null, null);
      }

      HashMap<String, Object> bindVars = new HashMap<String, Object>();
      bindVars.put("gender", "WOMAN");

      CursorResultSet<ExampleEntity> rs = driver.executeQueryWithResultSet(
          "select t from example_collection1 t where t.age >= 20 && t.age < 30 && t.gender == @gender@",
          bindVars, ExampleEntity.class, true, 10);

      System.out.println(rs.getTotalCount());
      for (ExampleEntity obj: rs) {
        System.out.printf("  %15s(%5s): %d%n", obj.name, obj.gender, obj.age);
      }

    } catch (AvocadoException e) {
      e.printStackTrace();
    } finally {
      driver.shutdown();
    }

  }

}

グラフも扱えます。 グラフの扱い方は以下のようになります。ただし効果的にトラバーサルする方法がまだ提供されていません。TinkerPopのBluePrintsとGremlinに対応するらしいので、そのあたりで出来ることは将来的にできるようになると思います。(私はGremlinでGraphを操作した事がないので詳細はわかりません。)
以下のサンプルは、Vertexを10個作って、エッジを3つ(e1=v0->v1, e2=v0->v2, e3=v2->v3)作り、v0のエッジを求めています。 

public class Example2 {

  public static class TestEdgeAttribute {
    public String a;
    public int b;
    public TestEdgeAttribute(){}
    public TestEdgeAttribute(String a, int b) {
      this.a = a;
      this.b = b;
    }
  }
  public static class TestVertex {
    public String name;
  }

  public static void main(String[] args) {

    AvocadoConfigure configure = new AvocadoConfigure();
    AvocadoDriver driver = new AvocadoDriver(configure);

    final String collectionName = "example";
    try {

      // CreateVertex
      ArrayList<DocumentEntity<TestVertex>> docs = new ArrayList<DocumentEntity<TestVertex>>();
      for (int i = 0; i < 10; i++) {
        TestVertex value = new TestVertex();
        value.name = "vvv" + i;
        DocumentEntity<TestVertex> doc = driver.createDocument(collectionName, value, true, false, null);
        docs.add(doc);
      }

      // 0 -> 1
      // 0 -> 2
      // 2 -> 3

      EdgeEntity<TestEdgeAttribute> edge1 = driver.createEdge(
          collectionName, docs.get(0).getDocumentHandle(), docs.get(1).getDocumentHandle(),
          new TestEdgeAttribute("edge1", 100));

      EdgeEntity<TestEdgeAttribute> edge2 = driver.createEdge(
          collectionName, docs.get(0).getDocumentHandle(), docs.get(2).getDocumentHandle(),
          new TestEdgeAttribute("edge2", 200));

      EdgeEntity<TestEdgeAttribute> edge3 = driver.createEdge(
          collectionName, docs.get(2).getDocumentHandle(), docs.get(3).getDocumentHandle(),
          new TestEdgeAttribute("edge3", 300));

      EdgesEntity<TestEdgeAttribute> edges = driver.getEdges(collectionName, docs.get(0).getDocumentHandle(), Direction.ANY, TestEdgeAttribute.class);
      System.out.println(edges.size());
      System.out.println(edges.get(0).getAttributes().a);
      System.out.println(edges.get(1).getAttributes().a);

    } catch (AvocadoException e) {
      e.printStackTrace();
    } finally {
      driver.shutdown();
    }

  }

}

Hiveのパッチ投稿のやりかた

こんにちは、ポテトサラダを極めようと心に決めたタムタムです。
(昨日作って食べたら満足しました(・w・))

今日はHiveのパッチの書き方をまとめます。
Hadoop本体とはちょっと作法が違います。Hadoop本体はパッチを投げると自動でJenkinsがテストしてくれたりするらしいのですが、Hiveはそんなことやってくれません。またパッチもJIRAに添付するだけではダメで、Phabricator(Reviewボードみたいなもの)というツールを使ってDiffをアップしてReviewしてもらう必要があります。

今回は実際にHIVE-2703のパッチを投げるまでの手順を書いていきます。
http://mt.orz.at/archives/2012/02/hivejira.html こちらの内容と被っていますが、こっちでは説明しきれなかったところも書いていきます。
なお、手順はsubversionの手順になっています。ぶっちゃけgitの方をオススメします。subversionだと複数のパッチを書くときにローカルでブランチを切れないのでとってもとってもつらいです・・。(私がsubversionを使っているのは、gitだとphabricatorのarcコマンドがうまく動作せずにarc diff --jira HIVE-xxxでパッチをあげることができなかったからです)

なお私はFedora16をVirtualBoxの上で動かして開発しています。(MacbookとかオシャレなPC持っていません・・)
あと、Hadoopの環境は普通に作っておいてください。ローカルモードで良いです。これも省略。

今やいろんな本が出ていますが、Hadoop徹底入門がオススメです。

まずざっくりとした手順は以下のようになります。

  • ソースを落とす
  • ソースをいじる
  • テストする
  • (JIRAで適宜コメント書いたり、やりとりしたり)
  • Phabricatorでreviewして貰う
  • 最終版のDIFFをJIRAに添付する(ASFライセンス付ける)

細かい手順を説明していきます。

まず、ソースを落としてEclipseのプロジェクトを作ります。(Eclipse使わなくてもパッチは投稿できます。ただJavaのソースをIDE無しで書くのは結構つらいと思います。)
なお、ソースを落としてきたら、HIVE-2902, HIVE-2904のパッチを当てましょう。今のバージョンだと正しいEclipseのテンプレートを作ってくれません。

# ソースを落としてくる
svn checkout http://svn.apache.org/repos/asf/hive/trunk hive-trunk
cd hive-trunk
# パッチを当てる(※1)
wget https://issues.apache.org/jira/secure/attachment/12523301/HIVE-2902.2.patch.txt
wget https://issues.apache.org/jira/secure/attachment/12522738/HIVE-2904.D2487.3.patch
patch -p0 < HIVE-2902.2.patch.txt
patch -p0 < HIVE-2904.D2487.3.patch
# ビルドする
ant clean package
# Eclipse用のプロジェクトを生成する
ant eclipse-files
# テストドライバとか生成する
cd metastore
ant model-jar
cd ../ql
ant gen-test

※1 追記(2012/04/19 13:30) パッチのバージョンを変更しました。最新のソースだとDDLTaskがjson形式で出力できるようになっていて、JsonMetaDataFormatterがjacksonに依存するようになっていました。

これでhive-trunkにeclipseのprojectファイルが出来ています。あとはEclipseからインポートするだけです。なお、Eclipseの使い方は今更説明するようなものではないので省略します。注意点としては、ワークスペースの文字コードをUTF-8に、JDKは6を使いましょう。

また、プロジェクトの設定で「Javaエディタ → 保管アクション」の設定でインポートの編成が行われるようになっています。なのでパッチを書いた時にdiffを取ったらいじっていないところも変更されている事があります。これは気にしたら負けです。
もう1つ、プロジェクトの設定の「ビルダー」で「Hive_Ant_Builder」のチェックは外した方が良いです。保存する度にAntが走って重めのビルドが走ることになってちょっとイラッとします。

ここまでで、開発環境は整いました。
というわけで、普通に開発をしましょう。なお、パッチを出す時は基本的にテストケースも一緒に追加します。(パッチの内容にもよりますけど)。

次にテストの方法です。単体テストはJUnitで書かれています。Eclipse用のプロジェクトを生成した時に、実行構成も既に設定されています。例えばJdbcDriverTestという単体テストの実行構成を見ると、以下のように既に必要な設定(VM引数、環境変数、作業ディレクトリなど)が入っています。


どのクラスがどの単体テストが担当しているのかは、クラスの参照を調べればなんとなく推測できます。まずは関連する単体テストがピンポイントで通るようにするのが良いです、なお、UDF関数やクエリのテストはまたちょっと違います。後者のテストの方法は以下のようにします。 このテストは、「ql/src/test/queries/clientpositive/timestamp_udf.q」を実行した時に「ql/src/test/results/clientpositive/timestamp_udf.q.out」の結果になるかというテストです。前回の記事にも書いてあります。 -Dqfileを省略すると全部のテストを実行します。

# ポジティブテスト
ant -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false test
# ネガティブテスト
ant -Dtestcase=TestNegativeCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false test

このあたりをピンポイントで実行してから、最後に全体のテストを実行します。非情に時間がかかります。4,5時間は覚悟してください。寝る前に実行しましょう。

ant package test
ant testreport

これで build/test/junit-noframes.html というファイルが生成されます。このファイルにテストの結果が書いてあります。

パッチを投げる前にsvn diffを取って差分を確認してください。(※最初に環境構築をするためにパッチを当てたやつは除外する必要があります。)

# LANGが日本語になっていると「リビジョン」という日本語が入るので
export LANG=C
svn diff > HIVE-xxxx.1.patch.txt

gitの場合は、以下のような感じです。

git diff --no-prefix > HIVE-xxxx.1.patch.txt

これでパッチを投げる準備ができました。(後述するPhabricatorを使わない場合は、このDIFFをJIRAにファイル添付すれば良いです。)

チケットの開き方は前回の記事を参考にしてください。
※こんな事を書くと怒られるかもしれませんが、ぶっちゃけ英語なんて適当で通じます(たぶん)。英語が出来なくてウジウジするくらいならコードを書いてパッチを投げましょう。(もしくは知り合いに英訳して貰いましょう)。自分たちが外国人のニンジャ語を理解できるように向こうの人もきっと理解してくれます。たぶん(・3・)。

パッチの投稿の仕方です。Phabricatorをインストールする必要があります。以下のURLを参考にします。
http://www.phabricator.com/docs/phabricator/article/Arcanist_User_Guide.html
https://cwiki.apache.org/confluence/display/Hive/PhabricatorCodeReview

# どこかのディレクトリで以下を実行
git clone git://github.com/facebook/libphutil.git
git clone git://github.com/facebook/arcanist.git
# BashのTab Completion(arc[TAB]で引数を表示してくれる)を有効にする
source arcanist/resources/shell/bash-completion

その後、以下のコマンドを叩いて準備をします。
arcanist/bin を環境変数PATHに通しておくと良いです。

ant arc-setup
arc install-certificate

Phabricatorにパッチを投げる手順は以下になります。これは実際にsvnかgitでdiffを取ってPhabricatorに投稿します。これでJIRAにパッチが添付されます。

# svnの場合
arc diff --jira HIVE-xxxx
# gitの場合(gitの場合はarc diffで--previewオプションが使えます)
git checkout HIVE-xxxx-dev-branch
ソース編集
git commit -a --amend --reset-author -C HEAD
arc diff

以上でJIRAにパッチが投稿されるので、「Submit Patch」を押します。するとステータスがPatch Availableになります。 パッチがNGな場合は、レビューの内容やコメントの内容を見て修正しましょう。

レビューが終わったら最終バージョンのパッチをJIRAに添付します。添付する時はASFライセンスを付与しましょう。この時に新機能の追加や互換性が無くなる変更の場合はReleaseNoteを各必要があります。 

私はHiveにはそんなに詳しくない初級者ですが、何か困ったことがあれば協力できるかも。

なお、Hadoop Hackathonというものがあります。
http://www.zusaar.com/event/250002

私が参加した時は、JIRAの使い方とパッチの書き方を最初に教えていただきました。基本は黙々とハッカソンするわけですが、
お菓子を食べながらプロジェクターにソースを映してアレやコレやと楽しかったです。
開発環境とJavaさえ書ければ、英語は出来なくてもみんな英語が出来るので誰かが助けてくれます。

Hadoopのここが気にくわないわー。Hiveのここが気にくわないわー。あのBug放置されてるわー、直したいわー。という人は参加を考えてみると良いかも。

TwitterJapanの綺麗なオフィスに遊びに行ってきました

書こう書こうと思って今更ながら記事を書きます。
TwitterJapanに遊びに行ったので、その時の写真を載せておきます。

先月はハッカソンイベントのために行ってきました。2,3時間で成果物出すのはきついw とりあえず動くもの2個つくった。(Twitterの内容をIRCへ垂れ流すやつと、Stream中のURLをリアルタイム集計していくやつ)。RedisとTwitter4jサマサマ。

このクッション欲しい・・。

4/3(風強すぎ・・)にも行ってきました。
こっちはメディアの方もいっぱい来ていたのであちこちで記事になっていると思います。聞いた内容をずらずらと書いても良いのですが、他の人が綺麗にまとめているので省略。

RobがJavaOneのために来日していて、Twitterのアーキテクチャの話をしてくれました。写真はこの前のバルスの時の負荷に関するもの。

個人的に気になるのは、Staging環境とPreFlight環境でのテストデータについて。Production環境から引っ張ってきていると思うんだけど、そこがどういうアーキテクチャになっているのかが気になる。ストレージのレイヤがMySQLと言っていたけど、ストレージのレイヤで解決しようとすると、単純にレプリケーションでStaging用のデータ作ってる場合はWriteしたら色々おかしくなるだろうし。Thriftでうにうにやってるレイヤーでやってるのかな・・。

というあたりがモヤモヤ。

JavaOneでのRobのセッションのつぶやきのまとめはこちら、Twitter Tokyo Open Houseのまとめはこちら

 

そういえば、Oracle Open WorldのさくらさんのZFSの話ってどこかに載ってないのかな。すごく気になる。
(Open Worldという名前なのに全然オープンじゃn(ry)

退職しました

このたび、株式会社スクウェア・エニックスを退職しました。
正確には4月2日が最終出社、4月30日付けで退職となります。

2006年に入社し、約6年勤めた事になります。
PlayOnlineというプロジェクトに始まり、FinalFantasyシリーズに関われた事はとても貴重で刺激の多い体験をする事ができました。(他にも兼任していたけど省略します)

 

エンジニア業務としては、
サーバも書かせて頂きましたし、Webのお仕事も立ち上げから運用まで一貫して携わらせて頂きました。独自のプロトコルを実装したり、フロムスクラッチでサイトを立ち上げたり。特にサイト(SNSっぽいもの)の立ち上げは、実装全般、開発環境やらステージング環境の構築も含めてサーバ構成なども良い経験でした。さっき数えたらそのサイトのために実装したクラスは1000ちょっとでした(意外と少ない)。Webサイトと言ってもSNSとしての機能をだいたい実装し、ゲームと連動させるためにMMOのサーバやスクリプトを読み解いたり、モデルの仕様を聞いてキャラクタの画像を表示できるようにしたり。なぜかゲームのサーバからクライアント、スクリプト、モデル仕様等までわかるようになっていました。
いろいろと問題の多いプロジェクトでもありましたが、逆にそのおかげで領域、職種関係なく自分でやらないと物事が進まないことも多く、様々な知見を得ることができました。
本当に0~10までやった感じです。

そういえばマイニング的な事もしてました。統計やマイニングって楽しいですよね。自分しか知らない秘密の性質を発見したり。自分で企画書書くときに、刺さるレイヤーがどんな感じかというのを知りたいためにやっていました。
ちなみに、こういうのは目的ありきでやらないと意味がないですし、その結果やその先にあるものを誰に(何に)フィードバックするかが重要だと考えています。データは(読み方を間違えなければ)嘘をつきませんが、データが全てではありません。経験や直感も大切です。

 

Webディレクター的な事やプランナー的な事もやらせて頂きました。
自分で企画書を書き、トップレベルのレイヤーに「やっていいよね?」程度の軽く承認を取っただけで、他セクションのいろんな人を巻き込んで物事を進めていったのは良い思い出です。直近では、あの企画がノリノリで一番楽しかったです。自分のスキルセットではどうにもならない部分(BG、フェイシャル、キャラ、ライト、モーション等)を協力してくれた方に特に感謝してます。
「こんな事考えているんですけど楽しそうじゃないですか?」 → 「実は動く物あります。作っちゃったー(てへぺろ)」 → 「でも私の実力だとMSペイントが限界だなぁ!デザイナさん達がやったら凄く良くなるんだろうなぁ!!(チラッチラッ)」 → 「わぁい 協力者げっとー」 という感じで巻き込んでいったのが多かった気がします。
良いやり方とは言えませんが(リスクが高いですし)、世の中にはやりたいしか言わない人も多いので「やりたいじゃないんだよ、やるんだよ!」という感じで自分から動けば物事を進めやすい環境でした。

 

仕事をする上で重点を置いていた事がいくつかあります。(細かく言うと他にもたくさんありますがきりが無いので。)
・自分より後に作業をする人には徹底的に配慮する。(デザイナ、QAチーム、運用チームなど)
・ユーザーサポート系の問い合わせ(ユーザーへ返信が必要なもの)については最優先(障害対応を除く)で対応する。
・ルールや規則が何度も弊害になる場合、ルールが間違っている事を疑う。
・拘るところには徹底的に拘る。そうじゃないところは(良い意味で)手を抜く。
・タスクの締め切りに曖昧な言葉(今日中、今週中 等)を使わない。
・最初にやりたい事のMAXを出してどうあるべきかの理想型を考える。いきなり内容をそぎ落としたりしない。
・「やりたい、やりたい」じゃなくて「やるんだよ!」
・出来ない理由を探して「出来ません」じゃなくて、「どうやったら出来るのか」を考える方が遙かに生産的。

 

ところで、私が毒吐きキャラというのは、まぁ周知の事実だとは思います。
(※ただし私が毒を吐くときは、"なんでこんなこともできないんだ"という事で吐くことはありません。基本的には自分でやるって言ったことをやらない場合に毒を吐きます。できないと言ったことはそもそも要求しません。)
それでも大人な対応でサポートしていただき、自分は周りに恵まれていたと思います。
本当に自由にやらせて頂きました。この会社では珍しいほど自由に動き回れました。これも周りの人のサポートがあったからだと思います。本当に多くのご迷惑をおかけしました。そして本当にありがとうございました。

ある上司に言われた「やりたいようにやれ。やっちゃいけない事をやったら叱るから、叱られるまでやれ(意訳)」という言葉は印象的でした。その結果こんな人材ができあがりました(笑)
辞めた人が在籍していた会社をDisっているのを時々見かけますが、私はこの会社が好きでしたし今でも好きな会社です。私がたまたま環境に恵まれていただけかもしれませんが、少なくとも私には働きやすい良い会社でした。
楽しい楽しい充実した日々を駆け抜ける事ができました。

 

やり残した事も正直多いです。
ただ、それ以上にやりたい事も多くなりました。

退職の理由は「いい年してなにゆってるの?」と言われそうなので恥ずかしいのですが、同じ方向性のスキルや問題意識を持ったエンジニアの方々と肩を並べてお仕事をしたい、自分もトップエンジニアの領域まで登り詰めたいと思った事です。
そのために暫く充電期間を取ろうと思いました。

 

なお、今まで多くの企業からお声をかけていただきました。在職中にお話をしたところも多々あります。その時は状況とタイミングが悪かったため全てお断りをしていました。
今は心身ともに余裕があります。
お食事、飲み、勉強会、情報共有、またはそういうお話などなど、カジュアルにいつでもWelcomeです(・ω・)ノ

それでは、今後ともよろしくお願いします。

 

MurmurHash Java版

リハビリのためにMurmurHashをJavaに移植してみました。
Version1,2,3全てありますので、32bit,64bit,128bitのハッシュを求めることができます。

ソースはこちらです。
https://github.com/tamtam180/MurmurHash-For-Java

オリジナルと同じ結果を返すことは確認済みです。

MurmurHashってなんぞという人はオリジナルのサイトをどうぞ。
https://sites.google.com/site/murmurhash/
http://code.google.com/p/smhasher/

github便利だけど、バージョン管理して配布するような場合ってGoogle Projectの方が良いような気がしてる。パッケージをダウンロードさせたい場合、Githubではどうやるのかわかんないや。

HiveをEclipseを使って開発する時の手順で躓いた時の対処法

HiveをEclipse使って開発しようとするとき、こちらのページには以下のようにしましょうねと書いてあるのですが、

$ ant clean package eclipse-files
$ cd metastore
$ ant model-jar
$ cd ../ql
$ ant gen-test

最後の ant gen-test を実行すると、以下のエラーが発生します。(trunkのrev 1293919で確認)

BUILD FAILED
/home/tamtam/work/hive-trunk/ql/build.xml:116: Problem: failed to create task or type if
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any <presetdef>/<macrodef> declarations have taken place.

<if>というタスクが無いよーというエラーです。 ${hive.root}/testlibs/ant-contrib-1.0b3.jar このJARファイルのIfTaskは中に入っており、build.xml中でtaskdefしてあげないといけません。以下の定義を追加してあげる必要があります。${hive.root}/build.xmlには定義されているんですけどね。。

<taskdef resource="net/sf/antcontrib/antcontrib.properties">
  <classpath>
    <pathelement location="${hive.root}/testlibs/ant-contrib-1.0b3.jar"/>
  </classpath>
</taskdef>

この定義をql/build.xmlのgen-testのtarget配下に書いてもいいのですが、いろんなbuild.xmlがbuild-common.xmlをimportしていて、さらにbuild-common.xmlでも<if>タスクを使っているので、build-common.xmlに定義するのが良いかと思います。

diffはこんな感じです。

Index: build-common.xml
===================================================================
--- build-common.xml    (リビジョン 1293919)
+++ build-common.xml    (作業コピー)
@@ -89,6 +89,12 @@
   </condition>
   <import file="build-offline.xml"/>

+  <taskdef resource="net/sf/antcontrib/antcontrib.properties">
+    <classpath>
+      <pathelement location="${hive.root}/testlibs/ant-contrib-1.0b3.jar"/>
+    </classpath>
+  </taskdef>
+
   <!--this is the naming policy for artifacts we want pulled down-->
   <property name="ivy.artifact.retrieve.pattern" value="[conf]/[artifact]-[revision](-[classifier]).[ext]"/>

つか、誰も困って無いのかな・・。
それっぽいチケットも無いんだけど。

HiveのUDF一覧を自動生成してみた

発熱でDOWNしているタムタムです。
インフルエンザのようなすごい高熱というわけでもなく、プリン食べて家でしょぼーんとしてます。

今回はHiveのUDF一覧を自動生成してみました。(Hive0.8)
http://mt.orz.at/archives/hive_udf_0.8.html
(追記) Hive0.8用はURLを変更して、こちらにtrunk(rev1293519)のバージョンを起きました。

reflect関数とかWikiには載っていないと思います。
builtinと書いてあるやつはFunctionRegistryのstaticイニシャライザで関数登録しているやつです。Alias名がある関数はそれぞれ1つの項目に作ってあります。

作り方は至ってシンプルで、UDF関数はDescriptionというアノテーションが定義されています。desc function xxxxという構文で表示される説明は、このアノテーションの情報を表示しているだけです。というわけで、SpringFrameworkのClassPathScanningCandidateComponentProviderを使ってClassPath中のDescriptionアノテーションが定義してあるクラスを取得して情報を拾っているだけです。

こんな感じです。

ClassPathScanningCandidateComponentProvider scanner =
        new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Description.class));
Set<BeanDefinition> beans = scanner.findCandidateComponents("");

取得したBean定義を回して、リフレクション使ってAnnotation情報を取得します。
nameをカンマで区切っているのは、Aliasがある場合はカンマ区切りで入っているからです。あとはdescから情報を普通に引っこ抜けます。

for (BeanDefinition def : beans) {
    Class<?> clazz = Class.forName(def.getBeanClassName());

    Description desc = AnnotationUtils.findAnnotation(clazz, Description.class);
    String[] funcNames = desc.name().split(",");
}

簡単ですね (=ω=)b

Hiveのテスト方法とJIRAチケットの開き方

Hiveのチケットを開いてBug報告をしてみたのでその手順をメモメモ。せっかくなのでテストの方法とかも書いておきます。
(チケットの内容はほとんどshiumachiさんに書いたもらったものですけどね(ノ∀`))
shiumachiさん、丁寧に教えていただきましてありがとうございます。

https://cwiki.apache.org/confluence/display/Hive/HowToContribute
Wikiにも書いてあるので、こちらを参考にすると良いと思います。

■ trunkで再現するかの確認について
Eclipseを使ってやる場合は、こちらのページが参考になると思います。 今回は簡単な修正だったのでコンソール上だけで行いました。 trunkを落としてきてビルド、Hiveを起動する手順を以下に書きました。設定ファイルを特に作っていないのでMetastoreはカレントディレクトリに作られます。MetastoreをMySQLに入れるところがーとかを修正する場合は、ちゃんとconfの下に設定ファイルを作って設定しましょう。 (あ、HADOOP系の環境変数は設定済みの前提です)

$ git clone git://git.apache.org/hive.git
$ cd hive
$ ant clean package
$ cd build/dist
$ export HIVE_HOME=`pwd`
$ cd bin
$ ./hive

今回初めてgitを使いました。 Hiveの規模であれば、--depthオプション無くてもいけます。 svnの場合はwikiにある通りに、以下のようにソースを持って来ます。

svn co http://svn.apache.org/repos/asf/hive/trunk hive-trunk

■ソースを修正したらテストをします。
私のPCではテスト全部通すと4時間程度かかりました。

全てのテストを実行する場合は以下のように実行します。テストが終わったらテストレポートも作ります。 テストレポートはbuild/testディレクトリにjunit-noframes.htmlというファイルが作られます。このレポートファイルを見ると、何のテストが失敗したのかと、簡単な情報(例えばExceptionが出ているとか)がわかります。

$ ant test
$ ant testreport

個別にテストをする場合は、以下のようにします。qファイルはフルパスではなくファイル名を指定します。(以下の場合はql/src/test/queries/clientpositive/timestamp_udf.qのテストをしています)

$ ant test -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false

なお指定したqueryのテストに対応する正解データは.q.outファイルです。上記の場合は、ql/src/test/results/clientpositive/timestamp_udf.q.outのファイルが正解データとなります。なお、正解データを実行結果で上書きする事ができます。まぁこれ手で正解データ作るのは無理ですw 以下のコマンドで上書きできます(-Doverwrite=trueを追加)。

$ ant test -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Doverwrite=true -Dtest.silent=false

私は今回上記のテストケースファイルに追加する形で対応しました。
テストする時は、上記のように影響があるテストをピンポイントで実行して最後にテストを全部通すと良いと思います。
全体テストは時間がかかるので、並列実行する方法があります。(直列のまま実行する場合は、寝る前に実行すれば良いと思うよ!!)

テストに関する情報は、こちらのWikiのページのTestingの項目が参考になります。
(パッチを投稿する場合)テストが通ったらパッチを作ります。

■パッチの作り方について

パッチの作り方はもHow To Contributeのページに書いてあるとおり、以下のように作ります。 パッチファイルはテキストファイルとして作った方がブラウザで見れるのでその方が良いです。「Appending '.txt' to the patch file name makes it easy to quickly view the contents of the patch in a web browser.」とWikiに書いてありますし、shiumachiさんからもその方がいいよーとの事です。

git diff --no-prefix > HIVE-xxxx.1.patch.txt

■JIRAのチケットをOPENします。

ちなみに、 https://gist.github.com/1797071 これを参考にすると良いよ!
shiumachiさんの添削が載っているので、とっても参考になると思います。
(githubのアカウントもこの時に作ったので、今度からちょこちょこ何かソースアップしていきます。)

実際にOPENする方法は、まずはJIRAのアカウントを作ります。(アカウントの設定でLanguageを日本語に出来ますが、逆にわかりづらくなって発狂するのでEnglishのままのが良いです。(HiveのJIRAのページはここです)
右上のCreateIssueを押して「Hive」「Bug」を選択して「Create」を押すか、その下のCreate: Bugを押します。(下の絵を参照)

20120213_jira_ticket_01.png

Issueの内容ですが、IssueTypeにはBugが設定されていると思うので、他の項目を入力していきます。

Summary: いわゆるタイトルです。
Priority: この項目は以下を参考にします。

Blocker: Blocks development and/or testing work, production could not run
Critical: Crashes, loss of data, severe memory leak.
Major: Major loss of function.
Minor: Minor loss of function, or other problem where easy workaround is present.
Trivial: Cosmetic problem like misspelt words or misaligned text.

Component/s: 対象のコンポーネントを選択します。今回は「UDF」を選択しました。
Affects Version/s: 発生するバージョンを選択します。今回はHive0.8を選択しました。Trunkの場合は選択しないようです。選択されているチケットが少ない気がするのですが、リリースされているバージョンで発生するので、ちゃんと選択してみました。
Fix Version/s: 選択しませんでした。(これはコミッターが後で選択するもの?)
Environment: なんか書いている人が少ない。。Hadoopのバージョンとか書くのかな・・。空欄にしちゃいました。
Description: チケットの内容を書きます。

あとは空欄にしちゃいました。(ノ∀`)

パッチを添付する場合は一度チケットを作ってから添付します。
More ActionsのAttach Filesを選択すると添付できます。

実際に作ったチケットはこれです。
https://issues.apache.org/jira/browse/HIVE-2803

何か作法が間違っていたら追記していきます(・x・;)

Hive0.8のfrom_utc_timestamp, to_utc_timestampの不具合を修正する

HadoopアドベントカレンダーでTimestampの機能について書いた時に見つけた不具合を調査してみました。 自分の備忘録用にメモメモ。

from_utc_timestampまたはto_utc_timestampをSELECT句に複数書くと値がおかしくなるというものです。正確には、1つ書いても壊れます。まずは再現コード。

create table ts1(t1 timestamp);
load data local inpath '/mnt/iscsi/test1/data4.txt' overwrite into table ts1;
select t1 from ts1;
2011-12-25 09:00:00
select from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 18:00:00

うまくいっているように見えますが、、

select t1, from_utc_timestamp(t1, 'JST'), t1 from ts1 limit 1;
2011-12-25 18:00:00     2011-12-25 18:00:00     2011-12-25 18:00:00

おや、全部の値が変わってしまいました。さらに、from_utc_timestamp(t1, 'JST')を追加してみます。

select t1, from_utc_timestamp(t1, 'JST'), t1, from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-26 03:00:00     2011-12-26 03:00:00     2011-12-26 03:00:00     2011-12-26 03:00:00

SELECT句で書いたt1が全て同じ値になります。
次に実装クラスを見てみます。org.apache.hadoop.hive.ql.udf.genericパッケージのGenericUDFFromUtcTimestampとGenericUDFToUtcTimestampです。GenericUDFToUtcTimestampはGenericUDFFromUtcTimestampを継承していて、フラグに応じてOffset値の正負を反転させているだけです。で、問題はGenericUDFFromUtcTimestampのapplyOffsetメソッドです。ソースを下に書きました。

  @Override
  public Object evaluate(DeferredObject[] arguments) throws HiveException {
    Object o0 = arguments[0].get();
    TimeZone timezone = null;
    if (o0 == null) {
      return null;
    }

    if (arguments.length > 1 && arguments[1] != null) {
      Text text = textConverter.convert(arguments[1].get());
      if (text != null) {
        timezone = TimeZone.getTimeZone(text.toString());
      }
    } else {
      return null;
    }

    Timestamp timestamp = ((TimestampWritable) timestampConverter.convert(o0))
        .getTimestamp();

    int offset = timezone.getOffset(timestamp.getTime());
    if (invert()) {
      offset = -offset;
    }
    return applyOffset(offset, timestamp);
  }

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    int nanos = (int) (t.getNanos() + (newTime % 1000) * 1000000);
    t.setTime(newTime);
    t.setNanos(nanos);

    return t;
  }

行を評価中、timestamp変数は同じインスタンスを毎回返してきます。そのインスタンスに対してapplyOffsetでsetTimeしちゃってます。つまり、その行を評価中はt1は同じインスタンスを使い回してしまいます。なのでSELECT句に書いたOffset値全てが計算された結果が出力されるわけです。そして、1列評価→出力→1列評価→出力ではなく、全ての列を評価→出力なので、出力時には全て同じtimestampインスタンスを指すことになって、全て同じ値が表示されるわけです。(ここで言う全てはt1を参照している列という意味です)

修正方法ですが、結局のところは同じインスタンスを指せないという事で、インスタンスを生成する必要が出てきます。というわけで、手っ取り早く修正するのであればapplyOffsetを修正します。以下が修正版です。

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    int nanos = (int) (t.getNanos() + (newTime % 1000) * 1000000);
    Timestamp t2 = new Timestamp(newTime);
    t2.setNanos(nanos);

    return t2;
  }

(追記)上記のコードはダメでした。nano時間のところがそもそもバグっていて秒未満の値がずれます。以下のコードの方が良いです。(timestamp_udf.q.outの秒未満部分もずれてました・・)

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    Timestamp t2 = new Timestamp(newTime);
    t2.setNanos(t.getNanos());

    return t2;
  }

hiveをビルドしなおして再度実行してみます。 

select t1, from_utc_timestamp(t1, 'JST'), t1, from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 09:00:00     2011-12-25 18:00:00     2011-12-25 09:00:00     2011-12-25 18:00:00

直りました。
さて、Hadoop、Hiveのコミュニティっていきなりチケットを開いてもいいのかな。ちょっとMLとJIRAを漁って作法を調べよう。

 

チケット開く前にパッチを置いておこう。Javaソースとテスト用のqファイル、q.outファイルのパッチは分離して2つに分けた方が良かったかな・・。
http://mt.orz.at/archives/HIVE-xxxx.1.patch.txt

 

HiveでJSON形式のログを構造化する

fluentの勉強会に参加したら、まわりがモヒカンばっかりで消毒されそうになったタムタムです。

先日のMixi Engineers' Seminar #3でMixiがJSONデータをHiveで集計しているよーという発表をしていて、Fluentのtail + apacheもjsonとしてデータが飛んでいるとの事で、相性は悪くないよねーというわけでサンプルを作ってみました。(スライドを見ていた感じではこんな感じだったはず)

まずサンプルデータを用意します。
適当に100件ほど作りました。 (nameが名前でexam{1|2|3}は何かのテストの点数です)

{"name" : "Irma", "exam1": 58, "exam2": 73, "exam3":85}
{"name" : "Dorothy", "exam1": 50, "exam2": 39, "exam3":11}
{"name" : "Echo", "exam1": 95, "exam2": 47, "exam3":96}
{"name" : "Ina", "exam1": 50, "exam2": 68, "exam3":38}
{"name" : "Kay", "exam1": 15, "exam2": 11, "exam3":46}
....

まずはデータをそのまま1つの列に入れます。 今回はサンプルなのでPARTITIONは切っていません。

create table raw_data(line string);
load data local inpath '/mnt/iscsi/test1/exam_data1.txt' overwrite into table raw_data;

このJSONデータをテーブル化するクエリは以下のように書けます。

select
  get_json_object(raw_data.line, '$.name'),
  get_json_object(raw_data.line, '$.exam1'),
  get_json_object(raw_data.line, '$.exam2'),
  get_json_object(raw_data.line, '$.exam3')
from raw_data;

もしくは、json_tupleとlateral viewを使います。 (Hiveのマニュアルには、上記のようなクエリを書くなら下記のようなクエリを書きましょうねと書いてあります。)

SELECT t1.*
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as nm, exam1, exam2, exam3;

json_tupleはstring型として返ってくるので、これを整数型にします。SELECT句でCastしているだけです。

SELECT t1.name, cast(t1.exam1 as int), cast(t1.exam2 as int), cast(t1.exam3 as int)
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as name, exam1, exam2, exam3;

これをView化します。

DROP VIEW IF EXISTS exam_table;
CREATE VIEW IF NOT EXISTS exam_table (
  name,
  exam1,
  exam2,
  exam3
)
AS
SELECT t1.name, cast(t1.exam1 as int), cast(t1.exam2 as int), cast(t1.exam3 as int)
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as name, exam1, exam2, exam3;

簡単な集計をしてみます。

SELECT
  count(*),
  avg(exam1),
  avg(exam2),
  avg(exam3)
FROM
  exam_table;
100     50.72   52.71   50.43

集計できました。めでたし、めでたし。
ちなみにHive0.5までは(私は0.6から使い始めたのでわからんですが・・)Where句を書くとクエリがコンパイルできないらしくて、そのときは、「set hive.optimize.ppd=false;」と設定するみたいです。
https://issues.apache.org/jira/browse/HIVE-1056

ちなみにJSON用のSerdeもあります。
http://code.google.com/p/hive-json-serde/
ぶっちゃけ、こっち使った方がスマートに書けますw

というわけで、hiveはfluentとの相性も良さそうですね。
(ただし今回は統計情報を見ていないので、上記のサンプルが効率良いのかは調べていません(=ω=))

Solaris10でKyoto CabinetとKyoto Tycoonをビルドする。

Kyoto Tycoonが0.9.53でSolaris10でも動くようになったのでメモメモ。
0.9.52まではSolaris10ではsetsockoptのSO_RCVTIMEOとSO_SNDTIMEOが必ず失敗するのでSocketの接続が全てRejectされていました。0.9.53では設定は試みるけどエラーチェックをしなくなったので、Solaris10でも繋がるようになりました。

ビルドをするためにGCC4.4を入れます。GCC4.6じゃないの?と言われそうですが、依存ライブラリのmpc(gcc-4.5から必要)のビルドがgcc3.4.3で出来ないので1回GCC4.4系を入れます。gcc3.4.3でもconfig.hを書き換えればいけると思いますけど。
gmpとmpfrとmpcはgcc4-deps-libへ入れるという方向で行きたいと思います。

まず事前にgrepが/usr/xpg4/bin/grepを使うようにPATHを調整します。そうしないと、/usr/bin/grepを使ってしまいconfigure時にオプションを解釈できなくてチェックが正常に動きません。

gmp

 cd gmp-5.0.2
 ./configure --prefix=/usr/local/gcc4-deps-lib
 gmake > /dev/null
 gmake check
 gmake install

mpfr(documentに書いてあるとおり、パッチも当てます)

 cd mpfr-3.1.0
 wget http://www.mpfr.org/mpfr-3.1.0/allpatches
 /usr/bin/gpatch -N -Z -p1 < allpatches

 ./configure --prefix=/usr/local/gcc4-deps-lib --with-gmp=/usr/local/gcc4-deps-lib
 gmake > /dev/null
 gmake check
 gmake install

libgccのビルドにこけるので、以下を設定します。

 export LD_LIBRARY_PATH=/usr/local/gcc4-deps-lib/lib:$LD_LIBRARY_PATH

gcc4.4.6

 ./configure \
   --prefix=/usr/local/gcc446 \
   --with-ar=/usr/ccs/bin/ar \
   --with-as=/usr/ccs/bin/as \
   --with-ld=/usr/ccs/bin/ld \
   --enable-shared \
   --enable-languages=c,c++ \
   --with-gmp=/usr/local/gcc4-deps-lib \
   --with-mpfr=/usr/local/gcc4-deps-lib

 gmake -j 2 bootstrap
 gmake install

確認

 export LD_LIBRARY_PATH=/usr/local/gcc446/lib:$LD_LIBRARY_PATH
 /usr/local/gcc446/bin/gcc -v
Using built-in specs.
Target: i386-pc-solaris2.10
コンフィグオプション: ./configure --prefix=/usr/local/gcc446 --with-ar=/usr/ccs/bin/ar --with-as=/usr/ccs/bin/as --with-ld=/usr/ccs/bin/ld --enable-shared --enable-languages=c,c++ --with-gmp=/usr/local/gcc4-deps-lib --with-mpfr=/usr/local/gcc4-deps-lib
スレッドモデル: posix
gcc version 4.4.6 (GCC)

環境変数はこんな感じで設定しております。

LD_LIBRARY_PATH=/usr/local/gcc446/lib/amd64:/usr/local/gccc446/lib:/usr/local/gcc4-deps-lib/lib:/usr/local/lib:/usr/ccs/lib:/usr/sfw/lib:/opt/sfw/lib:/usr/lib:/lib:/etc/lib
PATH=/usr/local/gcc446/bin:/usr/local/bin:/usr/xpg4/bin:/usr/xpg6/bin:/usr/ccs/bin:/usr/sfw/bin:/opt/sfw/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

次にKCを入れます。

 ./configure CXX=/usr/local/gcc446/bin/gcc --prefix=/usr/local/kc-1.2.72
 gmake
 gmake install

次はKTを入れます。 とりあえずの注意点としては、-m64を付けないとconfigure時にlibkyotocabinetが認識できません。これは、libkyotocabinetが64ビットのSOであるのにconftest.cppを作ってリンクの確認をする時に-m64がつかなくて32ビットのELFに64ビットのSOをリンクしようとするからです。また、CXXに明示的にg++を指定しているのは、-m64つけた時にこれを指定しないとpthreadなどの他のライブラリを認識できなかったからです。

./configure \
  CXXFLAGS="-m64" \
  CXX="/usr/local/gcc446/bin/g++" \
  LDFLAGS="-R/usr/local/kc-1.2.72/lib -R/usr/local/kt-0.9.53/lib" \
  --prefix=/usr/local/kt-0.9.53 \
  --with-kc=/usr/local/kc-1.2.72
gmake
gmake install

確認をします。ktserverを立ち上げて以下のコマンドを打ちます。

bash-3.2# echo "hello" | /usr/local/kt-0.9.53/bin/ktutilmgr http -put http://localhost:1978/test1
bash-3.2# /usr/local/kt-0.9.53/bin/ktutilmgr http -get http://localhost:1978/test1
hello

動いた動いた。やったね。

 

JavaのsubListの罠

本当は食べ物の写真とか載せたいんですけどね。。
ついこの前やらかしちゃった事を備忘録としてメモメモ。

Javaでは基本的にSerializableを実装していないと(Externalizableでもいいけど)、Objectをシリアライズできません。例えばMemcachedにそのままObjectをまるっと保存する場合は、ObjectOutputStreamを介して保存する事が多いと思います。(MsgPackとかJSONICとかを使う方が良いかもしれませんが)。

で、この前やらかしてしまったのは、JavaのListインターフェースにあるsubListで返ってくるリストはSerializableインターフェースを実装していないListなので、キャッシュに保存できないでエラーが出まくりでした。と書くと語弊があるので正確に書くと、ArrayListとLinkedListはAbstractListを継承しており、subListの実装はAbstractListが持っています。AbstractList#subListの実装は、RandomAccessを実装しているクラスの場合はRandomAccessSubListを、実装していない場合はSubListのObjectを返します。RandomAccessSubListもSubListもSerializableは実装されていません。

public List<E> subList(int fromIndex, int toIndex) {
    return (this instanceof RandomAccess ?
            new RandomAccessSubList<E>(this, fromIndex, toIndex) :
            new SubList<E>(this, fromIndex, toIndex));
}

ちなみに、subListは要素をコピーして作っているわけでは無く、単純に元リストのラッパークラスのように振る舞っているだけなので、そこも注意なのです。subListで返ってきたリストに対して操作するとどうなるかのサンプルを書きました。

import java.util.ArrayList;
import java.util.List;

public class Test1 {
	public static void main(String[] args) throws Exception {

		// サンプルデータを作る
		ArrayList<Integer> srcList = new ArrayList<Integer>();
		for (int i = 0; i < 10; i++) {
			srcList.add(i);
		}
		System.out.println("src=" + srcList);
		// サブリストを作ってみる
		List subList = srcList.subList(5, 8);
		System.out.println("sub=" + subList);
		// サブリストを操作する
		subList.add(100);

		System.out.println("=====after=====");
		System.out.println("sub=" + subList);
		System.out.println("src=" + srcList);

		System.out.println(subList instanceof Serializable);
	}
}

実行結果

src=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub=[5, 6, 7]
=====after=====
sub=[5, 6, 7, 100]
src=[0, 1, 2, 3, 4, 5, 6, 7, 100, 8, 9]
false

 

Home > 日記 > 日記2012前期 Archive

Search
Feeds

Return to page top