Представьте, сервис на Ruby стал медленнее или нестабильнее после релиза. Какие language/runtime-specific причины вы проверите?
Проверяю GC pressure (GC.stat, memory_profiler), утечки через ObjectSpace, thread contention при GIL, отсутствие eager_load в продакшн, сегфолты от обновлённых C-extensions и профилирую через stackprof или rbspy.
Диагностика деградации Ruby-сервиса после релиза
Медленность или нестабильность после деплоя — сигнал для систематической диагностики. Ruby/MRI добавляет специфические причины поверх общих (DB slow queries, network latency).
1. GC и память
Первое подозрение — утечка памяти или избыточное давление на GC. Ruby 3.x использует несколько алгоритмов: mark-and-sweep + incremental + compaction (с 3.1 включён по умолчанию). Симптом — постепенный рост RSS без плато, частые GC-паузы, high latency с пиками.
# Включить GC.stat в middleware/endpoint
GC.stat
# Ключевые поля:
# :heap_live_slots — живые объекты
# :total_allocated_objects — всего создано
# :major_gc_count / :minor_gc_count
# :time — ms потраченных в GC (Ruby 3.1+)Инструменты: gem memory_profiler, derailed_benchmarks (для Rails), stackprof с wall-clock профилированием.
2. ObjectSpace и retained objects
require 'objspace'
ObjectSpace.memsize_of_all # байт всех живых объектов
ObjectSpace.each_object(String).count # количество строк
# В тесте: найти что держит память
require 'memory_profiler'
report = MemoryProfiler.report do
# ваш код
end
report.pretty_print3. GIL и thread contention
MRI имеет GIL (Global Interpreter Lock) — только один поток выполняет Ruby-код одновременно. Если после релиза добавили многопоточность (Puma workers vs threads), высокий thread count при CPU-bound задачах только увеличит contention. Puma рекомендует: threads 5, 5 для IO-bound, меньше для CPU.
# Посмотреть текущие настройки Puma
bundle exec puma --help | grep threads
# Мониторинг через Puma control app
curl http://localhost:9293/stats4. Frozen string literals и аллокации строк
Каждый "string" без frozen_string_literal: true — новый объект в heap. В релизе, где появился новый горячий путь с интенсивным string-building, это может дать заметный регресс. Проверка: ruby --enable-frozen-string-literal как эксперимент.
5. Require и загрузка кода
Если сервис использует code reloading (Zeitwerk в Rails development или неправильно настроенный продакшн), require на каждый запрос убьёт производительность. В Rails: config.eager_load = true в production обязателен.
6. C-extensions и native gems
После обновления gem с C-extension (например, nokogiri, msgpack, bcrypt) возможны memory leak или segfault из нативного кода. Симптом — случайные падения воркеров без Ruby backtrace. Проверить через rbtrace или системные crash-логи.
7. Инструменты профилирования
# stackprof — sampling profiler
gem install stackprof
# В коде:
StackProf.run(mode: :wall, out: 'tmp/stackprof.dump') do
# heavy operation
end
stackprof tmp/stackprof.dump --text --limit 20
# rbspy — attach к живому процессу без изменения кода
rbspy record --pid $(pgrep -f puma) --duration 30Подводные камни
- GC compaction (Ruby 3.1+) может вызвать нестабильность с C-extensions, не поддерживающими compact — проверить через
GC.verify_compaction_references. ObjectSpaceсам по себе замедляет работу — включать только в профилировочном окружении, не в продакшн.- Puma preload_app! + fork сохраняет соединения к БД из мастер-процесса — нужно явно переоткрывать в after_fork.
- YJIT иногда даёт регресс на коротких скриптах или при частом code invalidation (monkey patching в runtime).
- Fiber Scheduler требует, чтобы все IO-операции шли через scheduler-aware gems; один блокирующий вызов ломает всю async-цепочку.
- Символы до Ruby 2.2 не собирались GC — динамически создаваемые символы (
to_sym) в старых версиях вызывали утечку. - Rack middleware с state (не thread-safe) при многопоточном Puma даёт race condition — симптом нестабильности без явных ошибок.
What hurts your answer
- Сразу обвинять Ruby, не проверив соседние слои системы
- Чинить симптом без минимального воспроизведения и evidence
- Не учитывать версии, конфигурацию, окружение и recent changes
What they're listening for
- Умеет локализовать проблему вокруг Ruby
- Двигается от симптома к гипотезам и проверкам
- Отличает баг инструмента от ошибки использования или окружения