Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

RoRとAWSで100,000Req/Minを処理する

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige

Hier ansehen

1 von 56 Anzeige
Anzeige

Weitere Verwandte Inhalte

Diashows für Sie (20)

Ähnlich wie RoRとAWSで100,000Req/Minを処理する (20)

Anzeige
Anzeige

Aktuellste (20)

RoRとAWSで100,000Req/Minを処理する

  1. 1. RoRとAWSで100,000Req/ Minを処理するには? または、インフラを構築すると きに最低限気をつけていること 株式会社アカツキ 田中 勇輔
  2. 2. 株式会社アカツキ CTO 田中 勇輔 @csouls Rubyとインフラが好きです! 好きなキーボード配列は Qwertyですが、最近Dvorak 派が社内に増えて挫けそうです
  3. 3. 今日話すこと • RoR, AWS で 100,000 Req/Min を処理するの に必要なこと • 失敗の共有
  4. 4. 前提 • RESTなAPIサーバ • なぜRailsなのか? • 既存資産を活かせる • 認証や課金のライブラリ • 環境構築、デプロイの仕組み • Rails-APIでも十分
  5. 5. 余談 • Rails 5 からWebSocketサポート・Turbolinksでの部分更新・Rails-API を追加 • リアルタイム更新性の強化というリッチ方面と、Rails-APIの追加というシン プル方面の両面を強化 • Railsがあればなんでも出来ると捉えるか、Rails学習初期コスト高すぎと捉える か • 企業にとっては、Railsが常に進化していて、追って行けばたいていのことが 出来るというのは楽です。利用者が増えることにより、周辺技術が発達して いくのも嬉しいことです • これからもアカツキはRails/Rubyを応援していきます!
  6. 6. RoR, AWS で 100,000 Req/Min を処理するのに必要なこと • スケールアウト戦略 • 負荷テスト • 地雷をうまく避ける
  7. 7. RoR, AWS で 100,000 Req/Min を処理するのに必要なこと • どれだけ大量のリクエストがあったとしても、難し く考える必要はない • 基本は、ボトルネックを特定し、スケールアップ or スケールアウトのどちらかで対応するか(対応でき るか)を判断し、やるだけ • パターンを知ることで素早く対応できるようになる
  8. 8. スケール アウト
  9. 9. アプリケーション
  10. 10. アプリケーション • c3.4xlarge x 30~50台 • 負荷テストをした時に、8xlarge x 15台より も、4xlarge x 30台の方が安定した • スケールアウトしたアプリケーションサーバの 構成管理、デプロイ方法はきちんと考えておく
  11. 11. 補足: 負荷テスト条件 c3.8xlarge x 15 c3.4xlarge x 15 nginx worker_processes 36 16 worker_connections 1024 1024 client_body_timeout 30 30 client_header_timeout 30 30 proxy_read_timeout 30 30 Unicorn worker_processes 144 64 timeout 30 30 • 以下条件で10万Req/Min を超える負荷を掛けた時、c3.8xlarge x 15 では、 HTTP Response Code: 500, 504 が発生した • 原因の深追いは時間の制約で出来ていない…
  12. 12. アプリケーション構成+デプロイ • Nginxをリバースプロキシとして、UnicornをWebサーバと して使い、GodでUnicornプロセスを監視する • Nginx1.6.2 • Unicorn 4.8.3 • God 0.13.6 • Rails 4.2 • God 設定(chef cookbook): https://github.com/csouls/chef- god-unicorn
  13. 13. RDB • db.r3.8xlarge! + r3.4xlarge * 8 の富豪構成 • どちらも、ピーク時CPU30%未満なので、4xlarge + 2xlarge にダウンしてもまったく問題ない
  14. 14. RDB • ゲームデータは1DB。ユーザの行動によって 増えるデータは、ユーザIDを元にShardingし て複数DBに分割
  15. 15. RDB • DB Parameters • 一般的なWebサービスであれば、DBパラメータは、RDSデ フォルトでほぼ問題ない • 要件に合わせてマニアックに変更することはある • Sharding • ユーザデータを分割 • RailsでのShardingはOctopusを使っている。辛さはある
  16. 16. RDB • config/shards.yaml というような config/shards.ymlを用意して default: &default adapter: mysql2 encoding: utf8 charset: utf8 collation: utf8_general_ci reconnect: false pool: 5 <—— snip —-> octopus: environments: - production production: user01: <<: *default database: "user01" host: user01 user02: <<: *default database: "user02" host: user02
  17. 17. RDB • リクエスト単位でユーザDB向けに Octopus.using を指定 class ApiController < ApplicationController around_action :select_shard private def select_shard(&block) if current_user.blank? logger.error "select_shard current_user is blank" yield else Octopus.using(User.shard(current_user.id), &block) end end end
  18. 18. RDB • すると、ゲームデータ(1DB)へのアクセスは、ActiveRecord レベルでusing(:master)を指定することになる • 以下の様に、ActiveRecord::Baseを継承して、Baseモデル を作りたい class MasterModel < ActiveRecord::Base self.abstract_class = true octopus_establish_connection(Rails.configuration.database_configuration[Rails.env]) end class Card < MasterModel end
  19. 19. RDB • Octopusの辛み • https://github.com/tchandy/octopus/ issues/219 • 継承したクラスで、 octopus_establish_connection がうまくいか ない問題
  20. 20. RDB • 毎回 using(:master) 書く • めんどくさい • ゲーム共通データもUser Shardに置く • 更新時の差分が怖い
  21. 21. RDB • 毎回 using(:master) 書く • めんどくさい • ゲーム共通データもUser Shardに置く • 更新時の差分が怖い
  22. 22. RDB • 今思えば、ゲーム共通データもUser Shardに 置く方が辛くなかった…orz • 一部辛みがあるとはいえ、今のところ Sharding用途ではOctopusが筋良さそう • 改良に取り組んでいきたい
  23. 23. デプロイ on AWS • Capistrano3 with EC2 tag • EC2のタグを元に、デプロイ先を決定する module Ec2Helper def self.included(_klass) ::AWS.config(access_key_id: ENV['AWS_ACCESS_KEY_ID'], secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], max_retries: 8) end def tagged_servers(tag_key, tag_value, default = []) @ec2 ||= ::AWS::EC2.new(ec2_endpoint: 'ec2.ap-northeast-1.amazonaws.com') addresses = @ec2.instances.map do |instance| next if instance.tags[tag_key] != tag_value next if instance.status != :running instance.dns_name || instance.private_dns_name || instance.ip_address end.compact return default if addresses.empty? addresses end def ec2_tag(tag_value, *args) ::AWS.memoize do tagged_servers(fetch(:tag_key), tag_value).each do |host| server(host, *args) end end end end
  24. 24. デプロイ on AWS include Ec2Helper set :tag_key, 'Role' set :tag_value, ENV['EC2_TAG'] || 'app' ec2_tag fetch(:tag_value), user: 'deployer', roles: %w(web app) • Capistrano3 with EC2 tag • EC2のタグを元に、デプロイ先を決定する
  25. 25. Redis • m3.large x 64!!! • なぜこんなことになってしまったのかは後ほど共有し ます
  26. 26. 永続化層:Redis • RDBと同じようにSharding • redis-rbの、Redis::Distributedをシンプルに使 えばOK • コンシステント・ハッシュ法が使われている ので、何も考えなくてもいい感じに分散して くれる(便利)
  27. 27. 第3回 IIJ社における分散DB技術「ddd」(1) http://thinkit.co.jp/article/1030/1/page/0/1 永続化層:Redis • 障害の時に影響範囲が少ない • (ノードが一定以上あって、Key数が膨大でなければ)偏りが少ない
  28. 28. 参考: コスト比率 • コンテンツ配信量の多いゲームは、 CloudFrontの比率が高くなりがち
  29. 29. 負荷テスト
  30. 30. ruby-jmeter • みんなだいすき JMeter の、jmxスクリプトを 簡易に作れるようになる Ruby Gem • 覚えておくと負荷テストがめっちゃ楽になり ます
  31. 31. ruby-jmeter extract_id =<<EOS var json = JSON.parse(prev.getResponseDataAsString()); var id = json['key'][0]['id'] vars.put('id', id); EOS test do threads count: 100 do header({name: "Content-Type", value: "application/json"}) header({name: "X-Platform", value: "android"}) header({name: "X-ClientVersion", value: "1.0.0"}) post name: '/api_with_body', url: "#{protocol}://#{host}:#{port}/api_with_body", raw_body: {"user_account"=>{"some_parameter"=>"SomeValue", "some_parameter2"=>"SomeValue2"}}.to_json do extract name: 'return_value', regex: %q{"value":s?([d]+)} end get name: '/get_api', url: "#{protocol}://#{host}:#{port}/get_api/#{return_value}" post name: "/api/js", url: "#{protocol}://#{host}:#{port}/api/js", raw_body: params.to_json do bsf_postprocessor name: "extract_id", scriptLanguage: 'javascript', script: extract_id end end end.jmx
  32. 32. ruby-jmeter test do threads count: 100 do end end.jmx • スレッド数の指定
  33. 33. ruby-jmeter test do threads count: 100 do header({name: "Content-Type", value: "application/json"}) end end.jmx • リクエストヘッダの指定
  34. 34. ruby-jmeter test do threads count: 100 do post name: '/api_with_body', url: "#{protocol}:// #{host}:#{port}/api_with_body", raw_body: {"user_account"=>{"some_parameter"=>"SomeValue", "some_parameter2"=>"SomeValue2"}}.to_json do end end end.jmx • リクエストBodyの指定
  35. 35. ruby-jmeter test do threads count: 100 do post name: '/api_with_body', url: "#{protocol}:// #{host}:#{port}/api_with_body", raw_body: {"user_account"=>{"some_parameter"=>"SomeValue", "some_parameter2"=>"SomeValue2"}}.to_json do extract name: 'return_value', regex: %q{"value":s?([d]+)} end end end.jmx • レスポンスBodyから、正規表現で値を抽出 to ‘return_value’
  36. 36. ruby-jmeter test do threads count: 100 do get name: '/get_api', url: "#{protocol}://#{host}:#{port}/ get_api/#{return_value}" end end.jmx • ‘return_value’ を使って、GETリクエスト
  37. 37. ruby-jmeter extract_id =<<EOS var json = JSON.parse(prev.getResponseDataAsString()); var id = json['key'][0]['id'] vars.put('id', id); EOS test do threads count: 100 do post name: "/api/js", url: "#{protocol}://#{host}:#{port}/api/js", raw_body: params.to_json do bsf_postprocessor name: "extract_id", scriptLanguage: 'javascript', script: extract_id end end end.jmx • レスポンスBodyから、JavaScriptを使って値を抽出 to ‘extract_id’
  38. 38. 地雷を避ける
  39. 39. 地雷を避ける • 他の人の失敗から学ぶ • 致命的な処理を発見できるようにする
  40. 40. 愚者だけが自分の経験から学ぶと信じている。私はむし ろ、最初から自分の誤りを避けるため、他人の経験から 学ぶのを好む。 愚者は経験に学び、賢者は歴史に学ぶ。
  41. 41. 失敗 • Rails 4.1/Arel 5.0 でコネクション切断時にス キーマキャッシュが使われない • Redis: KEYS pattern
  42. 42. Rails 4.1/Arel 5.0 でコネクション切断 時にスキーマキャッシュが使われない • sonots/activerecord-refresh_connection を利用 • リクエストの度に、SHOW FULL FIELDS FROM ~~ が発行される • User DBは問題ないが、Master DBは死亡 • 当時、原因を潰す時間的余裕がなかった
  43. 43. Rails 4.1/Arel 5.0 でコネクション切断 時にスキーマキャッシュが使われない • 原因: http://so-wh.at/entry/2015/03/15/Rails_4.1/ Arel_5.0%E3%81%A7%E3%82%B3%E3%83%8D%E3%82%AF %E3%82%B7%E3%83%A7%E3%83%B3%E5%88%87%E6%96%AD %E6%99%82%E3%81%AB%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E %E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%8C %E4%BD%BF • 対処 : winebarrel/arel_columns_hash を使う or sonots/activerecord-refresh_connectionを 止める
  44. 44. Redis: KEYS pattern
  45. 45. Redis: KEYS pattern
  46. 46. Redis: KEYS pattern • 全てのサーバにリクエストがいくので、サーバ増やしても 意味ない • そもそもO(N)以上の計算量のコマンドを使ってはいけない • レビュー漏れた & 負荷テストでRedisのデータ量の チェックが漏れていた • 64台まで増加した要因に。本当は8台で十分
  47. 47. もう一つ重要なこと • 最小のコストで最大の利益を得る • パレートの法則 : プログラムの処理にかかる時間の80%は コード全体の20%の部分が占める • 実際は1%に満たない部分が大量リクエストのボトルネッ クになることが多い(大きくなればなるほど、1つのミス が致命的になりやすい) • “致命的な部分”を発見できるシンプルな仕組み作りが重要
  48. 48. 致命的な処理の発見 : まずやること • NewRelic • テスト環境サーバから入れておく • 負荷テスト • 良いツール(例えば ruby-jmeter)を使って作りやすく、メンテしやすくして おく • Railsはクソクエリが生まれやすい。pt-query-digest 等のツールを使い、 負荷テスト環境のクエリを分析しておく • 監視 • CloudWatch Alert : 発見できなかったらダメ、設定しすぎて狼少年になっ てもダメ
  49. 49. NewRelic • 本番環境はもちろんですが、テスト環境に Pro 版 を導入しておくのを推奨 • Liteだと、Database Reportが見れないのが辛い • 一台あたり149$/月を全台導入するのはコスト高 すぎるので、一部のサーバのみ設定しておく
  50. 50. NewRelic • DatabaseReport - Slowest
  51. 51. NewRelic • DatabaseReport - Most time consuming
  52. 52. NewRelic • 以下設定しておいて、環境変数で切り替え • 実運用では、5台適当に選択して有効にしている • デプロイでサーバが変わることがあるが、利用料金は 5台で済む production: <<: *default_settings monitor_mode: <%= ENV['NEWRELIC_MONITOR_MODE'] || "false" %>
  53. 53. • dotenv-rails + Capistrano • role(:app)のサーバを数台選択し、”NEWRELIC_MONITOR_MODE=true” を設定して配布 NewRelic # config/deploy/production.rb set :newrelic_monitor_number, 5 # lib/capistrano/tasks/deploy.rake namespace :deploy do desc 'Upload added NEWRELIC_MONITOR_MODE=true .env file' task :upload_newrelic_env do monitor_true = "NEWRELIC_MONITOR_MODE=truen" temp = Tempfile.new("env") tempfile = temp.path temp.write(File.open(".env", "r").read) temp.write(monitor_true) temp.close on roles(:app).to_ary[0...fetch(:newrelic_monitor_number).to_i] do |host| upload! tempfile, File.join(shared_path, ".env") end FileUtils.rm(tempfile) end end
  54. 54. CloudWatch 設定の一例 対象 メトリック 設定例 ELB RequestCount Sum > 150,000 for 3 minutes HTTPCode_Backend_5XX Sum >= 200 for 2 minutes UnHealthyHostCount Max >= 1 for 3 minutes Latency Ave. > 1 for 2 minutes EC2 CPUUtilization Ave. >= 60 for 3 minutes RDS CPUUtilization Ave. >= 50 for 1 minute ReplicaLag Ave. > 1 for 2 minutes WriteIOPS Ave. >= 2,750 for 1 minute FreeStorageSpace Min < 200,000,000,000 for 1 minute WriteLatency Ave. >= 0.2 for 2 minutes Elaticache CPUUtilization Ave. >= 35 for 3 minutes CurrConnections Ave. > 50,000 for 5 minutes FreeableMemory Ave. < 500,000,000 for 5 minutes Evictions Sum > 1 for 1 minutes • とりあえずこれらのメトリックを設定して、後で追加する
  55. 55. まとめ • スケールアウト戦略 • 設計が重要。例えば、Octopusを後で導入するのは辛いとはいえ、DBの Shardingは普通必要ない。
 一概には言えないが、目安として 4~50,000Req/Min あたりからDB分割を検 討しても良いのでは • 負荷テスト • 計測大事 • 地雷をうまく避ける • 基本的な監視 + Railsのクエリに気をつける
  56. 56. ありがとうございました!

×