Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fast Rails API

Fast Rails API

History of optimizing Rails API, starting from AR connections pool and ending by using Fragment caching. Also there is list of tools for profiling applications (stackprof, etc.). All techniques are quite famous.

D83b63db79b42978fe35e120550ca180?s=128

Anton Kaliaev

March 03, 2014
Tweet

Transcript

  1. Fast Rails API

  2. z

  3. Max workload - 1500 rps Target response time - 100

    ms
  4. rps 1500 65 What we have What we want

  5. None
  6. Rails 3 Ruby 1.9.3 Unicorn Nginx Postgresql Initial stack

  7. # config/unicorn.rb worker_processes (ENV['UNICORN_WORKERS'] || 1).to_i # config/database.yml development: ...

    pool: 5 Connection pool https://devcenter.heroku.com/articles/concurrency-and-database-connections Unicorn worker Unicorn worker DB AR connection pool 1 connection per worker for max productivity
  8. # config/unicorn.rb before_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end

    end after_fork do |server, worker| if defined?(ActiveRecord::Base) config = Rails.application.config.database_configuration[Rails.env] config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds config['pool'] = ENV['DB_POOL'] || 5 ActiveRecord::Base.establish_connection(config) end end Establish DB connection after fork https://devcenter.heroku.com/articles/concurrency-and-database-connections
  9. # config.ru if ENV['RAILS_ENV'] == 'production' require 'unicorn/worker_killer' max_request_min =

    500 max_request_max = 600 # Max requests per worker use Unicorn::WorkerKiller::MaxRequests, max_request_min, max_request_max oom_min = (240) * (1024**2) oom_max = (260) * (1024**2) # Max memory size (RSS) per worker use Unicorn::WorkerKiller::Oom, oom_min, oom_max end # Gemfile group :production do gem 'unicorn-worker-killer' end Unicorn worker killer
  10. # Gemfile gem 'rails-api' # app/controllers/api/application_controller.rb class Api::ApplicationController < ActionController::API

    Rails-api
  11. # config/application.rb require "active_record/railtie" require "action_mailer/railtie" require "action_controller/railtie" require "action_view/railtie"

    require "rails/test_unit/railtie" require "sprockets/railtie" Do not load ‘rails/all’
  12. # config/application.rb require "active_record/railtie" require "action_mailer/railtie" require "action_controller/railtie" require "action_view/railtie"

    require "rails/test_unit/railtie" require "sprockets/railtie" Do not load ‘rails/all’
  13. Connection pool Rails-api Do not load ‘rails/all’ Establish DB connection

    after fork/ new thread was created Unicorn worker killer Summary REALY HELPFUL HELPFUL IMPROVES STABILITY
  14. rps 1500 66 What we have What we want

  15. Postgresql tuning http://postgresql.leopard.in.ua/html/ http://momjian.us/main/writings/pgsql/hw_performance/ PG Backend PG Backend Shared Buffer

    Cache Write-Ahead Log Query and checkpoint operations Transaction durability Kernel Disk Buffer Cache fsync Disk Blocks
  16. Postgresql tuning http://postgresql.leopard.in.ua/html/ http://momjian.us/main/writings/pgsql/hw_performance/ shared_buffers work_mem maintaince_work_mem checkpoint_segments effective_cache_size CLUSTER

    table
  17. Rails 4 Ruby 2.1.0 Postgresql tuning Summary REALY HELPFUL

  18. rps 1500 70 What we have What we want

  19. Oj (JSON dumper) # Gemfile gem 'oj' json 0.810000 0.020000

    0.830000 ( 0.841307) yajl 0.760000 0.020000 0.780000 ( 0.809903) oj 0.640000 0.010000 0.650000 ( 0.666230) Good idea to use the fastest dumper
  20. Active Model Serializers # Gemfile gem 'active_model_serializers' # app/serializers/user_serializer.rb class

    UserSerializer < ActiveModel::Serializer root false attributes :id, :guid, :current_channel_id, :current_telecast_ts, :created_at, :updated_at end # app/controllers/api/user_controller.rb def show @user = User.find params[:id] render json: @user end
  21. Fragment caching https://devcenter.heroku.com/articles/caching-strategies http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works # app/controllers/api/application_controller.rb class Api::ApplicationController < ActionController::API

    include ActionController::Caching # app/controllers/api/users_controller.rb def show @user = User.find params[:id] json = cache ['v1', @user] do ActiveModel::Serializer.build_json(self, @user, {}).to_json end render json: json end
  22. Fragment caching # app/controllers/api/users_controller.rb def index json = cache ['v1',

    CacheKeyRegistry.users] do @users = User.all ActiveModel::Serializer.build_json(self, @users, {}).to_json end render json: json end # lib/cache_key_registry.rb module CacheKeyRegistry class << self def users; key(User, __method__) end private def key(scope, key_prefix) count = scope.count max_updated_at = scope.maximum(:updated_at).try(:utc).try(:to_s, :number) "#{key_prefix}/all-#{count}-#{max_updated_at}" end end end “users/all-25-2014-02-23”
  23. Fragment caching Active Model Serializers Oj (JSON dumper) Summary REALY

    HELPFUL HELPFUL
  24. rps 1500 1500 What we have What we want

  25. None
  26. Rails 4 Ruby 2.1.0 Unicorn Nginx Postgresql Memcached Current stack

  27. Do not instantiate AR objects Try OOBGC Try different web

    server (e.g. Puma) Other ideas
  28. Do not instantiate AR objects def self.lightning connection.select_all(select([:guid, :current_channel_id, :current_telecast_ts]).arel).each

    do |attrs| attrs.each_key do |attr| attrs[attr] = type_cast_attribute(attr, attrs) end end end
  29. A note about profiling tools

  30. $ stackprof stackprof-cpu-6196-1393432908.dump --text ================================== Mode: cpu(1000) Samples: 545 (0.00%

    miss rate) GC: 48 (8.81%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 42 (7.7%) 39 (7.2%) #<Module:0x000000020550f8>.escape 33 (6.1%) 23 (4.2%) ActiveRecord::ConnectionAdapters::Column.new_time 21 (3.9%) 21 (3.9%) Set#include? 13 (2.4%) 13 (2.4%) block in ActiveSupport::Dependencies#search_for_file 13 (2.4%) 13 (2.4%) block in ActiveSupport::Dependencies#autoloadable_module? 26 (4.8%) 4 (0.7%) Channel::Telecast#cover_url Stackprof
  31. $ stackprof stackprof-cpu-6196-1393432908.dump --text --method 'Channel::Telecast#cover_url' Channel::Telecast#cover_url (/projects/undev/simpletv-backend/app/models/concerns/coverable.rb:12) samples: 4

    self (0.7%) / 26 total (4.8%) callers: 26 ( 100.0%) Channel::TelecastSerializer#cover callees (22 total): 22 ( 100.0%) Channel::Telecast#cover code: | 12 | def cover_url(version = nil) | 13 | if cover.file.nil? && external_cover 26 (4.8%) / 4 (0.7%) | 14 | external_cover.gsub(configus.cdn_host, '/cdn') Stackprof
  32. https://github.com/MiniProfiler/rack-mini-profiler Rack Mini Profiler

  33. Bullet Request log analyzer NewRelic Other

  34. although Rails sucks without cache Don’t know what to do?

  35. http://www.slideshare.net/maxlapshin/rails-26416461

  36. Any other ideas? Questions? Anton Kalyaev twitter.com/AntonKalyaev github.com/akalyaev