日記2012前期 Archive
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の綺麗なオフィスに遊びに行ってきました
- 2012年4月 7日 19:28
- 日記2012前期
書こう書こうと思って今更ながら記事を書きます。
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)
退職しました
- 2012年4月 3日 10:50
- 日記2012前期
このたび、株式会社スクウェア・エニックスを退職しました。
正確には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を押します。(下の絵を参照)
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をビルドする。
- 2012年2月 1日 01:00
- 日記2012前期
- Tags: Kyoto Cabinet Kyoto Tycoon Solaris10
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