プランナーよりのログ解析基盤のその後

以前2種類のログ解析基盤 - wyukawa’s blogで書いたログ解析基盤のうち2つ目のプランナーよりのシステムが現在どうなっているかを書いてみたいと思います。


ちなみに1つ目のエンジニアよりのシステムの方も更新はあって、Fluentd+Norikra+Elasticsearch+Kibanaによるリアルタイムモニタリングを始めたり、メルカリでのNorikraの活用、 Mackerelを添えてを真似て、Norikraにクエリを登録したらGrowthForecastに自動でグラフが出来るようにしたり、Norikraでアプリログを集計してリアルタイムエラー通知 # Norikra meetupと少し似ている、Norikraにクエリを登録してログに特定のキーワードがあったらHipChatに通知するようにしたり、といったことをしています。


2つ目のプランナーよりのシステムの全体像はこんな感じです。

今日は上記に書かれているうちのyanagishima, Netezza, MySQL, Prestoについて書いてみたいと思います。
それ以外だと最近HDP2.3も出てアップグレードはどうしようかなと思ってます。
あと最近話題のre:dashはちょっと試したんですが、使ってないです。うちの環境だとレポーティングツールがあるので。
re:dashはスケジュールされたクエリ、キャッシュ、ダッシュボードがいいらしいんですが、うちの環境で適用させるポイントがあるのか分かりません。


yanagishima

shibはあんまり使わなくなりyanagishimaを使うようになりました。
https://github.com/wyukawa/yanagishima

なおshibは1つ目のシステムでばりばりつかってます。

yanagishimaはSingle Page Applicationで、フロントエンドはJavaScriptでバックエンドはJavaで実装されています。
サーバーサイドはPresto coordinatorへのgatewayとなっておりPrestoへクエリした結果をJSONで返してJavaScriptがそれを受けて画面を作ってます。
僕のUI力はゴミなので一部表示が変なところがありますが、まあ使えています。

yanagishimaはMySQL Workbenchライクに操作できて、どのデータがどのカラムなのかわかりやすいし、TSVダウンロードも出来るようになったので、便利になったと思います。
あとクエリのフォーマットも出来るようにしたのですが、これが別のところで役立ちました。後述します。


Netezza

InfiniDBが不安定なこともありNetezzaを導入して使い始めています。まだそんなに使い込んでいないですが、すでに問題が出ていて不安があります。。。

使っているのはN3001-001という比較的最近出た一番安価なモデルで物理的な意味でのFPGAはありません。ソフトウェア的にシミュレートしたFPGAというのはあるので論理的な意味でのFPGAはあります。

性能はInfiniDBと同じぐらいです。たぶん。

問題が出ているのはPerformance PortalというNetezzaの運用管理用のWebアプリケーションです。これはNzAdminというWindowsで動くソフトウェアの後継で、クエリの実行プランや履歴や負荷を見る事ができます。

で、クエリの検索をおこなうとロードアベレージが50を超えて下がらない状況になり他のクエリ検索ができなくなることがあります。再現条件はわかりませんが、頻度は高い気がします。
topで見てみるとqemu-kvmが異様にCPUを使っているという状況でした。nzsessionでUserがPORTALのセッションをkillしたらロードアベレージが下がりました。

他にもqemu-kvmがswapを使い果たしたりしました。

一応サポートに問い合わせているんですが、ログをくれとかどういう手順で操作したのかを聞かれるばかりでなんの進展も無いというのが現状です。向こうも今回のような自分達のナレッジに無い事はベンダーに転送しているんですが、ちょっと解決しそうもないですね。。。
たぶんN3001-001はまだあんまり市場に出てなくて枯れてないんだと思うんですよね。あとサポートのWebサイトがいけてないんですよね。何がいけてないって、問い合わせのやりとりをWebから見る事ができなくて結局メールをあさるしかないという。。。


MySQL

MySQLは役割が変わって、以前のようにHiveで集計した結果を入れるものではなくなりました。Hiveで集計したものはHiveに入れてPresto経由で見るようになりました。
じゃあなんでMySQLが残っているかというとCognosから参照しているマスターデータ的なものがあることと、別Webアプリのストレージとして使っているからです。
PrestoからMySQLとHiveをjoinすることも想定しています。


Presto

Prestoは複数のデータソースを見ることが出来るというメリットがあるのですが、一方でMySQLPostgreSQLのコネクタのようにJDBCベースのものだとLIMITが効かないという問題があります。マスターデータのようにデータ量が多くない場合は問題にならないのですが、データ量が多い場合はPrestoのworkerがOutOfMemoryになって落ちます。PrestoのMySQLコネクタを使ってInfiniDBにアクセスしたときにその問題に遭遇しました。Hiveだとそういう問題はおきません。

Prestoは新しいバージョンが出たら割とすぐに追随しています。ただバグを踏む事はあって最近だと0.108で一部クエリが返ってこなくなる問題に遭遇して0.107にロールバックしたことがありました。この問題はRevert "Finish inner join if build is empty" by cberner · Pull Request #3212 · prestodb/presto · GitHubにあるように一部コミットをrevertして解決しています。この問題のようにスタックトレースも無いタイプだと解決が難しいと思うのですが、解決にいたってなによりです。

最近url encodeされたデータを扱う必要が出てきたのですがPrestoにはurl encode/decodeの手段が無いのでPull Request出しました。Add url_encode/url_decode function by wyukawa · Pull Request #3235 · prestodb/presto · GitHub
丁寧にレビューにしてもらってますが、最近進展が無いですね。。。
現状はHiveのreflect関数使ってurl decodeしてますが、PrestoにあればわざわざETL書く必要無くPresto Viewでなんとか出来るのに。。。

うちのチームではPresto Viewを多用していて200個以上あります。で、実際に作られているviewとGitHubで管理しているDDLに差異がありそうだということでこれをチェックするツールを書きました。

Presto Viewは内部的にはクエリをフォーマットしてbase64エンコードしてhiveのメタストアに突っ込んでいます。
base64エンコードした結果はhiveのdesc formattedで見れます。

下記を比較して差異があったらアラート通知するようにしました。

  • desc formattedしてbase64デコードしたもの
  • GitHubにあるDDLをyanagishimaになげてフォーマットされたクエリ(他にもcreate部分や末尾の;などを削除している)

前述したようにyanagishimaにはクエリのフォーマット機能があります。これはPrestoのフォーマッターを使っているだけですが、フォーマットされたクエリをJSONで返すのでこれを使っています。

雰囲気は下記のような感じです。

conn = pyhs2.connect(...)
cursor = conn.cursor()
cursor.execute("desc formatted %s" % (presto_view))
for row in cursor:
    if row[0] == "View Original Text: ":
        base64encode_ddl = row[1].replace("/* Presto View: ", "").replace(" */", "")
        base64decode_ddl = commands.getoutput("echo %s | base64 -d" % (base64encode_ddl))
        json_data = json.loads(base64decode_ddl)
        already_created_format_ddl = json_data["originalSql"]
        
        data = {}
        with open (presto_view_file, "r") as file:
            data["query"] = file.read().rstrip("\n").rstrip().rstrip(";")
        r = requests.post(yanagishima_url + "/format", data=data)
        jc = r.json()
        format_github_ddl = jc.get("formattedQuery")
        lines = format_github_ddl.split("\n")
        delete_create_format_github_ddl = "\n".join(lines[1:])
        # check difference between already_created_format_ddl and delete_create_format_github_ddl