Как сделать так, чтобы Rails масштабировалась до миллионов запросов в минуту.

Ruby on Rails - это великолепный фреймворк, когда вам нужна высокая скорость разработки для вашего проекта или стартапа. Он полезен прямо из коробки и содержит множество закулисных волшебств, облегчающих вашу жизнь. Однако с точки зрения производительности это не самый быстрый фреймворк. Вы найдете примеры того, как отдельные лица и компании отходят от Rails в пользу чего-то другого. Несмотря на это, есть много компаний, которые преуспели в масштабировании Rails и добились успеха - просто взгляните на Airbnb, Github, Gitlab и Shopify.

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

Оглавление

  1. Общие советы
  2. Активная запись (база данных)
  3. Серверы и оборудование
  4. Вишня сверху (Разное)
  5. "Передовой"

Общие советы

Во-первых, есть несколько общих советов, которые следует реализовать в вашем проекте Rails, чтобы настроить себя на успех.

Настроить APM

Вы не сможете повысить эффективность, если не измеряете ее сначала, поэтому необходимо отслеживать и контролировать правильные показатели. Помимо прочего, вы должны отслеживать время загрузки, время запросов и время запросов к базе данных. Лично я считаю New Relic одним из лучших инструментов APM для Rails, но это немного дороговато. Более доступная альтернатива - бесплатная пробная версия SkyLight.

Оставайтесь в курсе

Я лично знаю, насколько ужасным может быть обновление версии Rails для вашего проекта, если вы слишком долго оставляете его устаревшим, и я сочувствую всем, кому пришлось пройти через это испытание. Так что сделайте себе одолжение и постарайтесь синхронизироваться с новыми версиями Ruby и Rails. Это поможет вам избавиться от проблем, связанных с перепрыгиванием нескольких версий, и гарантирует, что у вас будут все новые улучшения производительности.

Принцип Парето (правило 80/20)

Когда дело доходит до разработки программного обеспечения, это хорошо известное правило - принцип Парето. Он также известен как «закон немногих важных» и гласит, что для большинства событий 80% последствий вызваны 20% причин. Идея состоит в том, чтобы не тратить время на микрооптимизации, пока у вас есть более серьезные проблемы, которые необходимо решить. Вы не добьетесь многого, сэкономив миллисекунды на сериализации, если ваши запросы к базе данных будут чрезвычайно медленными. Так что тщательно выбирайте битвы.

Держи это бережливым

Rails поддерживается замечательным сообществом и библиотекой драгоценных камней, которые могут легко помочь вам в выполнении сложных задач. Но легко увлечься добавлением драгоценных камней в свой проект, что приведет к раздуванию. Будьте осторожны при выборе драгоценных камней, которые нужно добавить в свой проект, и постарайтесь сохранить свои зависимости минимальными.

Будь ленив - сделай это в фоновом режиме

Всякий раз, когда вам нужно сделать что-то сложное или долгое, подумайте о том, чтобы добавить его в фоновый рабочий процесс - отправить электронное письмо, push-уведомления, загрузить изображения и тому подобное. Сведение к минимуму работы в основном потоке гарантирует мгновенный отклик для пользователей. Для нас хорошо то, что в Rails есть несколько вариантов, позволяющих легко добиться этого - Sidekiq, Rescue или ActiveJob.

Измените сериализатор

Если у вас есть API в вашем проекте, вы, скорее всего, используете гем ActiveModelSerializers для сериализации ваших данных. Я настоятельно рекомендую перейти на fast_jsonapi от Netflix. Этот драгоценный камень на порядок 25 times быстрее, чем стандартные ActiveModelSerializers, и я могу лично поручиться за это по своему опыту.

Кэшировать меня снаружи

Иногда, если у вас много статических данных или вы не можете ускорить работу, другой альтернативой является использование кеша. Rails делает это невероятно легко прямо из коробки. Вот пример того, как легко сделать кеш со сроком действия:

# Lets say you have some categories you offer in your project.
Rails.cache.fetch("categories", expires_in: 5.minutes) do 
   Categories.all.map(&:name)
end

Active Record (база данных)

ActiveRecord - это волшебная ORM (одна из лучших когда-либо существовавших), предлагаемая Rails. Легко увлечься простотой использования, не понимая деталей, которые могут привести к возникновению узких мест в будущем.

Используйте свою базу данных

Это то, что остается незамеченным или нереализованным для многих разработчиков, поскольку они спешат делать все в коде или, может быть, их запугивают написанием необработанного SQL. Но когда это возможно или удобно, вы должны использовать свою базу данных. Обработка и сортировка структур данных в Ruby может отнимать процессорное время, в то время как ваша база данных может делать это без особых усилий.

Заглянуть за занавес

Не восхищайтесь магией активной записи, не беспокоясь о том, что происходит за кулисами. Чтобы устранить узкие места в производительности, вам необходимо посмотреть на фактические запросы, которые запускаются, и понять их. В среде разработки Rails распечатает все выполняемые запросы, что позволит вам заметить любые ненужные запросы. Кроме того, вот несколько уловок, которые помогут вам копнуть глубже:

Мастер SQL

Не волнуйтесь, я не собираюсь предлагать вам писать все в виде сырых SQL-запросов. Но изучение основ SQL и проектирования баз данных поможет вам лучше понять, что происходит под прикрытием, и позволит вам оптимизировать запросы по мере необходимости. Это ценный навык разработчика программного обеспечения, который будет иметь большое значение в вашей карьере.

Быть скупым

Один из способов сделать ваши запросы более эффективными - это выбрать только то, что вам действительно нужно. Вместо SELECT * укажите, какие столбцы нужно извлекать из базы данных. По умолчанию ActiveRecord выбирает все, но вы можете использовать select или pluck для решения этой проблемы.

N + 1 запросов

Это классическая проблема. Если вы загружаете Blog из базы данных, а затем пытаетесь найти все комментарии для этого блога, просматривая записи в цикле, вы заставляете Rails выполнять запрос для каждого из комментариев. Этого можно избежать, предварительно загрузив комментарии Blog.includes(:comments), и это поможет избежать проблемы с запросом N + 1. Совет для профессионалов: взгляните на гем Bullet, который поможет вам найти любые проблемы с запросами N + 1.

Объединение запросов

При работе с более крупными командами с несколькими разработчиками над проектом иногда вы можете заметить, что каждый человек, касающийся кодовой базы, может добавлять запросы по всему пути кода. Чаще всего эти запросы можно объединить в меньшее количество запросов в верхней части пути кода. Это гарантирует отсутствие повторяющихся запросов и позволяет базе данных стратегически выполнять тяжелую работу.

Индекс, Индекс, Индекс

Это передовой метод работы с базами данных, который нельзя игнорировать. Если вы не выполните индексацию должным образом, это отрицательно скажется на производительности базы данных и вызовет ненужное сканирование таблиц. При создании новой функции заранее продумайте, что будет запрашиваться в проекте, и попытайтесь добавить правильные индексы. Если у вас есть существующий проект, вы всегда можете использовать Active Record Doctor или LolDBA для поиска недостающих индексов.

Миграции

Когда вы работаете в масштабе и у вас есть миллионы записей в ваших таблицах, обычный способ выполнения миграции в Rails, к сожалению, вас не подведет. Они будут либо выводить ошибки, либо блокировать таблицы на длительный период времени, что приведет к остановке вашего сайта. Имея дело с этим в прошлом, есть два инструмента, которые решат эту проблему за вас:

  • Gh-ost: Это решение для онлайн-миграции схемы без триггера для MySQL и единственный инструмент, который работал у меня в большом масштабе.
  • LHM: при этом ваши таблицы будут перенесены, пока они находятся в оперативном режиме, без блокировки таблицы.

Экстремальный совет: используйте необработанный SQL, если осмелитесь

Если есть конечная точка, где вам отчаянно нужна производительность, вы можете рассмотреть возможность объединения всех запросов Active Record по всей вашей функциональности в один массивный SQL-запрос вверху, чтобы вытащить все записи, необходимые для выполнения варианта использования. Заявление об ограничении ответственности: Да, сложно поддерживать массивный необработанный SQL-запрос, к тому же он труднее читается. Но я упомянул, что это только в случае отчаяния.

Серверы и оборудование

При развертывании проекта Rails следует помнить несколько вещей относительно базовой инфраструктуры и архитектуры.

Выберите масштабируемую архитектуру

Поскольку облачная инфраструктура прошла такой долгий путь, ушли в прошлое те времена, когда приходилось строить голые железные серверы для масштабирования вашего приложения. При выборе базовой архитектуры для проекта Rails вы должны использовать масштабируемую облачную систему. Например, вы можете использовать AWS Fargate или Kubernetes для автоматического масштабирования докеризованного приложения Rails по мере необходимости.

Бротли Сжатие

Brotli - еще один алгоритм сжатия, основанный на gzip, с множеством улучшений и улучшенной степенью сжатия. Теперь он поддерживается большинством веб-серверов, и добавление его - простой способ оптимизировать скорость сжатия. И хорошо, кто не хочет бесплатного повышения производительности в Интернете. (Ссылка: Brotli vs Gzip)

Дайте ему больше сока

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

Тюнинг

Начиная с Rails 5 веб-сервер для Rails был переключен на Puma, и по умолчанию он будет запускать только одного воркера. Как только вы настроите развертывание Rails, убедитесь, что количество рабочих процессов увеличилось до количества доступных ядер на вашей машине или чего-то более разумного.

HTTP / 2

Если вы используете обратный прокси-сервер, такой как Nginx, убедитесь, что у вас включен параметр HTTP / 2, чтобы получить все преимущества производительности, которые вы получаете от него.

Вишня на вершине (Разное)

Мы почти на финише - этот раздел посвящен некоторым дополнительным советам по повышению производительности.

Не используйте динамические методы

Магия Rails имеет свою цену. Некоторые из этих методов потребляют много ресурсов, и их лучше не использовать. Например, find_by() и find_all_by() работают медленно, потому что им нужно пройти через method_missing и проанализировать имя по списку столбцов в БД.

Дросселирование

По мере масштабирования вы неизбежно столкнетесь с злонамеренными попытками на различных конечных точках, которые невозможно кэшировать, что приведет к расходу ресурсов на дорогостоящие операции. Чтобы обойти это, я бы порекомендовал добавить такой драгоценный камень, как rack-attack, чтобы реализовать регулирование конечных точек, таких как вход в систему, сброс пароля или регистрация.

Знай свой O (n) против O (1)

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

Всегда используйте CDN

Все ваши статические ресурсы всегда должны выходить на CDN, и убедитесь, что политики кеширования разумны. Если вам нужен детальный контроль над аннулированием кеша, вы можете изучить использование электронных тегов и Cache-Control.

Передовой

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

Расчесать на C

Основная реализация Ruby написана на C, что позволяет вам переписать медленную часть вашего кода на C в качестве альтернативы - например, если вы используете криптоалгоритмы для генерации сертификатов. Если вам не нравится идея погружаться в код C, вы можете использовать сторонние гемы, написанные на C сообществом Rails.

Более быстрые фоновые задания

С увеличением фоновой нагрузки вы можете переключить фоновых рабочих на более производительный язык. Используя очереди, такие как Redis или Amazon SQS, рабочие могут быть разделены на свои собственные микросервисы. Ознакомьтесь с этой совместимой с Sidekiq библиотекой go-worker или версией sidekick, написанной с помощью Crystal в качестве двух вариантов.

Попробуйте более быстрые версии Ruby

Есть несколько реализаций Ruby, направленных на повышение производительности. Если вас интересуют эти вариации, взгляните на Truffle-Ruby или JRub y.

Суть

Если мы посмотрим на компании, использующие Rails, мы увидим, что они смогли использовать потрясающую скорость разработки Rails, чтобы поставить своих клиентов на первое место, а также смогли повысить производительность по мере масштабирования. В этой статье мы обсудили несколько советов и приемов повышения производительности. Надеюсь, вы нашли это руководство полезным. До следующего раза.

Любите говорить о технологиях или стартапах?

Тебе повезло, мне тоже! Если вы хотите поговорить о передовых технологиях, предпринимательстве или опасностях основателя стартапа, найдите меня в Twitter или LinkedIn.