Нам нужны твои мозги

Хотите расти как разработчик и найти крутую работу? Не протирайте штаны — займитесь Open Source проектами. Так легче всего попасть в лучшие команды разработчиков и положить себе в резюме настоящий проект, вместо нелепых «примеров кода». Но найти подходящий проект для участия сложно. Начинаются лень и отговорки, а за ними — отсутствие профессионального роста, критики по-настоящему крутых программистов, уныние и застой.

На Cult of Martians мы собираем интересные задачи для современных веб-программистов. Можно выбрать подходящую по сложности, продолжительности и специализации. Задачи не выдуманы «из воздуха» — каждая решает насущную проблему, и решить ее можно через создание нового Open Source проекта или улучшение существующего. Решайте задачи, прокачивайтесь, присылайте решение на оценку. Лучших могут пригласить к себе на работу компании, программистам которых понравится ваше решение.

Бэк: Создать Ruby gem для обнаружения потенциальных распуханий памяти

Для уверенных в себе, задача на неделю

Необходимо написать инструмент для Ruby-стека, который будет обнаруживать участки кода, способные вызвать memory bloat.

Польза: разобраться с тем, как Rails работает с I/O; написать инструмент, которым будет активно пользоваться Ruby-сообщество.

Постановка задачи

Memory Bloat (или «распухание памяти») возникает, когда приложению требуется одновременно загрузить большое количество данных в оперативную память. Это опасно, так как виртуальная машина Ruby очень неохотно возвращает запрошенную память системе, а для эксплуатации приложения требуется больше ресурсов. Многие APM умеют обнаруживать уже случившиеся распухания памяти, но хотелось бы иметь возможность обнаруживать потенциальные узкие места заранее.

Чаще всего распухание возникает сразу после получения данных из некоторого источника, и решением проблемы является уменьшение объема читаемых данных. Допустим, нам требуется посчитать сумму неких транзакций за месяц:

class ReportController < ApplicationController
  def monthly_transaction_sum
    sum = Transactions.where(date: Date.current.all_month).sum(&:amount)
    render json: { sum: sum }
  end
end

Мы загрузили все транзакции за месяц (а их может быть много!), но в ответе пользователю отправили всего одно число. Если транзакция всего одна — ничего страшного, но что произойдет, если их несколько тысяч?

В качестве решения предлагается использовать хитрую метрику I/O to response payload ratio, которая вычисляется как отношение объема данных, полученных из I/O (БД/Redis/файла/HTTP/…) в процессе обработки HTTP–запроса клиента к объему итогового HTTP–ответа. В результате каждый из экшенов должен быть обернут в некий блок, собирающий информацию об обращении к I/O и формировании HTTP–ответа:

class ReportController < ApplicationController
  include IoToResponsePayloadRatio::Controller

  def monthly_transaction_sum
    sum = Transactions.where(date: Date.current.all_month).sum(&:amount)
    render json: { sum: sum }
  end
end

Затем, в зависимости от настроек, будет происходить либо журналирование, либо публикация события в ActiveSupport::Notifications:

IoToResponsePayloadRatio.configure do |config|
  config.publish = :logs
  # или
  config.publish = :notifications
end

В случае журналирования можно будет увидеть подобную запись:

Completed 200 OK in 1232ms (ActiveRecord: 400ms, DB Payload: 10kB, Body: 1kB, Allocations: 213223)

Критический уровень определяется настроенным значением порога срабатывания warn_threshold:

IoToResponsePayloadRatio.configure do |config|
  config.warn_threshold = 0.8
end

При снижении ниже порога нужно использовать WARN с подробным описанием ситуации; для начала ограничимся следующим:

I/O to response payload ratio is 0.4, while threshold is 0.8

В идеальном случае ratio должен быть близок к единице 🙂

Для первой версии ограничимся только подсчетом объема данных, полученных из БД, но стоит заложить в архитектуру гема возможность учитывать и другие источники данных — Redis, HTTP–запросы, и так далее.

Советы по реализации

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

Вот так Rails пишет в логи.

Инструкции по выполнению

  1. (Опционально) Придумать имя покороче 🙂
  2. Форкнуть проект io_to_response_payload_ratio на GitHub.
  3. Реализовать необходимый функционал.
  4. Сделать PR.