レール接続の最適化


これはシリーズで3番目のポストです.
これまでに突然と不可解に陰気な応答時間を提供するレールのアプリを経験したことがありますか完全に応答しない?特に環境の中で何も変わっていないときは、特に信じられないほど混乱することができます.
これが起こる最も一般的な理由の1つを見ましょう.

データストア接続


正しくRails接続を構成することは、複雑でありえます.ここでは、それが間違って取得する方法を簡単に説明短いビデオです.
この問題は、プライマリデータベースには分離されないことに注意してください.限られた接続を持つどんなデータストアも構成することは可能です.Postgres、REDIS、memcachedなど.
考慮するこの問題のいくつかの側面があります.

接続制限


Datastoreはどのように多くのアクティブな接続を合理的にサポートすることが上限を持っています.システム資源は一般にこれらの制限を管理する.例えば、PostgreSQL デフォルトはRedis デフォルトは10000です.デフォルトの接続制限をオーバーライドすることが可能です.ちょうどあなたが何をやっているかを確認してください.
ホスティングプロバイダはさらに制限を課す.毛奥のStandard 0 Postgresは、120の接続で彼らのキャップを計画しますPremium 0 Redis計画キャップ40

接続プール


接続を確立することは、高価で遅い操作です.Connection pooling あらかじめ確立された接続を再利用することによって接続がどれだけ頻繁に設定されているかを制限することによって、全体の待ち時間を減らします.
接続プーリングから得られる改善された効率は、アプリケーションを単独でdatastore接続限界に基づいて可能であるより、より多くの同時リクエストを提供するのを可能にする.プーリングは力の乗数です.

展開トポロジー


展開構成は重要な役割を果たします.40の接続の制限を持つデータベースを考えてください.我々は、20の接続を使用するように構成されている2つのサーバーを持つ容量になります.
展開としてのこの問題化合物は、より複雑になります.そして、チャレンジはサーバの数に制限されません.また、サーバごとに実行するプロセス数と各プロセスでアクティブなスレッド数を説明する必要があります.

変数


Railsアプリケーションでは、次の環境変数を使用します.
  • WEB_CONCURRENCY - プロセス数
  • RAILS_MAX_THREADS - 糸の数
  • これらは記述的な名前ですが、我々はより良い行うことができると思います.RailsアプリケーションがWebリクエストを提供するよりも多くの場合、数式を見る前にいくつかのより適切な名前を考慮しましょう.
  • SERVERS - サーバ/dynosの数
  • PROCESSES - プロセス数
  • THREADS - 糸の数
  • また、データベースにアクセスできる外部システムにも影響を与える必要があります.これは別の変数を考慮に入れます.
  • EXTERNAL_CONNECTIONS
  • それは現在、数式の上にすべての変数です.

    公式


    式は非常に簡単です.
    (SERVERS * PROCESSES * THREADS) + EXTERNAL_CONNECTIONS
    
    標準のRails展開を見てみましょう.
  • 2ウェブサーバ、2つのプロセス、8つの糸
  • 3労働者サーバー、4つのプロセス、16の糸
  • 10外部接続

  • 接続の数の式を示します.
    (2 * 2 * 8) + (3 * 4 * 16) + 10 = 234
    
    あなたが利用できるより多くの接続を構成しないことを確実とすることは、重要です.プロビジョニングハードウェアを増やすか、アプリケーションが使用する接続数を減らす必要があるかもしれません.

    構成


    上記の展開のための柵の設定の例です.
    # config/database.yml
    default: &default
      adapter: postgresql
      encoding: unicode
      pool: <%= ENV["THREADS"] %>
      # it's generally a good idea to configure timeouts
      connect_timeout: 1
      variables:
        statement_timeout: 5s
        lock_timeout: 2s
    
    # config/initializers/sidekiq.rb
    settings = {
      url: ENV["REDIS_QUEUE_URL"], 
      # sidekiq spawns a couple of additional threads,
      # so we need to ensure we're using a smaller pool size 
      size: ENV["THREADS"].to_i - 2, # pool size
      # it's generally a good idea to configure timeouts
      connect_timeout: 0.2,
      read_timeout: 0.5,
      write_timeout: 0.5
    }
    
    Sidekiq.configure_server do |config|
      config.redis = settings
    end
    
    Sidekiq.configure_client do |config|
      config.redis = settings
    end
    
    # config/environments/production.rb
    Rails.application.configure do
      config.cache_store = :redis_cache_store, {
        url: ENV["REDIS_CACHE_URL"],
        size: ENV["THREADS"], # pool size
        # it's generally a good idea to configure timeouts
        connect_timeout: 0.2,
        read_timeout: 0.5,
        write_timeout: 0.5
      }
    end
    
    # config/puma.rb
    workers ENV["PROCESSES"]
    max_threads_count = ENV["THREADS"]
    min_threads_count = ENV["THREADS"]
    threads min_threads_count, max_threads_count
    
    # Procfile
    web: PROCESSES=2 THREADS=8 bin/start-pgbouncer bundle exec puma -C config/puma.rb
    worker: PROCESSES=4 THREADS=16 bin/start-pgbouncer bundle exec sidekiq -C config/sidekiq.yml
    
    公式と構成は単純に見えるかもしれません、しかし、それらは一見複雑です.複雑さは展開としてより明白になります.たとえば、異なる接続制限を持つRedisインスタンスによってバックアップされた様々なサイズの展開を考えます.他の展開トポロジーを考えることができますか?数学はどうやってうまくいくのですか.

    上級オプション


    積層プーリング


    のようなツールで接続プールをスタックすることが可能ですpg_bouncer , さらに、あなたのRailsアプリケーションがデータベース接続を排出しないようにすることができます.Herokuはこれをサポートしますbuildpack .

    Sidekiq群れ


    プロセスとスレッドの数は、ツールのようなツールの使用を決定するのは難しいことができますSidekiq swarm . ちょうど注意を払って、あなたが数が何であるかについてわかっていることを確認してください.