Какие production-риски есть у Ruby on Rails: blocking code, connection pooling, config, auth, observability, deploy или graceful shutdown?
Основные production-риски Rails: блокирующий I/O в синхронных контроллерах, исчерпание пула БД, утечки соединений, небезопасные дефолты auth (mass assignment, IDOR), отсутствие structured logging и проблемы с graceful shutdown при деплое.
Production-риски Ruby on Rails
1. Blocking code и модель потоков
Puma использует thread-per-request модель. Один медленный запрос (внешний HTTP, файловая операция) блокирует поток. При RAILS_MAX_THREADS=5 пять одновременных медленных запросов исчерпывают пул.
# Плохо: синхронный HTTP внутри контроллера
def show
result = Net::HTTP.get(URI("https://slow-api.example.com/data"))
render json: result
end
# Хорошо: вынести в фоновый джоб
def show
FetchExternalDataJob.perform_later(current_user.id)
render json: { status: "queued" }
end
2. Connection Pooling
ActiveRecord использует пул соединений из config/database.yml. Значение pool должно соответствовать RAILS_MAX_THREADS. Превышение вызывает ActiveRecord::ConnectionTimeoutError.
# config/database.yml
production:
adapter: postgresql
url: <%= ENV["DATABASE_URL"] %>
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
checkout_timeout: 5
idle_timeout: 300
connect_timeout: 5
При Sidekiq (concurrency: 25) пул должен быть не меньше 25 + запас для web.
3. Конфигурация и секреты
Не передавайте секреты через config/credentials.yml.enc без rotation-процесса. Используйте переменные окружения или Vault:
# Проверить что секреты не попали в git
git log --all --full-history -- config/master.key
# Ротация ключа
RAILS_ENV=production rails credentials:diff
4. Безопасность аутентификации и авторизации
- Mass assignment: без строгого
params.require(:user).permit(:name, :email)возможна инъекцияadmin: true. - IDOR:
Post.find(params[:id])без проверки владельца даёт доступ к чужим данным. Всегда используйтеcurrent_user.posts.find(params[:id]). - Session fixation: вызывайте
reset_sessionпосле логина.
# Безопасный паттерн
def update
@post = current_user.posts.find(params[:id]) # scope к пользователю
@post.update!(post_params)
end
private
def post_params
params.require(:post).permit(:title, :body) # whitelist
end
5. Observability
Rails по умолчанию пишет plain-text лог. В production нужен structured JSON для Datadog/Loki:
# config/environments/production.rb
config.log_formatter = proc do |severity, datetime, progname, msg|
{ level: severity, time: datetime, msg: msg }.to_json + "\n"
end
# Или через гем lograge
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.custom_options = ->(event) {
{ user_id: event.payload[:user_id], request_id: event.payload[:request_id] }
}
6. Graceful Shutdown и деплой
Puma поддерживает graceful shutdown через SIGTERM — дожидается завершения in-flight запросов. Kubernetes должен отправлять SIGTERM и ждать terminationGracePeriodSeconds.
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
on_worker_shutdown do
ActiveRecord::Base.connection_pool.disconnect!
end
Подводные камни
- После hot restart Puma старые воркеры доживают запросы, но новые уже обрабатывают трафик — миграции с удалением колонок должны быть в отдельном деплое.
ActiveRecord::Base.connectionв потоке, не управляемом Rails, не возвращает соединение в пул — всегда оборачивайте вwith_connection.- Devise по умолчанию хранит
remember_meтокен в виде SHA1 — обновляйтеstretchesи используйте Devise 4.9+. - Rack::Attack без лимитов на
/api/v1/sessionsпозволяет брутфорс паролей — добавьте throttle по IP и email. - Rails.logger.info в hot path создаёт много строк и аллоцирует память — используйте уровень
:warnв production для не-критичных событий. - Отсутствие health check endpoint с проверкой БД и Redis вызывает проблемы при rolling deploy — Kubernetes считает pod healthy пока не настроен
readinessProbe. - N+1 в GraphQL или serializer незаметен в dev, но на production при 1000 записях убивает БД — используйте Bullet в staging и query count assertions в тестах.
- Sidekiq без Redis Sentinel/Cluster — единая точка отказа: падение Redis останавливает всю обработку джобов.
What hurts your answer
- Говорить только о запуске Ruby on Rails, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Ruby on Rails
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию