Uma em particular merece atenção especial porque a maioria sempre esquece disso. O Router do Heroku evoluiu bastante desde o começo e também desde a controvérsia da RapGenius que estourou no começo de 2013. Mas o artigo não é sobre isso. Alguns já devem ter tentado navegar numa aplicação pesada que colocou no Heroku e receber uma página com um erro genérico roxa do próprio Heroku e não saber o que é. Ou, se você investiu pesado em marketing e começou a receber toneladas de acesso (dezenas ou centenas de requests por minuto), ver seus dynos patinando sem saber porque.
Então aqui vai a dica. Use a gem rack-timeout e configure um timeout baixo, igual ou menor que 15 segundos (que, convenhamos, se uma request demora 15 segundos pra ser processada é porque ela é extremamente mal feita. Culpe seu código antes de culpar o Rails, o Heroku ou qualquer outra coisa).
Para instalar é muito fácil. Adicione à sua Gemfile:
1 |
gem 'rack-timeout'
|
Rode bundle pra instalar e crie o arquivo config/initializers/rack_timeout.rb com o seguinte:
1 2 3 |
if defined?(Rack::Timeout) Rack::Timeout.timeout = Integer( ENV['RACK_TIMEOUT'] || 12 ) end |
Finalmente, configure sua aplicação no Heroku com o timeout que você quer, por exemplo:
1 |
heroku config:set RACK_TIMEOUT=10 |
E se você usa Unicorn, configure seu config/unicorn.rb com o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Based on https://gist.github.com/1401792 worker_processes 2 timeout 25 preload_app true before_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! Rails.logger.info('Disconnected from ActiveRecord') end end after_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection Rails.logger.info('Connected to ActiveRecord') end end |
Especial atenção à configuração de timeout. O timeout do Router do Heroku, por padrão, será sempre 30 segundos. Configure o timeout do Unicorn pra ser menor que 30 segundos (como no exemplo, 25 segundos), e configure o Rack Timeout pra ser menor ainda do que isso (no exemplo, 10 segundos).
Leia o README do Rack Timeout para mais detalhes, mas feito isso, se algum processamento levar mais do que 10 segundos para finalizar, ele vai estourar uma exception. E é isso que queremos, caso contrário o Router do Heroku vai cortar após os 30 segundos e não vai lhe dizer onde foi o problema, o gargalo que levou a dar um timeout acima dos 30 segundos.
Agora, você precisa de uma forma de ver o stacktrace gerado e para isso use uma opção simples como Exception Notification que você configura facilmente primeiro adicionando uma opção de envio de email à sua aplicação no Heroku como Sendgrid ou Mailgun, e então adiciona ao seu arquivo config/environments/production.rb algo como:
1 2 3 4 5 6 |
config.middleware.use ExceptionNotification::Rack, :email => { :email_prefix => "[Exception - MyApp] ", :sender_address => %{"no-reply" <no-reply@myapp.com.br>}, :exception_recipients => [ENV['EXCEPTION_NOTIFICATION_EMAIL']] } |
Ou então adiciona algo mais parrudo como Honeybadger. O importante é você receber esse stacktrace. Com essa informação você pode otimizar sua aplicação. Talvez seja o caso de otimizar uma query muito lenta, talvez faltem índices nas suas tabelas (veja a gem Bullet), talvez seja uma questão de adicionar Caching, talvez você devesse mover um processo demorado como uma tarefa assíncrona via Sidekiq. Enfim, existem diversas opções para melhorar o tempo de uma requisição para que ela fique dentro do que eu considero como "bom" (abaixo de 1 segundo) ou "ótimo" (abaixo de 100ms).
E falando em otimização de requests, outra excelente opção ao Unicorn para colocar no Heroku é o Phusion Passenger que, com o novo garbage collector e a implementação de Out-of-Band Garbage Collector, pode diminuir dramaticamente o tempo das suas requests.
O importante deste artigo: você precisa de informação antes de saber como agir. O timeout default do Router do Heroku não vai lhe dizer, mas o Rack Timeout pode ser o que falta para descobrir seu gargalo, então configure o quanto antes.