Home > Archives > 2012年2月 Archive

2012年2月 Archive

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

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

 

Home > Archives > 2012年2月 Archive

Search
Feeds

Return to page top