yanagishima v8をリリースしました。

https://github.com/wyukawa/yanagishima

yanagishima v7をリリースしました。 - wyukawa’s blogから1週間ぐらいしか経ってませんが、v8をリリースしました。

今回の目玉はクエリ履歴とブックマークをサーバーサイドで保存できるようにしたことです。
今まではlocal storageを使っていたんですが、ブラウザが変わると引き継がれないのは不便だということで対応しました。
ただyanagishima側ではユーザ管理機能は持っていないため認証用のreverse proxyサーバーが別途必要です。
そのためdefaultでは変わらずlocal storageを使います。

それ以外にはパーティション表示を階層的にしたり、jsonデータをpretty printできるようにしたりといった機能追加があります。
設定ファイルもデフォルト値を設定できるものは設定してユーザがあまり設定ファイルをいじる必要がないようにしました。

データ民主化の負の側面

データの活用が当然のことのようになってエンジニア以外でもSQL書いてデータ抽出するのが一般的になってきました。さらにデータサイエンティストの登場により高度な分析もされるようになってきて、顕在化してきたのがHadoopクラスタの無法地帯化とエンジニアの疲弊なんじゃないかと最近思っております。なおHadoopに限らずElasticsearchでも言えたりします。

これって要はユーザと管理者のバランスの問題で、Hadoopエンジニアを採用するのが難しいというのが背景にあります。

SQL書ける人はそれなりにいるけど、インフラ側の人材不足ですね。この状態でデータの民主化が進むとどうなるかというと、

クエリの数が増える -> なかにも重いクエリも結構ある -> 管理者がそれをチェックするのに疲れて放置するようになる -> クラスタの負荷が増えて障害も出るようになる -> クエリ実行にも時間かかるようになるため、まとめてクエリを投入する人がでてくる -> クエリの数が増える -> ...

というような流れになっていきます。ユーザも多くなっている状況なのでクラスタのアップグレードのようなメンテナンス作業も難しいでしょう。そうして管理者が去り古いインフラを使い続けるユーザだけが残りましたとさ、、、という暗い未来が想像できてしまいます。

まあそうなったらもう外注なり外部のクラウドサービス使った方が良いでしょう。

データの民主化が成立するのって、ユースケースとして重いクエリを投げる必要がないというのと、クエリ投げる人が少数でモラルがある、例えばクエリを投げっぱなしで帰らないとか、自分が投げたクエリをチェックしていて時間かかってそうなら一旦killしてクエリに問題ないか見直すとか、そういう前提が必要なのかなと思っています。

セキュリティ周りとかその辺の基盤がちゃんと整う前になし崩し的にデータの民主化が進むと、クラスタの荒廃化は進みがちです。まあそれでもちゃんとデータサイエンティストの成果が出てそれがエンジニアの給料に反映されるなら問題無いという説もあります。

ただともかくデータにアクセスさせろという人が増えてくると、RDBSからHiveにデータ持ってくる仕事ばかりが増えてきて、そんな仕事モチベーション湧かないよねという。。。なのでエンジニアがツール用意してそれを使って好きにやってもらうというのが現実的なのかもしれませんが、Spark使いたいとかR使いたいとかいろいろ出てくるとそれ全部対応するのは難しいでしょう。

エンジニアを疲弊させるもうひとつの例が、データサイエンティストが書いたクエリ、コードはそのままじゃプロダクションに入れられないから、エンジニアが引き取ってってやつ。や、長い複雑なクエリ渡された人の気持ちを考えると。。。原則としてシンプルなコードじゃない限り人のコードを読むのは気が進まないものです。

このようにエンジニアを疲弊させるものが増えているので、どうしたもんかなと考えています。そういう背景があるので、データくれっていう人に対して感じ悪く塩対応するエンジニアとか、糞クエリを容赦なくkillするhive/presto警察とか、クエリ実行中だろうがメンテナンスして強引にアップグレードするとか、そういった強引さが時として必要なのではないかと思ったりしております。まあデータサイエンティストの人も気を使ってるとは思いますが。

GoogleとかFacebookとかだと充実したインフラがありそうだからそういう問題はないのかもね。

yanagishima v7をリリースしました。

yanagishimaは割とカジュアルにメジャーバージョンを上げていて、作っている方も正直ちゃんとchange logを管理してないのはよくないのですが、最近7.0をリリースしました。

3.0のリリースブログがyanagishima v3をリリースしました。 - wyukawa’s blogなんで4,5,6はどうしたんだというツッコミは置いときます。

7.0はhive対応がメインです。3.0からの変更という意味ではいっぱいあるんだけど忘れた。。。デスクトップ通知、テーブル補完、テーブルバリデーション、ブックマークにタイトル追加、クエリをfluentd経由でロギング、見せたくないスキーマを非表示、、、あたりかな。

うちの環境だとhiveでバッチを書いてアドホッククエリはprestoというすみわけで、prestoでデータチェックできればほとんどのケースで困らないんですが、hiveを使いたいケースもたまにあります。

例えばバッチでhive viewを使っているケースです。prestoからhive view見れないですからね。
まあそれ以外にもたまにhiveを使いたいケースがあるだろうということと、僕がStrata Data Conference in Singapore 2017で喋る予定なのでそこでyanagishimaがpresto, hive両方対応している方が多少なりともインパクトあるだろうということで実装しました。最近流行りのカンファレンス駆動に近いものがあります。ただyanagishimaが日本人以外には発音しにくいということに最近気づきました。。。まあ長い名前というのもありますが、ただ長いおかげでSEOに強いという側面もあったりします。

閑話休題

yanagishimaがhive対応するにあたってどうやったかというと普通にjdbc使いました。

prestoではJDBC使ってないです。その理由は、yanagishimaでなぜJDBCを使ってないのか - wyukawa’s blog

hiveはprestoと違ってjobをsubmitしたあとに戻り値でapplication idを取れないので、そこは工夫が必要なところなのですが、そこは先人の知恵を借りてGitHub - tagomoris/shib: WebUI for query engines: Hive and Prestoの方式を採用しています。

どうやっているかといういとyanagishima側で独自のid(例:20170809_164758_ac5624e46a802ea3acdcff3fdfa100d1)を生成してそれをset mapreduce.job.name=...を使ってjob名にセットします。例えば、yanagishima-hive-20170809_164758_ac5624e46a802ea3acdcff3fdfa100d1

Yarnのジョブ一覧はresource manager APIの/ws/v1/cluster/appsをたたけば取れるのであとはこの一覧からjob名をgrepしてapplication idを取得するという流れです。application idさえ取れればkillできます。

https://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/ResourceManagerRest.html#Cluster_Applications_API

注釈:startedTimeBeginをつけないと最新のjobを取ってきてくれない環境もありました。

こういう流れなのでset mapreduce.job.name=...が使える環境、つまりHive on MRでかつセキュリティ的な制約が無い環境じゃないと完全には動かないと思います。
まあHive on Tezでも動きそうな気がするけど、少なくとも自分がクエリ投げた画面でkillはできない。一覧から探してkillはできると思うけど。

またhadoopのresource managerのapplication idごとの画面にはX-Frame-Options SAMEORIGINが付いてるのでiframeで表示できないためprestoと違って新規ウィンドウで表示するようにしています。

hive対応したこともあり設定できる項目が増えて、設定ファイルの書き方が難しくなっている状況です。

7.0を出した後も細かい改善は随時入れています。社内でアンケート取ってフィードバックもらって、すぐ対応できそうなものからやっているという感じです。その辺が一通り終わったら8.0出すと思います。

本当はドキュメントやデモサイトも整備したほうがいいんですが、そこまで手が回ってない状況です。

オホーツク網走マラソン走ってきた

記録はネットタイムが4時間38分28秒で去年、オホーツク網走マラソン走ってきた - wyukawa’s blog、よりはマシだけどワースト2でした。

まあ練習不足だししゃーない。これが実に20回目のフルマラソンでした。これだけ走っているとだいたいタイムも予想できて、今回も想定内でした。

天気もよく、去年ほど暑くはなく、走るには良いコンディションでした。

今年も金さんとワイナイナさんがゲストランナー。ワイナイナさんは行きの飛行機が一緒だった。

ワイナイナさんは最後尾からスタートしてどんどん追い抜いて行くんだけど、僕は6km付近の上りであっさりつかまる。やはり早い。。。

マイペースで走りつつ、エイドの給食もとって楽しめました。最後のエイドでりんごとぶどうが出たんだけど、これは去年はなかったような。

そして最後はひまわり畑の中をゴール

ゴール後はビールのみつつうどんとウィンナーを食いました。屋台がいろいろでてるんだけど、ランナーが食う前にランナー以外の人が食って売り切れになっているものが多かったw

ベルギー、フランス、イギリス旅行

夏休みを2週間とってベルギー、フランス、イギリスに旅行してきました。

まずは成田からブリュッセルへ。ホテルはブリュッセル中央駅近くにとりました。

到着した当日8/18(金)にchez leonでムール貝を食う。

8/19(土)はグランプラス、小便小僧を見学

その後ブリュージュへ移動して運河クルーズ

その後アントワープに移動

ブリュッセルに戻ってきて晩御飯食べるついでにブリュッセル風ワッフルを食う

8/20(日)はタリスにのってアムステルダムへ移動してアヤックスvsフローニンゲンを観戦
堂安はベンチ入りするも出番なし。フンテラールアヤックスに来てたんだ。
この試合目立っていたのはアヤックスの背番号10のレフティー。
今調べたらハキム・ツィエクらしい。

ブリュッセルの戻ってビアバーで食事

8/21(月)リエージュに移動してモンタニュ・ビューランを上る。


8/22(火)タリスにのってパリへ

タリスで出た軽食

ホテルについて、エッフェル塔まで歩く

その後セーヌ川クルーズ

8/23(水)モンサンミッシェル
TGVでレンヌまで行ってその後バスです。

オムレツ

8/24(木)はストラスブールに行って、ここでも運河クルーズ

8/25(金)は凱旋門から出てるご飯付き観光バスに乗る。

夜はPSGの試合

8/26(土)はユーロスターに乗ってイギリスへ
車内で出てきた朝食

ユナイテッドvsレスターを観戦

8/27(日)はリバプールに移動してリバプールvsアーセナルを観戦

8/28(月)は湖水地方に移動

8/29(火)ボウネスから船でウオーターヘッドに移動後、バスを乗り継いでケズイックへ
ここでも船に乗ってダーウェント湖を観光
その後キャッスルリング・ストーンサークル

8/30(水)はフェリー経由でヒル・トップへ
ヒル・トップからの眺め

8/31(木)湖水地方からロンドンへ、ロンドンからブリュッセルへ、ブリュッセルから成田へ、という経路で帰国

fluentdでUndefinedConversionErrorに遭遇した

エラーログはこんな感じ。

  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-mixin-plaintextformatter-0.2.6/lib/fluent/mixin/plaintextformatter.rb:85:in `encode'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-mixin-plaintextformatter-0.2.6/lib/fluent/mixin/plaintextformatter.rb:85:in `to_json'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-mixin-plaintextformatter-0.2.6/lib/fluent/mixin/plaintextformatter.rb:85:in `stringify_record'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-mixin-plaintextformatter-0.2.6/lib/fluent/mixin/plaintextformatter.rb:115:in `format'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:590:in `block in emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/event.rb:149:in `feed_each'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/event.rb:149:in `each'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:574:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-plugin-forest-0.3.3/lib/fluent/plugin/out_forest.rb:175:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/event_router.rb:90:in `emit_stream'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/out_relabel.rb:24:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:42:in `next'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/out_relabel.rb:25:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:42:in `next'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/out_relabel.rb:25:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:42:in `next'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluent-plugin-flowcounter-0.4.3/lib/fluent/plugin/out_flowcounter.rb:202:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/output.rb:42:in `next'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/out_copy.rb:78:in `emit'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/event_router.rb:90:in `emit_stream'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:178:in `on_message'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:338:in `call'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:338:in `block in on_read_msgpack'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:337:in `feed_each'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:337:in `on_read_msgpack'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/cool.io-1.5.0/lib/cool.io/io.rb:123:in `on_readable'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/cool.io-1.5.0/lib/cool.io/io.rb:186:in `on_readable'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/cool.io-1.5.0/lib/cool.io/loop.rb:88:in `run_once'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/cool.io-1.5.0/lib/cool.io/loop.rb:88:in `run'
  2017-08-07 12:25:55 +0900 [warn]: /path/to/ruby-2.2/lib/ruby/gems/2.2.0/gems/fluentd-0.12.39/lib/fluent/plugin/in_forward.rb:120:in `run'
  2017-08-07 12:25:55 +0900 [warn]: emit transaction failed: error_class=Encoding::UndefinedConversionError error="\"\\xE3\" from ASCII-8BIT to UTF-8" tag="..."

fluentdやプラグインのバージョンを上げる作業をしていて遭遇しました。

いろいろ試行錯誤して結局msgpackのバージョンを1.1.0から0.5.12にしたら回避できた。

fluend 0.12.32からmsgpackのバージョンが上がったのが原因ぽい。
https://github.com/fluent/fluentd/commit/e50a98ce518cadb3a37f76cde67ba8c6e076f638

これはmsgpack 0.6.0でバイナリを扱うようになり、今まではASCII-8BIT→msgpackのシリアライズ→デシリアライズUTF-8→to_jsonでエラーにならなかったのが、ASCII-8BITをto_jsonでエラーということっぽい。
https://github.com/msgpack/msgpack-ruby/issues/44

よく見ると本家のFAQにあったけど、要は上流のデータの文字コード指定が間違ってるという話
https://docs.fluentd.org/v0.12/articles/faq#i-got-enconding-error-inside-plugin-how-to-fix-it

確かにin_tailでマルチバイト文字を見てるところあるな。。。

ただfluentdの送信側をこちらでコントロールできない場合があるので、どうしたものか。。。


2017/08/09追記
in_tailでマルチバイト文字見てると思ったらurl encodeされてたので大丈夫そう。
そもそもfluent-plugin-flowcounterからfluent-mixin-plaintextformatterがなぜ呼び出されてるのかわからん。
fluent-plugin-forestからfluent-plugin-webhdfsを使ってはいるけど関係してないと思うんだけどなあ。

openrestyでmysqlをいじる

https://github.com/wyukawa/yanagishima
は認証、認可機能はないんですが、まあ実際問題必要なのでそこは別の仕組みでやってたりします。

認証は社内のシステムにのっかっていて、認可はnginxでリバースプロキシしてそこでluaでやってました。

nginx + lua環境って実は作るのが面倒で、どのぐらい面倒かというと下記のような感じです。
Cent OS(6.4)にNginx + lua-nginx-moduleをインストール - ゆく河の流れは絶えずして...

面倒なんだけど、上記ブログの通りに環境作って運用してました。


ただluaでif文羅列して権限管理するのも辛くなってきたので、そこは一部DB管理にして管理画面も作った方がよかろうということでやりました。

今までの環境だとDBさわれないんで、この機会に全部入りのopenrestyを試そう、そうしようということで試しました。やっと本題に入りました。


openrestyに入っているcomponentは下記の通り
https://openresty.org/en/components.html

yumでインストールできればよかったんですけど、別途モジュールをバインドする必要がありソースビルドしました。

標準だとhttp_stub_status_moduleが入ってないのでそれも追加して下記のようにconfigureしてmakeすればOK

./configure --add-module=... --with-http_stub_status_module

ただそのままだと下記のようにエラーになります。

$ /usr/local/openresty/bin/openresty -h
/usr/local/openresty/bin/openresty: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory

なので、

$ export LD_LIBRARY_PATH=/usr/local/openresty/luajit/lib:$LD_LIBRARY_PATH

すると

$ /usr/local/openresty/bin/openresty -h
nginx version: openresty/1.11.2.4
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
  -?,-h         : this help
  -v            : show version and exit
  -V            : show version and configure options then exit
  -t            : test configuration and exit
  -T            : test configuration, dump it and exit
  -q            : suppress non-error messages during configuration testing
  -s signal     : send signal to a master process: stop, quit, reopen, reload
  -p prefix     : set prefix path (default: /usr/local/openresty/nginx/)
  -c filename   : set configuration file (default: conf/nginx.conf)
  -g directives : set global directives out of configuration file

nginx 1.12系かと思ったら1.11系なのか。

まあ下記によれば1.11をforkしたのが1.12なので機能的には遜色なさそう。
NGINX 1.12 and 1.13 Released


起動スクリプト
https://gist.github.com/vdel26/8805927
をコピペして上記のLD_LIBRARY_PATH設定を追加しました。


lua+mysqlのコードは nginx実践入門 (WEB+DB PRESS plus) を参考にして下記のようなイメージです。
DBのところをIPアドレスで書いているのはホスト名だと「failed to connect: no resolver defined to resolve」のようにエラーになったためです。

local mysql = require "resty.mysql"
local cjson = require "cjson"

local db, err = mysql:new()
if not db then
  ngx.log(ngx.ERR, "failed to instantiate mysql: ", err)
  return
end

db:set_timeout(1000)

local ok, err, errno, sqlstate = db:connect{
  host = "192.168.0.1",
  port = 3306,
  database = "...",
  user = "...",
  password = "..."
}

if not ok then
  ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errno, " ", sqlstate)
  return
end

local sql = "SELECT ..."
res, err, errno, sqlstate = db:query(sql)
if not res then
  ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".")
  return
end

ngx.say(cjson.encode(res))

local max_idle_timeout = 10000
local pool_size = 50
local ok, err = db:set_keepalive(max_idle_timeout, pool_size)
if not ok then
  ngx.log(ngx.ERR, "failed to set keepalive", err)
  return
end

そんな感じです。

nginx 1.12を網羅したnginx実践入門第二版が欲しいなってちょっと思いました。