BdashというBIツールをリリースしました
BdashというアプリケーションをElectronで作りました。
bdash-app/bdash: A simple business intelligence application.
以下からダウンロードしてインストールできます(現状まだMac版だけ)。
https://github.com/bdash-app/bdash/releases
ざっくりとこんな感じのことができる。
- SQLを書いて保存&実行できる
- 結果を元にグラフを書ける
- gistで共有できる
- 現状で対応しているデータソースはMySQL、PostgreSQL(Redshift含む)、BigQuery
仕事でRedshiftを使って分析SQLを書くことが増えて、手元ではJupyter Notebookを使ってたんだけど、SQL書いてグラフを書くだけの用途には若干オーバースペックでもうちょっと簡単にできるといいなと思ったのがきっかけで去年から作り始めたのがようやく形になったので1.0.0をリリースした。
社内でしばらくベータ版を使ってもらっていたので、まともに使えるぐらいのクオリティにはなっていると思う。(ただしBigQueryは1.0.0リリース直前に実装してまだ使い込まれてないので完成度は低いかもしれない)
利用例
例えば僕が運用しているAdventarの、年ごとのエントリ数と、実際にエントリした人が記事(URL)を投稿したかどうかの割合を出してみる。
こんな感じでクエリを書いて実行できて
フォームちょいちょいといじるとグラフが書ける。年々エントリ数は増えているものの記事の投稿率が下がっているのがわかる。改善しよう。
そして結果をこんな感じでgistにワンクリックで共有できる。
https://gist.github.com/hokaccha/e128e1c3a68527ebf2c50d5e95a089b1
自画自賛だけどこのgist共有が本当に便利。
まだまだ実装したい機能はたくさんあってこれからガシガシ開発していくので興味がある人は使ってみてフィードバックもらえると嬉しいです。
macOSでアプリケーションが署名されてるかどうか確認する
codesignコマンドで確認できる。
$ codesign -vd /Applications/Hyper.app Executable=/Applications/Hyper.app/Contents/MacOS/Hyper Identifier=co.zeit.hyper Format=app bundle with Mach-O thin (x86_64) CodeDirectory v=20200 size=269 flags=0x0(none) hashes=3+3 location=embedded Signature size=8917 Timestamp=Oct 18, 2016, 10:48:57 AM Info.plist entries=22 TeamIdentifier=JW6Y669B67 Sealed Resources version=2 rules=12 files=2345 Internal requirements count=1 size=176
ISUCON6 4位でした
会社の同僚の@wata_devと@osadake212とISUCON6本戦に出場して4位でした。チームメンバー全員普段アプリケーション書いてるエンジニアでインフラ寄りのメンバーがいなくて複数台構成の本戦はきついだろうなと思ってたので、4位という結果はかなり健闘したほうだと思うけどやはり悔しい・・。
本戦での役割的にはざっくり
- @hokaccha: 方針たて
- @wata_dev: インフラ
- @osadake212: アプリケーション
という感じでした。
どんなアプリケーションだったか
序盤
itamaeでMySQL, Nginx, Ruby, Redisあたりのレシピを作っといて複数サーバーでも必要なミドルウェアといい感じの設定をすぐに入れられるようにしといたんだけどまさかのDockerだったのでそのまま使えなくてパニくったのが序盤のハイライト。
Docker全くわからないという感じではなかったものの、メンバー全員そこまで熟練しているわけではなく、最初の構成の確認とかローカルに開発環境作ったりするのに手間がかかって初動が遅れた。
結局昼前ぐらいに@wata_devの提案でDockerをやめようと決断して全てのコンポーネントを脱Dockerしてベンチ通ったのが13時ぐらいだった。このあたりはほとんど@wata_devにやってもらった。これで事前に仕込んどいたitamae使えるようになって僕歓喜。
終端をNginxで受けて静的ファイルをNginxで返すぐらいまではやって多少スコアあがったぐらいだった。ここまでアプリケーションには全く手を入れられず。
中盤
とりあえずサーバー5台をフルで使うための分散戦略を考え始めた。全サーバーにReact、Ruby、MySQLを同じ構成で立てて、room idごとに分散させつつtokenとかroomなどの共通で使うデータだけ一箇所に集めて、他はそれぞれのサーバーにたってるMySQL見るよにすればよさそうという案でアプリケーション側の実装とインフラが側の設定を始める。
これは最終的にはうまくいかなくて、結局トップページのroom一覧をつくるのにstrokeのデータも中央にないとダメで、failもでまくっていたので一旦諦めてMySQLは一箇所に変更した。完全に方針ミスった。
とりあえずアプリケーションの分散はできたのでこの構成でアプリケーションの高速化に取りかかることに。が、時間が足りなすぎた・・。この時点で16時ぐらいだったかな・・。
終盤
とりあえず分散させただけで高速化ほとんどやれてないので、アプリケーション側を改善に入る。ReactがSSRしている/img/:id
が重いことはわかっていたのでそこをキャッシュしようということになった。
JS側でキャッシュしてRubyは更新のときにキャッシュをpurgeする作戦で、僕がJS側、Ruby側を@osadake212にやってもらって割とさくっと実装できたが、僕がタイポしまくりで本番を壊しまくってたのが終盤のハイライト。
その実装が入って20000点を超えた。その後色々やりたいことはあったけど時間内でできそうなことがなかったので、ログ切ったり再起動チェックしてフィニッシュした。結果failせずに4位だったのはよかった。
その他の感想
- 序盤に、httpsってことはHTTP/2話せそうだねーという話はしていたが最後のほう完全に忘れていた
- Node.js/Reactあたりは自分の得意分野だったのにもかかわらずそこを最適化するまでに至らなかった。一番の悔しいポイント・・
- SSEのstreamの接続が数秒で切れるようになってる実装の意味がわからなくてだいぶそっちに頭もっていかれた
- 途中の方針完全に失敗してごめんなさいという気持ち
- itamae最高だった
謝辞
@wata_devは予選で一番難しい正規表現の改善をお願いした結果、最後までバグがとれなくて、つらい思いをさせてしまったんですが、本戦ではインフラの構築から細かいところまで色々とやってもらって本当に助かりました。
@osadake212には雑にアプリケーションの実装方針伝えたらさくっと実装してくれて助かりました。逆に実装早くて手持ち無沙汰にさせてしまってた感すらありました。
よいチームメートとでれて楽しかったです。
また、出題チームは本当にいい問題ありがとうございました。運営チームも毎年よい大会をありがとうございます。来年もがんばります。
sqlite3でカラム定義の変更
sqlite3だとalter table change column
みたいのがないらしいのでnot nullとかdefault valueを変更するのどうすればいいんだろうと思ってrailsがどうしてるか見てみた。
class CreateTodos < ActiveRecord::Migration[5.0] def change create_table :todos do |t| t.string :text t.boolean :completed t.timestamps end end end
class UpdateTodos < ActiveRecord::Migration[5.0] def change change_column :todos, :text, :string, null: false end end
こんな感じのmigrationを実行してみるとログはこうなった。
(0.1ms) begin transaction (0.2ms) CREATE TEMPORARY TABLE "atodos" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "text" varchar NOT NULL, "completed" boolean, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) (0.4ms) INSERT INTO "atodos" ("id","text","completed","created_at","updated_at") SELECT "id","text","completed","created_at","updated_at" FROM "todos" (0.6ms) DROP TABLE "todos" (0.1ms) CREATE TABLE "todos" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "text" varchar NOT NULL, "completed" boolean, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) (0.1ms) INSERT INTO "todos" ("id","text","completed","created_at","updated_at") SELECT "id","text","completed","created_at","updated_at" FROM "atodos" (0.1ms) DROP TABLE "atodos" SQL (0.1ms) INSERT INTO "schema_migrations" ("version") VALUES (?) [["version", "20161013104634"]] (0.7ms) commit transaction
一時テーブル作ってinsert select
でデータ退避させて作り直してるらしい。なるほど・・。
ISUCON6 予選の記録
ISUCON6にしましまスペシャルというチーム名で会社の同僚と参加して最終スコア147,028で予選通過できました。言語はRubyです。コードはここに公開してます。
https://github.com/hokaccha/isucon2016_qualifying
以下やったこととかのメモ。
10時-11時
下準備を整える
- どういうアプリケーションか確認
- コードをざっと読む
- サーバーのスペックとか動いてるプロセスを確認
- ベンチ流してみてリクエストの傾向を把握する
- nginxのログから集計してブラウザから見れるような雑なやつを用意しといた
11時-12時
作戦をたてる
- とりあえず
/
と/keyword
が遅いのでそこを改善することにする htmlify
の改善、isutar
の統合、インフラ・ミドルウェア周りの設定の3つに作業を分けてそれぞれ取り掛かるisupam
もどうにかしたほうがいいかと思ったけど、計測結果を見るにそこまで遅くないのでこの時点で改善を捨てた(結果この判断はよかった)- とりあえずruby実装に変えてベンチ流したらスコア 0になってここからしばらく0だった
12-15時
- ローカルで開発できる環境作ったりdeploy script書いて開発環境を整える
- nginxでstatic file返す
- unix domain socketを使う
isutar
を統合してstarを全部redisに載せるuser
は最初に全部引いてきてメモリに載せる- unicorn の worker 数増やす
- などなど、
htmlify
の改善以外は(isupam
を除いて)ほぼほぼやり終えた - しかしスコア300点だった(0からやや進んで喜んでた)
15-16時
htmlify
の置換処理をいい感じにする最初の実装がマージされる- 正しくリンクが作られずベンチがfailしたのでrevertする
htmlify
の高速化が入ってもどのみち結果はキャッシュしといたほうがよさそうなのでキャッシュを実装してみるhtmlify
の結果をredisにキャッシュしといてPOSTでinsert/updateが走ったときに変更が必要なキャッシュだけ消す実装にした- キャッシュの実装が入った結果スコア2万を超える
この実装が今回秘孔をついたらしい。
@sora_h @kani_b select keyword from entry where description like '%#{keyword}%' で引いてきたやつを全部消した
— Kazuhito Hokamura (@hokaccha) 2016年9月18日
16-17時
htmlify
の高速化のバグがなかなか取れなくてこのままでは間に合わない可能性がありそうということに気づき始める- これまで
htmlify
の高速化を信じて全く手をいれてなかったので、間に合わなかったことを考えて最低限keywords
ぐらいはキャッシュするようにした - この実装で6万ぐらいまでいった気がする(記憶が曖昧)
17-18時
- 初期データのdescription -> htmlの変換を予めやってDBに保存しといた上で
/initialize
でredisに全部乗っけるようにした- その結果スコア15万超える
htmlify
の高速化ロジックはバグが取れずにマージを断念- ログ切ったりunicornのworker数調整してベンチマークガチャ回して15万弱でfinishした
感想
htmlify
のロジック変更が間に合わない場合のことを考えた実装に取り掛かる判断が早めにできたのがよかった- アプリケーションよりのエンジニア3人で望んだけどなんとかなる問題でよかった
- 途中がんばって色々やってもスコアが0ではりついて動かなかったのはつらかった
- 本戦がんばります
Railsのscopeとclass method
http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope
- ARのscopeはclass methodとだいたい同じ
- scopeは
nil
やfalse
を返した時にall
を返すのでメソッドチェインをブロックしない - 必ず
ActiveRecord::Relation
を返す場合はscope
のほうがよさそう