PROJET AUTOBLOG


Planet-Libre

source: Planet-Libre

⇐ retour index

Pierre-Alain Bandinelli : Unicorn vs Thin pour propulser une application Ruby on Rails, stress test pour choisir !

samedi 16 mai 2015 à 19:29

Unicorn et Thin sont deux serveurs pour Ruby assez fréquemment utilisés et cités dans la littérature informatique. A titre d'exemple, Unicorn est utilisé notamment chez Github: une bonne référence ! Thin est souvent cité pour sa robustesse et sa légèreté. Lequel choisir ?

Avant d'envisager le déploiement d'une application en production (prévision de charge : quelques centaines d'utilisateurs simultanément), j'ai souhaité les comparer.

Protocole de test

thin -s 4 -e production -p 8080 start

ou, quand on le lance avec un seu

thin -e production -p 8080 start
unicorn -c config/unicorn.rb -E production -p 8080

avec pour contenu du fichier de config unicorn.rb une configuration très proche de celle proposée par Github sur cet article :

rails_root = "/path/to/app"
rails_env = ENV['RAILS_ENV'] || 'production'

# 4 workers, may be changed to 1 for the tests
worker_processes (rails_env == 'production' ? 4 : 1) 

# Load rails+app into the master before forking workers
# for super-fast worker spawn times
preload_app true

# Restart any workers that haven't responded in 30 seconds
timeout 30

before_fork do |server, worker|
  ##
  # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
  # immediately start loading up a new version of itself (loaded with a new
  # version of our app). When this new Unicorn is completely loaded
  # it will begin spawning workers. The first worker spawned will check to
  # see if an .oldbin pidfile exists. If so, this means we've just booted up
  # a new Unicorn and need to tell the old one that it can now die. To do so
  # we send it a QUIT.
  # Using this method we get 0 downtime deploys.

  old_pid = rails_root + '/tmp/pids/unicorn.pid.oldbin'
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # Unicorn master loads the app then forks off workers - because of the way
  # Unix forking works, we need to make sure we aren't using any of the parent's
  # sockets, e.g. db connection
  ActiveRecord::Base.establish_connection
end

Les résultats

Match 1 : Unicorn vs Thin, avec un seul processus pour chacun

Quand un seul processus (un seul "worker" dans le vocable propre à Unicorn) est activé, l'arrivée ininterrompue de nouveaux utilisateurs charge considérablement l'application...

Voici les résultats du "stress test" : benchmark_1process.png

On observe que :

Match 2 : Unicorn vs Thin, avec 4 processus pour chacun

Nous démarrons cette fois Unicorn avec 4 "workers" et 4 instances de Thin. Afin d'accéder de manière aléatoire aux différentes instances de Thin, nous utilisons cette fois Pound comme "load balancer". Pound a également été utilisé avec Unicorn pour rendre les conditions des tests équivalentes. Pound n'est toutefois "en théorie" pas nécessaire pour Unicorn qui répartit automatiquement les requêtes sur les différents "workers".

Place aux résultats : benchmark-4processus.png

On constate que :

Conclusion

Je suis tenté de préférer Unicorn pour servir un site Ruby on Rails chargé. Cependant, il conviendra d'offrir suffisamment de workers à Unicorn pour qu'il puisse faire le travail correctement !

Quelques enseignements annexes :

L'impact de Pound sur les temps de réponse

Sans Pound, le temps de réponse moyen de Unicorn est de 117 ms. Avec Pound, toute autre chose étant égale par ailleurs, le temps de réponse moyen est de 161 ms. Il semble donc que le passage par Pound augmente le temps moyen de réponse d'environ 40 ms.

La multiplication des workers pour Unicorn : bonne ou mauvaise idée ?

La machine de test dispose de 4 coeurs. J'ai testé 4 workers et 16 workers avec Unicorn. Les résultats sont très similaires : respectivement 131 et 130 secondes pour effectuer le scénario de test complet. Les temps de réponse moyens sont là encore très proches : 113 ms vs 117 ms. Je ne pense pas que les différences constatées soient significatives. Les écart-types sur le temps de réponse diffèrent plus : 216 ms avec 4 workers et 265 ms pour 16 workers... Compte-tenu du nombre de coeurs sur la machine de test (4), l'utilisation de 16 workers ne semble pas permettre des gains substantiels. Un petit excès du nombre de workers par rapport au nombre de coeurs est peut-être à conseiller toutefois.

Gravatar de Pierre-Alain Bandinelli
Original post of Pierre-Alain Bandinelli.Votez pour ce billet sur Planet Libre.