Антон Рябов bio photo

Антон Рябов

Не люблю бриться и у меня умный взгляд.

Email Twitter Github RSS

От переводчика: Оригинал статьи за авторством Paweł Urbanek

Обзор

В теории, вы можете запустить процессы Rails (веб приложение) и Sidekiq (отложенные задачи) на одном инстансе Heroku с 512мб памяти. Для хобби проектов с небольшим количеством трафика это сохранит 7 баксов в месяц. Однако, если пытаться запихнуть два Ruby процесса на один инстанс вы можете столкнуться с проблемой нехватки памяти. В этой статье, я объясню как вы можете ограничить количество памяти используемое Rails приложением.

Недавно, я прочитал замечательную статью от Bilal Budhani, которая объясняет как запустить Sidekiq процесс вместе с Puma на Heroku инстансе. Применив это на одном из своих хобби проектов, я столкнулся с этой ужасной ошибкой R14.

Изображение из оригинальной статьи с сайта pawelurbanek.com Использование памяти неравномерное, сопровождается ошибками и рестартами

Я начал копать и, после применения пары подходов по оптимизации использования памяти, график стал выглядеть так:

Изображение из оригинальной статьи с сайта pawelurbanek.com Равномерное использование памяти, сопровождается только GC

И вот, что я сделал:

Посади Gemfile на диету

Используя специальный гем для этого… В Ruby мире, оборачивать код в гем, это одно из “простейших” решений. При этом, помимо других side эффектов, легко допустить утечку памяти.

Лучший способ проверить как много памяти потребляет каждый из ваших гемов это использовать Derailed тесты (набор штук для тестирования Ruby On Rails приложений). Чтобы использовать их, просто добавьте:

gem'derailed_benchmarks', group: :development

в ваш Gemfile и запустите:

bundle exec derailed bundle:mem

Мой проект использует ботов Twitter и Facebook. Я был удивлен, что популярный гем использует 13 MB памяти на старте. Для начала, я заменил его на легковесную альтернативу grackle (~1 MB), а в итоге написал собственную реализацию HTTP запросов в Twitter API. Таким же образом я избавился от гема koala (~2 MB).

Еще одной быстрой победой было заменить гем gon (~6 MB) на кастомные JavaScript дата атрибуты.

Импорт нескольких мегабайт файлов гемов, чтобы упростить один HTTP вызов или нежелание написать пару строк JavaScript кода, определенно можно легко избежать.

Используй jemalloc

jemalloc это альтернативный аллокатор памяти для Ruby. На heroku можно добавить jemalloc с помощью buildpack’а. Для моего приложение такое изменение снизило использование памяти примерно на ~20%. Только обязательно протестируйте работу вашего приложения с jemalloc на staging, перед тем как деплоить в production.

Ограничь конкурентность и количество воркеров

Скорее всего для хобби проекта с небольшим трафиком вам не нужна большая пропускная способность. Можно снизить использование памяти, уменьшением числа Sidekiq и Puma воркеров и тредов. Вот мой файл config/puma.rb:

threads_count = 1
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "production" }
workers 1

preload_app!

on_worker_boot do
  @sidekiq_pid ||= spawn('bundle exec sidekiq -t 1')
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

on_restart do
  Sidekiq.redis.shutdown { |conn| conn.close }
end

plugin :tmp_restart

и config/sidekiq.yml:

---
:concurrency: 1
:queues:
  - default
  - [critical, 100]

Несмотря на то, что мы указали максимальное количество тредов 1, Puma может создать до 7 тредов. С такой минимальной настройкой, Smart Wishlist все еще способен обработать около 100К Sidekiq джоб в день и сервить React фронтенд и мобильное JSON API одновременно.

Оптимизируй парсинг JSON

Все эти Sidekiq задачи нужно чтобы скачивать актуальные цены из iTunes API (оптимизация запросов в моем TODO листе) и отправлять push нотификации о скидках. Это значит, что в приложении много парсинга JSON. И у меня есть однострочный фикс, который оптимизирует как использование памяти так и производительность:

gem 'yajl-ruby', require: 'yajl/json_gem'

yaji-ruby предлагает API совместимый со встроенным json, он вклинивается в вызовы JSON.parse, улучшая их производительность и использование памяти.

Заключение

Работа в условиях ограничений со стороны инфраструктуры это отличный способ прокачать ваши мускулы программиста и изучить техники оптимизации. В теории, вы всегда можете решить проблемы с памятью вбрасыванием большего количества денег на сервера, но почему бы не попытаться сохранить эти доллары?

#Heroku #RubyOnRails