console.blog(self);

技術、読んだ本、いろいろ。

1年間で取り組んだProttのパフォーマンスチューニング

f:id:sadah:20151225074123p:plain

Goodpatch CTOの id:sadah です。

これはGoodpatch Advent Calendar 2015の25日目のエントリです。

Prottというサービスではパフォーマンスチューニングのために、Railsアプリケーション/フロントエンドの改善、ミドルウェアのバージョンアップ、SPDYやHTTP/2の導入、オンプレミス環境からAWSへの移行などを行ってきました。 今回はその取り組みの一部をご紹介します。

Slides

10月にGoodpatch Engineer Meetupというイベントをやって、Prottのパフォーマンスチューニングについて話した。

イベントの内容はこちらにまとまっている。

僕のスライドはこんな感じ。

"Make the Prott Faster"ってタイトルにしたけど、theはいらないよってあとから教えてもらった。cute misstakeって言われた。感覚がわからない。

パフォーマンスチューニングで取り組んだこと

Goodpatchには2015年1月に入社して、そこからいろいろなことに取り組んできた。10月の時点で2倍くらいは速くなった。いまはさらに速くなっている。でもまだまだ改善できるので、来年もパフォーマンスチューニングに取り組んでいく。 2015年10月に計測したときはこんな感じだった。

f:id:sadah:20151225074418j:plain

f:id:sadah:20151225074437j:plain

イベントのあった10月までに、こんなことをやってきた。

  • Infrastructure
    • RubyRailsのバージョンアップ
    • Nginxのチューニング
    • SPDYの導入(HAProxyからNginxへの移行)
  • Rails Application
    • N+1問題の解消(道半ば)
    • Slow Queryの解消
    • メモ化
    • Cache(Russian Doll Caching)
  • Frontend
    • AngularJSバージョンアップ
    • $watchの削減

11月から12月にかけて大きな変更をした。

  • Infrastructure
    • オンプレミス環境からAWSへの移行(計画停止しての移行)
    • MongoDB / Nginx など各種ミドルウェアのバージョンアップ
    • HTTP/2の導入

僕は主にRails側の対応を行い、フロントエンドやインフラの対応はチームメンバーがやってくれた。SPDY導入や、$watchの削減についてはこちらの資料にまとまっている。

Prottの抱えていた課題

プロジェクト一覧の画面が遅い。

f:id:sadah:20151225074858j:plain

スクリーン一覧の画面が遅い。

f:id:sadah:20151225074920j:plain

このよく使うページを中心に改善していった。

パフォーマンスチューニングの指針

よく言われていることを、ちゃんと実践するようにする。

f:id:sadah:20151225074958j:plain

  • 推測するな、計測せよ。
  • 時期尚早な最適化は諸悪の根源だ。
  • 1度に修正するのは1箇所のみ。

このあたりに詳しく書いてある。

ただこのときは、時期尚早どころか
もっと早く対応するべきだった…。

SPDY

f:id:sadah:20151225075102j:plain

SPDYの導入にあたってはこんなことをした(いまはHTTP/2を使っているけど)。

  • spdycatで接続確認する
  • h2loadで計測する

このあたりを参考にさせていただいた。

spdycatを使うと、こんなふうに接続確認ができる。

% spdycat https://prottapp.com/ -v -n
[  0.379] NPN select next protocol: the remote server offers:
          * spdy/3.1
          * http/1.1
          NPN selected the protocol: spdy/3.1
[  0.386] Handshake complete
[  0.387] recv SETTINGS frame <version=3, flags=1, length=20>
          (niv=2)
          [4(0):100]
          [7(0):2147483647]
[  0.387] recv WINDOW_UPDATE frame <version=3, flags=0, length=8>
          (stream_id=0, delta_window_size=2147418111)
[  0.387] send SYN_STREAM frame <version=3, flags=1, length=215>
          (stream_id=1, assoc_stream_id=0, pri=3)
          :host: prottapp.com
          :method: GET
          :path: /
          :scheme: https
          :version: HTTP/1.1
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: spdylay/1.3.2

h2loadで負荷を掛けられる。cでクライアント数、nで回数を指定できる。出力はこんな感じ。

% h2load -c 1 -n 1 https://prottapp.com
starting benchmark...
spawning thread #0: 1 concurrent clients, 1 total requests
Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES256-GCM-SHA384
Server Temp Key: ECDH P-256 256 bits
progress: 100% done

finished in 117.42ms, 8.51665 req/s, 1.29MB/s
requests: 1 total, 1 started, 1 done, 1 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 158829 bytes total, 375 bytes headers, 158106 bytes data
                     min         max         mean         sd        +/- sd
time for request:    78.29ms     78.29ms     78.29ms         0us   100.00%
time for connect:    38.74ms     38.74ms     38.74ms         0us   100.00%
time to 1st byte:    90.82ms     90.82ms     90.82ms         0us   100.00%

開発環境でApache Benchとh2loadで測定したら、レスポンスタイムが10%程度改善した。あんまり厳密は計測していなくて、遅くなったりしないことだけ確認した。

MongoDBのSlowQuery

mongoidのexplainメソッドだとほしい情報が取れなかった(executionStatsを渡した結果がほしかった)ので、monogo shellでexplainした。

> db.xxxx.find({ deleted_at: null, screen_id: ObjectId('XXXXXXXXXXXX')}).sort({_id:1}).explain('executionStats')

{ "queryPlanner" : { ...
  "winningPlan" : {
          "stage" : "FETCH", ...
  },},
"executionStats" : { ...
  "executionTimeMillis" : 111,
  "totalKeysExamined" : 55555,
  "totalDocsExamined" : 55555,
} }

winningPlanの内容を確認すると、stageがFETCHになってはいるけど、sortが効いてるだけっぽい。 検索したindexの数と検索したdocumentの数が同一になってしまっているので、indexは効いていなかった。

indexを追加することで改善した。

> db.xxxx.find({ deleted_at: null, screen_id: ObjectId('XXXXXXXXXXXX')}).sort({_id:1}).explain('executionStats')

{ "queryPlanner" : { ...
  "winningPlan" : {
          "stage" : "FETCH", ...
  },},
"executionStats" : { ...
  "executionTimeMillis" : 2,
  "totalKeysExamined" : 1,
  "totalDocsExamined" : 1,
} }

Rails Application

計測するためにつかったツールとか。

使わなかったツールとか。

stackprofとstackprof-webnav

stackprof

stackprofはRubyのプロファイラ。作者はGitHubのAman Gupta(@tmm1)さん。RubyKaigi2014 3日目の基調講演のスピーカーでセッション終了後の拍手がものすごかった。

RubyKaigi2014でのスライドはこちら。

config.ruをこんな感じにするとRAILS_ROOT/tmpにダンプができる。

require ::File.expand_path('../config/environment',  __FILE__)

use StackProf::Middleware, enabled:    true,
                           mode:       :cpu,
                           raw:        true,
                           interval:   1000,
                           save_every: 5
run Rails.application

stackprofで解析するとこんな感じ。

$ stackprof tmp/stackprof-cpu-*.dump --text --limit 1
==================================
  Mode: cpu(1000)
  Samples: 780 (5.45% miss rate)
  GC: 88 (11.28%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
        33   (4.2%)          27   (3.5%)     Psych::ScalarScanner#tokenize

stackprof-webnav

stackprofの出力をWebで確認できる。ドリルダウンとかできて便利。

$ gem install stackprof-webnav
$ stackprof-webnav -f tmp/stackprof-cpu-53222-1444801808.dump

こんな感じで表示される。

f:id:sadah:20151225075311j:plain

こんな感じでドリルダウンできる。

f:id:sadah:20151225075355j:plain

Frontend

画像の Layout / Paint が重かった。Chrome DevTools の timeline を使って計測。画像を縮小して表示している部分が重かった。

f:id:sadah:20151225075536j:plain

DevToolsのUI、カオス。そしてまたいろいろ変わっているんだろうな。Renderingタブにいろいろ設定がある。

f:id:sadah:20151225075606j:plain

いろいろ有効にすると、LayerやFPSを確認できる。

f:id:sadah:20151225075634j:plain

改善するために、試しにtransform使ってGPU使ってみた(ちょっと試してみたかった)。速くなったけど根本的な解決じゃないではないので、Nginx(ngx_small_light)で縮小したサイズの画像を生成することにした。

画像への初回リクエスト時に指定されたサイズの画像を生成しキャッシュするようにした。適切なサイズの画像を使うことで、ブラウザがかなりスムーズに動くようになった。

あとはこんなツールを使ったり、細かいJSの改善をやり続けていった。

AWS

これらの取り組みのあと、インフラをオンプレミス環境からAWSに移行した。地理的には東京からオレゴンに移行した。Prottをグローバルに使われるサービスにしていくために必要なことだった。とても難しい決断だった。

ヨーロッパでProttが遅いという報告を以前から受けていて、10月から11月にかけてベルリンに行って調査した。アメリカにサーバを置くと、かなり改善することがわかった。もちろん日本からのアクセスのレイテンシーは大きくなる。

そのため、AWS化にともないNginx / MongoDBなど主要なミドルウェアのバージョンアップを行い、さらに使うインスタンスのスペックを上げた。

その結果、日本からのアクセスのレイテンシーは増えたけどパフォーマンスは上がり、これまでよりも速くなった。ヨーロッパやアメリカでは格段に速くなった。

事前にしっかり準備することで、大きな問題も起こさずに移行を完了できた。海外のメンバーから、Prottが速くなった!というメッセージをたくさんもらえて、とてもうれしかった。

まとめ

フロントエンドの改善は id:yoshiko_pg が、がつがつ進めてくれた。インフラ周りのほとんどの作業を id:urapico が担当してくれた。他のメンバーもいろいろな改善をやってくれた。

経営的判断として、パフォーマンスチューニングを最優先課題として取り組んだ。チーム全体でパフォーマンスチューニングに
取り組む期間を作れたことはとてもよかった。

ただ、まだまだボトルネックになっているところは多いし、もっと速くしていかなくてはならない。例えばキャッシュで速くなっても初回アクセスの重さが解消できていないし、当たり前の設定を入れた感じなので
もっと攻めたい。

Goodpatchは「ハートを揺さぶるデザインで世界を前進させる」というビジョンを掲げている。サービスが遅いと
ハートを揺さぶれない。パフォーマンスは
UXに直結する。

Prottをもっとよいサービスにするために、来年もいろいろなことに取り組んでいく。

We are hiring!!

Prottを改善していくエンジニアを募集しています!興味があれば気軽に連絡ください。

RubyKaigi2015に行って、Ruby 2.3.0-preview2をインストールした

今年もRubyKaigiに行ってきた!

去年はこんな感じだった。

初日のKeynoteの最中にRuby 2.3.0-preview2がリリースされた。

というわけでさっそくインストールしてみた。

Ruby 2.3.0-preview2のインストール

開発環境はMacで、homebrewとrbenvを利用している。homebrewのインストール方法とかは割愛。まだ rbenv や ruby-build をインストールしていない場合は、こんな感じでインストールする。

brew install --HEAD ruby-build
brew install rbenv

すでにrbenvやruby-buildがインストールされている場合は、こんな感じでアップデートする。

brew update && brew upgrade --HEAD ruby-build
rbenv install 2.3.0-preview2

ruby-buildのインストールやアップデートで --HEAD のオプションを渡す必要がある。これで最新のruby-buildを利用できる。このあたりのことは rbenv/ruby-build に書いてある。

brew upgrade --HEAD ruby-build
brew install --HEAD ruby-build

続いてRuby2.3.0-preview2をインストールする。

rbenv install 2.3.0-preview2

ちゃんとインストールできたか確認する。

rbenv versions
  system
  2.2.2
* 2.2.3 (set by /Users/sada/.ruby-version)
  2.3.0-preview2

よさそう。rbenv globalで利用するRubyのバージョンを指定できる。

rbenv global 2.3.0-preview2

RailsアプリケーションのRubyバージョン変更

Prottでは .ruby-version を利用している。RAILS_ROOTにある .ruby-version のバージョンを書き換える。

2.3.0-preview2

RAILS_ROOTのRubyのバージョンを確認する。

ruby -v
ruby 2.3.0preview2 (2015-12-11 trunk 53028) [x86_64-darwin15]

いい感じ。Rubyのバージョンが変わったので、bundlerをインストールする。

gem install bundler

rbenv rehash したあとに、bundle install で gem をインストールする。

rbenv rehash
bundle install

ProttはRSpecを使っているので、テストを流して問題ないことを確認した。CHANGELOGから問題の起きそうな変更がないことを確認した。 サーバを起動して簡単な動作確認を行ってみましたが、特に問題は起きなかった。

プロダクションに投入するときにはもうちょっとしっかり確認するけど、だいたいはこんな感じ。さくっと動いてよかった。

&.(ぼっちオペレーター)や did_you_mean をちょこちょこ試した。楽しい。クリスマスに正式版がリリースされる予定なので、積極的にバージョンアップしていきたい。

まとめ

Goodpatch は RubyKaigi 2015 のゴールドスポンサー。まだ小さい会社だけど、好きな技術のカンファレンスのスポンサーになったり、こういう活動ができてうれしい。

もちろんRubyエンジニアを募集していて

そのほかの職種も募集している。

興味があったら、気軽にぽちったり、連絡ください。

RubyKaigi 2016は京都で開催される。楽しみ。

Software in 30 Days スクラムによるアジャイルな組織変革"成功"ガイド

「Software in 30 Days スクラムによるアジャイルな組織変革"成功"ガイド」を読了した。

スクラムに関する本だけど、スクラム自体がテーマではなく、スクラムをどうやってチームや組織に適用していくのか、といった内容だった。

目次はこんな感じ。

  • 第1部 なぜ世界のあらゆるビジネスが30日間でソフトウェアを作れるのか
    • 第1章 ソフトウェアの危機:間違ったプロセスは間違った結果を生む
    • 第2章 スクラム:正しいプロセスは正しい結果を生む
    • 第3章 自分でやってみる:パイロット
    • 第4章 私は何ができるのか
  • 第2部 どうやって30日間でソフトウェアを作るのか
  • 付録A 用語集
  • 付録B スクラムガイド
  • 付録C エンタープライズアジリティを獲得するためのプレイブック

勝手に本書の内容を分けてみると、こんな感じだと思う。

本書を翻訳された吉羽さんのエントリに、こんなことが書かれていた。本書の内容が端的に示されていると思う。

本書は、ジェフとケンにとって初の「ソフトウェア開発をしない人向けに書いた」本です。

<中略>

スクラムのプラクティスはそれほど書かれているわけではなく、スクラムの考え方、組織への適用にフォーカスをあてているので、開発チームの方というよりは、マネージャー層以上の方が読まれるといいと思います。

Software in 30 Days スクラムによるアジャイルな組織変革“成功"ガイド 発売のお知らせ | Ryuzee.com

なぜソフトウェア開発プロジェクトは失敗するのか

第1部では、なぜソフトウェア開発プロジェクトが失敗するのか、いろいろな事例をもとに解説されている。

スクラムの本は他にも読んでいたので、この章ではあまり新しい発見はなかった。ただ、いろいろな事例が紹介されているので、スクラムがうまく導入できない状況の人には、参考になることが多いと思う。

チームでのスクラム

第2部では、スクラムをどうやって適用していくかという内容になる。

第5章、第6章では、普通のスクラムの事例が紹介されている。数名のチームでのスクラムの解説で、基本的な考え方やスプリントなどのプラクティスがいくつか紹介されている。

組織でのスクラム

第7章から第10章、ここがいちばん面白かった。複数スクラムチームで、どうやって巨大なソフトウェアを開発していくのか、スクラムを適用すことでどうやって組織を変革していくのか、といったことが、こちらも多くの事例をもとに解説されている。

第7章ではAdobe Premiere Pro CS3からCS5までの開発チームの変化が紹介されていて、そこが読み物としても特に面白かった。

Adobeのチームでは、個別のチームが開発を行いビックバン統合(リリース前にすべてを一気に統合する)でたくさんのバグがでてしまう状況から、スプリントごとにシステムを統合することで安定した開発ができるようになった。

第8章の組織や企業の変革に関する内容で、こんな一文があって印象的だった。

スクラムを組織全体に導入しようとする上級役員がいる場合、組織で最も変革を楽しみ、成功を得やすいのは、その上級役員である。

また第10章ではこんな文章があって、これは短い内容だけど納得した。

組織変革の際には、2つのスクラムチームを作る。

  1. 変革チーム:スクラムを使って組織を変革し、ビジョンを達成する。
  2. 展開チーム:スクラムを使って変革の実業務を行い、変化を引き起こす。

スクラムのやりかた

本文内で、スクラム自体の内容が少ない分、付録で補われている。スクラムガイドはPDFで読むことができる。

少ない量で、スクラムの解説がされているけど、短い分ちょっと分り辛い。スクラムを知っている人にはわかりやすいガイドだけど、スクラム自体を知りたいなら、ほかの書籍を読んだほうがよさそう。

用語集は短いけれど、わかりやすくまとまっていていい感じ。

まとめ

読んでたら、次のプロジェクトのメンバーのアサインや役割とか、こうしたらうまく回るんじゃないかとか、やりたかったあれができるようになるんじゃないかとか、いろいろアイディアがでてきた。

スクラムをちゃんと適用するのはけっこう大変で、自分のチームでもいくつかのプラクティスしか使っていない。もうちょっといろいろやっていきたいところ。

本書では旧態依然な企業が多く紹介されていた。今の会社は組織を作っている途中なので、組織変革というより、組織作りの真っ最中。だからいろいろなことが試しやすい。開発が必要なプロジェクトも多く、これから新しい問題がたくさん出てくると思う。それをうまくこなしていけるよい組織にするために、開発プロセスもちゃんと考えていきたい。