RPCメソッド一覧から見るhiveserverとhiveserver2の違い (と、ついでにPresto)
手元でいいかげんhiveserver2に移行しようと思いまして、移行するためには shib をhiveserver2に対応させなきゃならん、ということになるわけです。
で、実装する前にどうせ調べるから、ということでhiveserverとhivesever2は何が違うのかをRPC APIメソッドの一覧から見てみようと思いました。カジュアル!
hiveserver
古きよきhiveserverはThriftで接続してAPIを叩きます。*.thrift ファイルがやたら多いのが気になりますが、えいやとコード生成してしまえば結構わかりやすいコードが(どの言語でも)生成されますね。
メソッドの一覧を出してみるとこんな感じ。
- execute(query)
- fetchOne()
- fetchN(numRows)
- fetchAll()
- getSchema()
- getThriftSchema()
- getClusterStatus()
- getQueryPlan()
- clean()
ひっじょーにわかりやすいですね。queryは文字列、numRowsは数値であることは言うまでもないでしょう。流れとしてはこんな感じ。
- 接続したらおもむろに execute(query) する
- クエリがどう実行されるか知りたかったら getQueryPlan() する
- 返ってくるデータの型を知るため getSchema() する
- 結果をフェッチする
- fetchOne() もしくは fetchN(rows) を繰り返す
- どうなったら終了なのか仕様が無い
- どうも 0 カラムの row をターミネータとしているらしい
- あるいは fetchAll() を実行する
- fetchOne() もしくは fetchN(rows) を繰り返す
- 最後に clean() する
つまり execute() が先頭にあり、他の全ての処理は execute() したクエリに対しての処理だというのが暗黙の話になっている。まあ、わかりやすいけどさあ、みたいな。
hiveserver2
新しいほうの hiveserver2 もThrift APIを叩く。が、見てみると fb303 等の依存が取り除かれて thrift 定義まわりはずいぶんすっきりした。
で、同じようにえいやとコード生成してみると、あれ、なんだこれ、というのが見えてくる。メインの TCLIService について生成されるコードを見てみても各メソッドの引数がよくわからん……と思ったら、必ず対応する Req を渡して Resp をもらう、というプロトコルになったようだ。 TCLIService_types を眺めると対応するメソッドに何を渡せばよいかがわかる。
一覧はこう。
- OpenSession
- TOpenSessionReq(username, password, configuration)
- TOpenSessionResp(status, serverProtocol, sessionHandle, configuration)
- CloseSession
- TCloseSessionReq(sessionHandle)
- GetInfo
- TGetInfoReq(sessionHandle, infoType)
- TGetInfoResp(status, infoValue)
- ExecuteStatement
- TExecuteStatementReq(sessionHandle, statement, confOverlay)
- TExecuteStatementResp(status, operationHandle)
- GetTypeInfo
- TGetTypeInfoReq(sessionHandle)
- TGetTypeInfoResp(status, operationHandle)
- GetCatalogs
- TGetCatalogsReq(sessionHandle)
- TGetCatalogsResp(status, operationHandle)
- GetSchemas
- TGetSchemasReq(sessionHandle, catalogName, schemaName)
- TGetSchemasResp(status, operationHandle)
- GetTables
- TGetTablesReq(sessionHandle, catalogName, schemaName, tableName, tableTypes)
- TGetTablesResp(status, operationHandle)
- GetTableTypes
- TGetTableTypesReq(sessionHandle)
- TGetTableTypesResp(status, operationHandle)
- GetColumns
- TGetColumnsReq(sessionHandle, catalogName, schemaName, tableName, columnName)
- TGetColumnsResp(status, operationHandle)
- GetFunctions
- TGetFunctionsReq(sessionHandle, catalogName, schemaName, functionName)
- TGetFunctionsResp(status, operationHandle)
- GetOperationStatus
- TGetOperationStatusReq(operationHandle)
- TGetOperationStatusResp(status, operationState)
- GetCancelOperation
- TCancelOperationReq(operationHandle)
- TCancelOperationResp(status)
- CloseOperation
- TCloseOperationReq(operationHandle)
- TCloseOperationResp(status)
- GetResultSetMetadata
- TGetResultSetMetadataReq(operationHandle)
- TGetResultSetMetadataResp(status, schema)
- FetchResults
- TFetchResultsReq(operationHandle, orientation = 0, maxRows)
- TFetchResultsResp(status, hasMoreRows, results)
- オマケ: SessionHandle と OperationHandle
- TSessionHandle(sessionId)
- TOperationHandle(operationId, operationType, hasResultSet, modifiedRowCount)
ずいぶんメソッドが増えている、が、しかしよく整理されていてわかりやすい。いちばんの特徴は sessionHandle やら operationHandle やらの単語があちこちに出てくるようになったこと。これで複数セッションの管理をするということだろう。わかりやすい。
GetTables などのメソッドも増えた。これまでは "show tables" などを execute して結果を受け取るということをやっていたが、それがAPI一発で取得できるようになったみたい。わーいべんりー。
そして status というものがあちこちにあるので、つまり ExecuteStatement したらあとは適当に status を見て、終了したことを確認したら FetchResults するということだろう。これまでは闇雲に fetch して結果が返ってくるのをひたすら待つという厳しい戦いだったからなあ。FetchResults も結果(TFetchResultsResp)に hasMoreRows なんて項目が増えてちょーべんり。これで変な判定を入れて結果をフェッチし終えたかどうかとか考えなくて済む。
全体的によいAPIになっていると言えるでしょう。セッションまわりのシーケンスだけ確認すればすぐ実装できそう。
Presto
「shibでPrestoにクエリできたら便利じゃね?」という天使の囁きが脳内に起こったので、ついでだからさくっと調べた。
Presto は基本的に HTTP JSON API で各種の処理を実行する。それについてのドキュメントはざっと眺めたところ存在しなかったようなので、とりあえずソースコードを直接見た。PrestoのJavaのソースコードはわかりやすいなあ。
該当のハンドラはこのあたり: presto-server/src/main/java/com/facebook/presto/server/*.java
- ExecuteResource.java:@Path("/v1/execute")
- @POST (query, user, catalog, schema)
- NodeResource.java:@Path("/v1/node") :
- @GET
- @GET "failed"
- PeriodicImportJobResource.java:@Path("/v1/import/jobs")
- @GET
- @POST (job, uriInfo)
- @DELETE "{jobid}"
- @GET "{jobid}"
- QueryResource.java:@Path("/v1/query") :
- @GET
- @POST (query, user, source, catalog, schema, userAgent, resourceContext, uriInfo)
- @GET "${queryid}"
- @DELETE "{queryid}"
- @DELETE "stage/{stageid}"
- ShardResource.java:@Path("/v1/shard") :
- @DELETE "{shardUuid}"
- @GET "{shardUuid}"
- StageResource.java:@Path("/v1/stage") :
- @DELETE "{stageid}"
- StatementResource.java:@Path("/v1/statement") :
- @POST (statement, user, source, catalog, schema, userAgent, resourceContext, uriInfo)
- @GET "{queryid}/{token}" (queryId, token, maxWait, uriInfo)
- @DELETE "{queryid}/{token}"
- TaskResource.java:@Path("/v1/task") :
- @GET
- @POST "{taskid}"
- @GET "{taskid}"
- @DELETE "{taskid}"
- @GET "{taskid}/results/{outputid}/{token}"
- @DELETE "{taskid}/results/{outputid}"
わりと少ない。でもまあ、わかりやすいですね。というかとりあえずえいやと execute 叩いてしまってから、返ってくる情報を見てその次のAPIを叩くだけでよさそう。たぶん queryid が返ってくるのかな。リスト取得やら停止やらもどれを叩けばいいかわかりやすい。
ま、変なことしなければすぐ使えそうだ。curlで試してみればいいかな。
まとめ
HTTP APIはすばらしい! (あれー?)
何かRPC APIを叩きたいんだけどドキュメントがない!*1というとき、諦めるんじゃなくてコードをざっと眺めてみるとだいたいわかるし、おススメですよ、という話でした。カジュアル!!!! いやこれはちがった。
このエントリは Hadoop Advent Calendar 2013 に参加しています。
http://qiita.com/advent-calendar/2013/hadoop
*1:hiveserver系もRPCまわりのドキュメントは無いに等しい、と思う