RubySeniorExperience

Представьте, сервис на 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_print

3. 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/stats

4. 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
  • Двигается от симптома к гипотезам и проверкам
  • Отличает баг инструмента от ошибки использования или окружения

Related topics