読者です 読者をやめる 読者になる 読者になる

BdashというBIツールをリリースしました

BdashというアプリケーションをElectronで作りました。

bdash-app/bdash: A simple business intelligence application.

以下からダウンロードしてインストールできます(現状まだMac版だけ)。

https://github.com/bdash-app/bdash/releases

ざっくりとこんな感じのことができる。

  • SQLを書いて保存&実行できる
  • 結果を元にグラフを書ける
  • gistで共有できる
  • 現状で対応しているデータソースはMySQLPostgreSQL(Redshift含む)、BigQuery

仕事でRedshiftを使って分析SQLを書くことが増えて、手元ではJupyter Notebookを使ってたんだけど、SQL書いてグラフを書くだけの用途には若干オーバースペックでもうちょっと簡単にできるといいなと思ったのがきっかけで去年から作り始めたのがようやく形になったので1.0.0をリリースした。

社内でしばらくベータ版を使ってもらっていたので、まともに使えるぐらいのクオリティにはなっていると思う。(ただしBigQueryは1.0.0リリース直前に実装してまだ使い込まれてないので完成度は低いかもしれない)

利用例

例えば僕が運用しているAdventarの、年ごとのエントリ数と、実際にエントリした人が記事(URL)を投稿したかどうかの割合を出してみる。

こんな感じでクエリを書いて実行できて

f:id:hokaccha:20170207221708p:plain

フォームちょいちょいといじるとグラフが書ける。年々エントリ数は増えているものの記事の投稿率が下がっているのがわかる。改善しよう。

f:id:hokaccha:20170207221712p:plain

そして結果をこんな感じでgistにワンクリックで共有できる。

https://gist.github.com/hokaccha/e128e1c3a68527ebf2c50d5e95a089b1

自画自賛だけどこのgist共有が本当に便利。

まだまだ実装したい機能はたくさんあってこれからガシガシ開発していくので興味がある人は使ってみてフィードバックもらえると嬉しいです。

element.querySelectorで直下の要素を指定する

javascript

例えばel(DOM Element)の直下の.fooを取りたいときに、以下のようにしたい。

el.querySelectorAll('> .foo');

これだとエラーになるんだけど:scopeを使えばいける。

el.querySelectorAll(':scope > .foo');

ChromeFirefoxでは動いた。

macOSでアプリケーションが署名されてるかどうか確認する

mac

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: アプリケーション

という感じでした。

どんなアプリケーションだったか

  • リアルタイムお絵かきアプリ
  • Reactでサーバーサイドレンダリング
  • 裏にAPIサーバー
  • SSEで変更をpush
  • 全部Docker

序盤

itamaeでMySQL, Nginx, Ruby, Redisあたりのレシピを作っといて複数サーバーでも必要なミドルウェアといい感じの設定をすぐに入れられるようにしといたんだけどまさかのDockerだったのでそのまま使えなくてパニくったのが序盤のハイライト。

Docker全くわからないという感じではなかったものの、メンバー全員そこまで熟練しているわけではなく、最初の構成の確認とかローカルに開発環境作ったりするのに手間がかかって初動が遅れた。

結局昼前ぐらいに@wata_devの提案でDockerをやめようと決断して全てのコンポーネントを脱Dockerしてベンチ通ったのが13時ぐらいだった。このあたりはほとんど@wata_devにやってもらった。これで事前に仕込んどいたitamae使えるようになって僕歓喜

終端をNginxで受けて静的ファイルをNginxで返すぐらいまではやって多少スコアあがったぐらいだった。ここまでアプリケーションには全く手を入れられず。

中盤

とりあえずサーバー5台をフルで使うための分散戦略を考え始めた。全サーバーにReact、RubyMySQLを同じ構成で立てて、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 rails

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 予選の記録

isucon

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万を超える

この実装が今回秘孔をついたらしい。

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

rails

http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope

  • ARのscopeはclass methodとだいたい同じ
  • scopeはnilfalseを返した時にallを返すのでメソッドチェインをブロックしない
  • 必ずActiveRecord::Relationを返す場合はscopeのほうがよさそう