act-act about projects rss

Connecting to Hive from Erlang using Thrift part2

Failed to connect Hive Server

前回「thrift_client_util:new/4」で失敗していた件について調べてみたところ、thrift_erlangというモジュールがdepsフォルダ下に残っていた為に発生していました。

当初、thriftのライブラリモジュールとして、thrift_erlthrift_erlangのどちらを使用するべきか調べていたのですが、その時きちんとクリーンアップしなかった為に残ってしまっていたようです。

最終的にthrift_erlを使うことにしたので、thrift_erlangを取り除いて再度試したところ、以下のエラーが発生していました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ rebar eunit -v suites=thrift_hive_tests skip_deps=true tests=fetchOne_test_
WARN:  Ignoring sub_dirs for /home/masayuki/work/erlang/thrift-hive/deps/thrift_erl
==> rel (eunit)
==> thrift-hive (eunit)
Compiled src/thrift_hive.erl
NOTICE: Using experimental option 'tests'
    Running test function(s):
      thrift_hive_tests:fetchOne_test_/0
======================== EUnit ========================
thrift_hive_tests:6: fetchOne_test_...*failed*
in function thrift_hive:function_info/2
  called as function_info(execute,params_type)
in call from thrift_client:send_function_call/3 (src/thrift_client.erl, line 70)
in call from thrift_client:call/3 (src/thrift_client.erl, line 40)
in call from thrift_hive:fetch_one/0 (src/thrift_hive.erl, line 10)
in call from thrift_hive_tests:'-fetchOne_test_/0-fun-0-'/1 (test/thrift_hive_tests.erl, line 8)
**error:undef


=======================================================
  Failed: 1.  Skipped: 0.  Passed: 0.
ERROR: One or more eunit tests failed.
ERROR: eunit failed while processing /home/masayuki/work/erlang/thrift-hive: rebar_abort

コネクションを接続した後、HQLを発行するところでエラーとなっているようです。これはどうしても理由が分からず、thrift_erlのコードを読んでいったところ、以下のコードに答えがありました。

deps/thrift_erl/src/thrift_client.erl

1
2
3
4
5
6
7
8
9
10
%%--------------------------------------------------------------------                            
%%% Internal functions                                                                            
%%--------------------------------------------------------------------                            
-spec send_function_call(#tclient{}, atom(), list()) -> {#tclient{}, ok | {error, any()}}.
send_function_call(Client = #tclient{protocol = Proto0,
                                     service  = Service,
                                     seqid    = SeqId},
                   Function,
                   Args) ->
    Params = Service:function_info(Function, params_type),

一番下の行の「Service」は「thrift_client_util:new/4」で第3引数に渡しているモジュール名です。

1
thrift_client_util:new("127.0.0.1", 10000, thrift_hive, []).

この第3引数に指定したモジュール名を使って.thriftのインターフェースの情報を取得するようになっていました。この値はてっきり呼び出し側のモジュール名を指定するものと思い”thrift_hive”を指定していましたが、thriftコマンドで生成されたコードのモジュール名”thriftHive_thrift”を指定するのが正しいことが分かりました。この部分を修正したところ、このエラーは出なくなりました。

Read Timed out when executing query

先に進めると、今度はHQLを発行した後に制御が戻ってこなくなりました。指定しているHQLはhiveコマンドで実行すると結果が返ってくるので、何故Hive Severで応答が止まってしまうのか調べてみたところ、hive.logに以下のようなエラーが出ていました。

hive.log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2013-06-08 08:53:45,863 ERROR server.TThreadPoolServer (TThreadPoolServer.java:run(259)) - Error occurred during processing of message.
java.lang.RuntimeException: javax.jdo.JDOFatalDataStoreException: Cannot get a connection, pool error Could not create a validated object, cause: A read-only user or a user in a read-only database is not permitted to disable read-only mode on a connection.
NestedThrowables:
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Could not create a validated object, cause: A read-only user or a user in a read-only database is not permitted to disable read-only mode on a connection.
        at org.apache.hadoop.hive.service.HiveServer$ThriftHiveProcessorFactory.getProcessor(HiveServer.java:512)
        at org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:246)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
        at java.lang.Thread.run(Thread.java:662)
Caused by: javax.jdo.JDOFatalDataStoreException: Cannot get a connection, pool error Could not create a validated object, cause: A read-only user or a user in a read-only database is not permitted to disable read-only mode on a connection.
NestedThrowables:
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Could not create a validated object, cause: A read-only user or a user in a read-only database is not permitted to disable read-only mode on a connection.
        at org.datanucleus.jdo.NucleusJDOHelper.getJDOExceptionForNucleusException(NucleusJDOHelper.java:298)
        at org.datanucleus.jdo.JDOPersistenceManagerFactory.freezeConfiguration(JDOPersistenceManagerFactory.java:601)

エラーメッセージを指定してググってみたところ、HiveのMetadata storeに使用しているderbyのデータファイルに対して権限が足りてない、ということだったので、オーナーを変更しました。

1
chown -R hive:hive /var/lib/hive/metastore

これでようやくHiveの結果を取得することができました。

Getting Results

HQLの実行結果はバイナリなので、binary_to_listを使って文字列に変換して扱うようにしています。

thrift_hive.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-module(thrift_hive).

-include("hive_service_types.hrl").
-include("thriftHive_thrift.hrl").

-export([fetch_one/1, fetch_all/1]).

fetch_one(Hql) ->
    {ok, C0} = get_connection(),
    {C1, {ok, _}} = thrift_client:call(C0, execute, [Hql]),
    {_, {ok, R2}} = thrift_client:call(C1, fetchOne, []),
    Ret = binary_to_list(R2),
    {ok, Ret}.

fetch_all(Hql) ->
    {ok, C0} = get_connection(),
    {C1, {ok, _}} = thrift_client:call(C0, execute, [Hql]),
    {_, {ok, R2}} = thrift_client:call(C1, fetchAll, []),
    Ret = lists:map(fun(N) -> binary_to_list(N) end, R2),
    {ok, Ret}.

get_connection() ->
    thrift_client_util:new("127.0.0.1", 10000, thriftHive_thrift, []).

実行を確認する為のテストコードは以下となります。

thrift_hive_tests.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-module(thrift_hive_tests).
-include_lib("eunit/include/eunit.hrl").

fetchOne_test_() ->
    {timeout, 1200,
    ?_assertEqual(
        {ok, "a\t 1"},
        begin thrift_hive:fetch_one("select * from test") end
    )}.

fetchAll_test_() ->
    {timeout, 1200,
    fun() ->
        {ok, Lst} = thrift_hive:fetch_all("select * from test"),
        ?assertEqual(["a\t 1", "b\t 2", "c\t 3", "d\t 1", "e\t 2", "f\t 3", "a\t 4", "b\t 5"], Lst)
    end}.

Conclusion

thrift_erlを使い、Erlangのコードからthrift経由でHiveに接続し、HQLの結果を取得することができました。まだエラーが出た際の扱いとか色々ありますが、そのあたりは追々見ていきます。

今回初めてErlangを使ってみましたが、初心者から見てちょっと難しく感じられるのは、エラー時の出力内容から原因を特定するところ。もう少し色々と出力されると助かるのですが、それは仕方ないのかな。デバッグマクロをうまく使えばもっと早く原因を見つけられたかもしれません。

また、何か関数を呼び出した際、その戻り値の型が分からなくて困ることがよくあります。呼び出す関数のコードがあれば追えますが、無い場合はどうするんだろう。関数を定義している1行上に「-spec」を用いてインターフェースを記述するのが一般的なようですが、

deps/thrift_erl/src/thrift_client.erl

1
2
-spec receive_function_result(#tclient{}, atom()) -> {#tclient{}, {ok, any()} | {error, any()}}.
receive_function_result(Client = #tclient{service = Service}, Function) ->

このようにany()を使われてしまうと動かしてみないことには分からないような。

しかし、少しErlangのコードが読めるようになってきたので、暫くはErlangで遊びながらこのような疑問点への解を見つけていこうと思います。