<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Огненная чаша</title>
    <description>Блог Олега Дашевского
</description>
    <link>http://be9.ru/</link>
    <atom:link href="http://be9.ru/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 14 Jan 2016 09:57:23 +0000</pubDate>
    <lastBuildDate>Thu, 14 Jan 2016 09:57:23 +0000</lastBuildDate>
    <generator>Jekyll v2.4.0</generator>
    
      <item>
        <title>Божественное и человеческое</title>
        <description>&lt;p&gt;Пусть кто-нибудь посмеется, но я реально ощущаю божественную красоту в лисповских
скобках и &lt;a href=&quot;https://en.wikipedia.org/wiki/Homoiconicity&quot;&gt;гомоиконности&lt;/a&gt;. До этого,
давно, у меня было такое переживание в связи с &lt;a href=&quot;https://www.ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt;.
Но больше ни с какими другими языками.&lt;/p&gt;

&lt;p&gt;Вот картинка про гомоиконность
&lt;a href=&quot;http://theburningmonk.com/2015/05/understanding-homoiconicity-through-clojure-macros/&quot;&gt;отсюда&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/divine-and-human/clojure_homoiconicity.png&quot; alt=&quot;Гомоиконность&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Для меня такие переживания чрезвычайно важны, потому что это &lt;em&gt;вход&lt;/em&gt; в соответствующее
пространство. Соприкоснувшись с этим, переходишь к делу:
дальнейшему изучению языка, практическому применению и т.д. Но там, где-то
глубоко внутри, эта штука сидит, и греет, и питает тебя.&lt;/p&gt;

&lt;p&gt;Успех языка и соответствующих технологий, однако, отнюдь не гарантируется проявлениями
божественной красоты. Чтобы Ruby взлетел, потребовался фреймворк
&lt;a href=&quot;http://www.rubyonrails.ru/&quot;&gt;Ruby on Rails&lt;/a&gt;, появившийся чуть больше десяти лет спустя.
Он не вызывает у меня ощущения божественности,
но он невероятно ладно скроен и оказался чрезвычайно удобным для огромного
количества людей. Поэтому теперь и Ruby популярен. Заметьте: Ruby on Rails, а
не Rails on Ruby!&lt;/p&gt;

&lt;p&gt;Что касается Лиспа, то ему свежее дыхание придал язык &lt;a href=&quot;http://clojure.org&quot;&gt;Clojure&lt;/a&gt;.
Его автор Rich Hickey ловко освежил классический синтаксис новыми скобками &lt;code&gt;[]&lt;/code&gt; и &lt;code&gt;{}&lt;/code&gt;,
протащил сквозь весь язык концепцию неизменяемых данных, захватив и параллелизм,
а в качестве опоры взял инфраструктуру Java. Получилась невероятно мощная комбинация
— с появлением же &lt;a href=&quot;http://clojure.org/clojurescript&quot;&gt;ClojureScript&lt;/a&gt; силы ещё прибавилось.&lt;/p&gt;

&lt;p&gt;Вывод, в общем-то, прост. Божественная красота и проявленное человеком искусство по её воплощению
&lt;em&gt;(искусство, искусный, буквально: из кусков)&lt;/em&gt; дают невероятный результат. Важны обе составляющие.
Примеров только с одной составляющей вокруг много, но смотреть на них не хочется.&lt;/p&gt;
</description>
        <pubDate>Thu, 14 Jan 2016 14:51:00 +0000</pubDate>
        <link>http://be9.ru/2016/01/14/divine-and-human.html</link>
        <guid isPermaLink="true">http://be9.ru/2016/01/14/divine-and-human.html</guid>
        
        
      </item>
    
      <item>
        <title>Деплой на Heroku</title>
        <description>&lt;h3 id=&quot;section&quot;&gt;Пролог&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;11:34:13. &lt;strong&gt;CTO:&lt;/strong&gt; Ты деплоишь?&lt;/p&gt;

  &lt;p&gt;11:34:37. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; Деплою.&lt;/p&gt;

  &lt;p&gt;11:36:15. &lt;strong&gt;CTO:&lt;/strong&gt; Деплоится?&lt;/p&gt;

  &lt;p&gt;11:36:21. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; Ага.&lt;/p&gt;

  &lt;p&gt;11:37:13. &lt;strong&gt;CTO:&lt;/strong&gt; Задеплоилось?&lt;/p&gt;

  &lt;p&gt;11:37:16. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; Погоди, уже рестартится.&lt;/p&gt;

  &lt;p&gt;11:37:22. &lt;strong&gt;CTO:&lt;/strong&gt; …Зарестартилось?&lt;/p&gt;

  &lt;p&gt;11:37:25. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; Да. Можешь сказать Максу, пусть посмотрит.&lt;/p&gt;

  &lt;p&gt;11:40:01. &lt;strong&gt;CTO:&lt;/strong&gt; Макс говорит, что ничего не поменялось. Ты точно задеплоил?&lt;/p&gt;

  &lt;p&gt;11:40:48. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; Ну точно я задеплоил, блин. У меня работает. Хотя погоди-ка, что за…&lt;/p&gt;

  &lt;p&gt;11:41:03. &lt;strong&gt;Разработчик 2:&lt;/strong&gt; О, ребята, вы тут деплоите что ли? А я свою ветку поставил деплоиться.&lt;/p&gt;

  &lt;p&gt;11:41:09. &lt;strong&gt;Разработчик 1:&lt;/strong&gt; …!&lt;/p&gt;

  &lt;p&gt;11:41:14. &lt;strong&gt;CTO:&lt;/strong&gt; …!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Это не придуманный диалог. Примерно такой чат состоялся у нас в Campfire в 2011
году.&lt;/p&gt;

&lt;p&gt;Что тут скажешь? Процесс, затрагивающий многих, должен быть наглядным для всех!
Именно поэтому в аэропортах рейсы показываются на больших заметных табло,
а не только в маленьком терминальном окне авиадиспетчера.&lt;/p&gt;

&lt;p&gt;Если мы уже собрались в этом чате, так давайте здесь и деплоить!
И пусть все стадии и результат будут видны здесь же. Дальнейшая история расскажет,
как я реализовал такую схему.&lt;/p&gt;

&lt;p&gt;Вот как это работает в Slack-чате &lt;a href=&quot;https://www.shuttlerock.com&quot;&gt;Shuttlerock&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/slack_rodney_deploy.png&quot; alt=&quot;Пример деплоя&quot; width=&quot;771&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Возможно, моё решение окажется полезным и для вас!&lt;/p&gt;

&lt;h3 id=&quot;heroku&quot;&gt;Heroku&lt;/h3&gt;

&lt;p&gt;В проектах мы обычно используем Heroku. Как и всё на свете,
сервис Heroku имеет свои преимущества и недостатки. Он обходится дороже,
чем аренда виртуального сервера на DigitalOcean, но это плата за то, что
DevOps-инженеры Heroku не спят ночью, а вы можете спать.&lt;/p&gt;

&lt;p&gt;Деплой в Heroku устроен просто: &lt;code&gt;git push&lt;/code&gt; – и понеслась. Через некоторое время
приложение уже перезапустилось и работает. По крайней мере, в теории. А на
практике есть некоторые нюансы, с которыми приходится иметь дело.&lt;/p&gt;

&lt;h4 id=&quot;assets&quot;&gt;Нюанс 1. Assets&lt;/h4&gt;

&lt;p&gt;Да-да, это CSS, JS и их друзья. Если вы, не прикладывая специальных усилий, задеплоите
приложение на Heroku, оно, конечно, будет работать. Но все assets будут отдаваться клиентам
из Ruby, то есть ваши недешёвые dynos будут изображать из себя Apache.
Во-первых, это медленно (настоящие Apache и nginx справятся гораздо быстрее). Во-вторых,
пока код Rack заталкивает в сетевой буфер содержимое скомпилированного
&lt;code&gt;application.js&lt;/code&gt;, входящие запросы стоят и ждут. Очевидно, такая схема подойдёт
лишь для сайта вашей тёщи, но не более.&lt;/p&gt;

&lt;p&gt;Поэтому все &lt;em&gt;homo&lt;/em&gt;, которые &lt;em&gt;sapiens&lt;/em&gt;, для хранения и отдачи assets используют CDN.
Например, закачивают скомпилированные assets на S3 и раздают их оттуда через
CloudFront. Аналогично с Rackspace и прочими облачными платформами. Нет для статики ничего лучше,
чем CDN, – можно принять это за аксиому.&lt;/p&gt;

&lt;p&gt;Заметим, что для правильной генерации URL для assets приложение нуждается
в файле-манифесте, который создаётся в процессе выполнения &lt;code&gt;rake assets:precompile&lt;/code&gt;.
В Rails 4 этот файл лежит в каталоге &lt;code&gt;public/assets&lt;/code&gt; и имеет примерно следующий вид:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// public/assets/.sprockets-manifest-9b2e86e85245c42f19250388ba3e1a45.json&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&amp;quot;files&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&amp;quot;application-3dda9c3d8b35165ec9de63f4b471dc4ce898f16443b1c132aa5049128d8e9a1c.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;logical_path&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;application.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;mtime&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;2015-10-07T11:22:21+06:00&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;size&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;341219&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;3dda9c3d8b35165ec9de63f4b471dc4ce898f16443b1c132aa5049128d8e9a1c&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&amp;quot;integrity&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;sha256-PdqcPYs1Fl7J3mP0tHHcTOiY8WRDscEyqlBJEo2Omhw=&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&amp;quot;assets&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&amp;quot;application.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;application-3dda9c3d8b35165ec9de63f4b471dc4ce898f16443b1c132aa5049128d8e9a1c.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Благодаря манифесту конструкция &lt;code&gt;&amp;lt;%= javascript_include_tag :application %&amp;gt;&lt;/code&gt;
генерирует ссылку на имя файла с длинным шестнадцатеричным хвостом. Если этот файл
был предварительно загружен на CDN, всё работает.&lt;/p&gt;

&lt;p&gt;Как это всё увязывается с Heroku? По умолчанию в процессе деплоя
Heroku выполняет команду &lt;code&gt;rake assets:precompile&lt;/code&gt;, которая наполняет каталог &lt;code&gt;public/assets&lt;/code&gt;
скомпилированными assets, включая и манифест. Но как файлам попасть на CDN? Как
обеспечить стопроцентную актуальность манифеста?&lt;/p&gt;

&lt;p&gt;На то был создан гем &lt;a href=&quot;https://github.com/AssetSync/asset_sync&quot;&gt;asset_sync&lt;/a&gt;. Он
добавляет свою rake task, которая срабатывает после &lt;code&gt;rake assets:precompile&lt;/code&gt; и заливает
всё на S3. Таким образом, в процессе деплоя после компиляции прямо с билд-сервера Heroku
запускается синхронизация с S3.&lt;/p&gt;

&lt;p&gt;К сожалению, это решение оказывается так себе. Оно очень сильно удлиняет время деплоя,
особенно в больших проектах. Компилятор assets, вообще-то, умный. Он кеширует свои
результаты и заново работу делать не будет. Посмотрите на результаты локального запуска:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rm -rf public/assets
&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;bin/rake assets:precompile
&lt;span class=&quot;c&quot;&gt;#&amp;gt; Writing ..... (много строк пропущено)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&amp;gt; bin/rake assets:precompile  0,19s user 0,10s system 5% cpu 5,191 total&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;bin/rake assets:precompile
&lt;span class=&quot;c&quot;&gt;#&amp;gt; bin/rake assets:precompile  0,16s user 0,07s system 28% cpu 0,807 total&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В процессе второго запуска не было изменено ничего. Однако на Heroku этим кэшем
воспользоваться не получится, потому что билд-сервер каждый раз работает с чистого листа.
Полная компиляция и синхронизация будет
делаться &lt;strong&gt;каждый раз&lt;/strong&gt;, даже если вы assets не трогали. И это напрягает!&lt;/p&gt;

&lt;h4 id=&quot;section-1&quot;&gt;Нюанс 2. Миграции&lt;/h4&gt;

&lt;p&gt;Если вы используете SQL-базу данных, периодически возникает необходимость прогона
миграций. Но и здесь у Heroku всё непросто. Автоматически ничего не происходит,
вам нужно вызывать &lt;code&gt;heroku run rake db:migrate&lt;/code&gt; самостоятельно. &lt;strong&gt;Но&lt;/strong&gt;: исполнить
эту команду можно только &lt;strong&gt;после&lt;/strong&gt; деплоя, когда соответствующие файлы в &lt;code&gt;db/migrate&lt;/code&gt;
будут на месте. Соответственно, возможен случай, когда только что задеплоенный Ruby-код будет
ссылаться на атрибут в БД, которого ещё нет (миграция не успела выполниться);
тогда пользователь увидит 500-ю ошибку.&lt;/p&gt;

&lt;p&gt;Чтобы избежать этой неприятной ситуации, Heroku предлагает перед деплоем включать
режим maintenance. Полный скрипт деплоя будет выглядеть так:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;heroku maintenance:on
git push heroku master
heroku run rake db:migrate
heroku maintenance:off
heroku restart&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;heroku restart&lt;/code&gt; необходим для того, чтобы схема БД была перечитана. В
production-режиме она читается один раз на старте, и если в таблице &lt;code&gt;users&lt;/code&gt;
не было поля &lt;code&gt;age&lt;/code&gt;, то методы &lt;code&gt;User#age&lt;/code&gt; и &lt;code&gt;User#age=&lt;/code&gt; не будут созданы. И даже
после того, как миграция пройдёт и колонка &lt;code&gt;age&lt;/code&gt; появится, любой код, вызывающий
эти методы, так и будет падать.&lt;/p&gt;

&lt;p&gt;К сожалению, даже в этой схеме между выполнением &lt;code&gt;heroku maintenance:off&lt;/code&gt; и
&lt;code&gt;heroku restart&lt;/code&gt; может пролезть какой-нибудь неудачливый пользователь и
схлопотать свою 500-ю. Но иначе никак.&lt;/p&gt;

&lt;p&gt;А теперь давайте представим, что assets компилируются и синхронизируются несколько минут —
вполне реальные цифры для большого приложения.
И всё это время мы в режиме maintenance!..&lt;/p&gt;

&lt;h3 id=&quot;section-2&quot;&gt;Путь к желаемой системе&lt;/h3&gt;

&lt;p&gt;Итак, мы очертили круг проблем:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Наглядность процесса деплоя.&lt;/li&gt;
  &lt;li&gt;Компиляция assets и заливка на CDN.&lt;/li&gt;
  &lt;li&gt;Запуск миграций.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Поразмышляв, я пришёл к следующему решению:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Весь процесс деплоя работает внутри &lt;a href=&quot;https://jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt;, работающего
на выделенном VPS-сервере.&lt;/li&gt;
  &lt;li&gt;Assets полностью компилируются и заливаются на S3 &lt;strong&gt;перед&lt;/strong&gt; деплоем. Если изменений
не было (что можно отследить по истории git), то ничего не делается (а это очень быстро).
Если изменения-таки были, &lt;code&gt;rake assets:precompile&lt;/code&gt; использует локальный кэш, что убыстряет
компиляцию.&lt;/li&gt;
  &lt;li&gt;Запуск деплоя происходит через &lt;a href=&quot;https://hubot.github.com/&quot;&gt;Hubot&lt;/a&gt; — бота, висящего в чате
и взаимодействующего с Jenkins. Он же оповещает об успешном или неуспешном завершении деплоя.&lt;/li&gt;
  &lt;li&gt;Прогон миграций является опциональным. Вводится два режима деплоя: &lt;strong&gt;обычный&lt;/strong&gt; (приведённый выше
полный скрипт с включением режима maintenance) и &lt;strong&gt;быстрый&lt;/strong&gt;, когда миграции
не прогоняются вообще.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Таким образом, система состоит из следующих частей:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Hubot.&lt;/li&gt;
  &lt;li&gt;Кастомный скрипт для Hubot, реализующий команду deploy и взаимодействующий
с Jenkins.&lt;/li&gt;
  &lt;li&gt;Настроенный и работающий Jenkins с соответствующими сборками.&lt;/li&gt;
  &lt;li&gt;Bash-скрипт &lt;code&gt;bin/deploy&lt;/code&gt; в исходном коде приложения. Его в процессе сборки запускает Jenkins.&lt;/li&gt;
  &lt;li&gt;Модуль для синхронизации assets с S3 внутри приложения.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Давайте немного обсудим эти отдельные части.&lt;/p&gt;

&lt;h4 id=&quot;hubot&quot;&gt;Hubot&lt;/h4&gt;

&lt;p&gt;Я влюбился в Hubot с первого взгляда и использую его в каждом проекте. Мои любимые
команды помимо &lt;code&gt;deploy&lt;/code&gt; — это &lt;a href=&quot;https://github.com/hubot-scripts/hubot-google-images&quot;&gt;&lt;code&gt;image&lt;/code&gt;&lt;/a&gt; и &lt;a href=&quot;https://github.com/github/hubot-scripts/blob/master/src/scripts/excuse.coffee&quot;&gt;&lt;code&gt;excuse&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;По сути, Hubot обеспечивает удобный программный интерфейс к вашему чату в &lt;a href=&quot;https://slack.com/&quot;&gt;Slack&lt;/a&gt;
(&lt;a href=&quot;https://www.hipchat.com/&quot;&gt;HipChat&lt;/a&gt;, &lt;a href=&quot;https://campfirenow.com/&quot;&gt;Campfire&lt;/a&gt;).
Написать JS-код, который будет реагировать на команды, нетрудно.&lt;/p&gt;

&lt;p&gt;Сам Hubot легко деплоится на Heroku.&lt;/p&gt;

&lt;h4 id=&quot;jenkins&quot;&gt;Jenkins&lt;/h4&gt;

&lt;p&gt;Jenkins – это тот ещё Java-монстр. Однако он стабильно работает и к нему есть очень много
плагинов. Свой плагин написать не так просто, как для Hubot, но обойдёмся.
Итак, что же хорошего нам даёт Jenkins?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Прозрачная работа с Git.&lt;/em&gt; Jenkins может склонировать репозиторий, выбрать и обновить нужную ветку.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;История сборок с логами.&lt;/em&gt; Если что-то не работает, результат прогона будет доступен всем разработчикам,
допущенным в Jenkins.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Очередь сборок.&lt;/em&gt; Если поступило подряд две команды на деплой, вторая встанет в очередь и подождёт, пока
первая закончит свою работу. Если вторая была дана случайно, можно успеть зайти в веб-интерфейс Jenkins и отменить её.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Параллельность сборок&lt;/em&gt;. Если у вас несколько приложений или версий одного приложения (staging, production),
деплой будет идти параллельно.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;По сути, Jenkins — это супер-богатая оболочка для запуска bash-скриптов.&lt;/p&gt;

&lt;h3 id=&quot;section-3&quot;&gt;Настройка&lt;/h3&gt;

&lt;p&gt;Перейдём же к настройке и конфигурированию. Ниже будет предполагаться, что
Heroku-приложение называется &lt;em&gt;coolapp&lt;/em&gt;.&lt;/p&gt;

&lt;h4 id=&quot;jenkins-1&quot;&gt;Jenkins&lt;/h4&gt;

&lt;p&gt;Установить Jenkins несложно. В последнем проекте я для этого использовал &lt;a href=&quot;http://www.ansible.com/&quot;&gt;ansible&lt;/a&gt;,
взяв готовую роль &lt;a href=&quot;https://github.com/Stouts/Stouts.jenkins&quot;&gt;Stouts.jenkins&lt;/a&gt;.
Нам понадобятся следующие плагины:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin&quot;&gt;GIT plugin&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Build+Token+Root+Plugin&quot;&gt;Build Authorization Token Root Plugin&lt;/a&gt;.
Нужен для того, чтобы можно было HTTP-запросом запустить сборку (деплой) снаружи.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin&quot;&gt;Notification plugin&lt;/a&gt;. Потребуется для
уведомления Hubot о процессе сборки.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Я также ставлю &lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/Green+Balls&quot;&gt;Green Balls&lt;/a&gt; для красоты,
&lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/ChuckNorris+Plugin&quot;&gt;ChuckNorris Plugin&lt;/a&gt; для прикола,
&lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/Matrix+Authorization+Strategy+Plugin&quot;&gt;Matrix Authorization Strategy Plugin&lt;/a&gt;
для удобного управления доступом, &lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/Credentials+Plugin&quot;&gt;Credentials Plugin&lt;/a&gt;
для задания дополнительных ключей доступа к репозиториям
 и &lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/SCM+Sync+configuration+plugin&quot;&gt;SCM Sync Configuration Plugin&lt;/a&gt;
для сохранения конфигов Jenkins в git.&lt;/p&gt;

&lt;p&gt;У того пользователя, под которым будет работать Jenkins (обычно это &lt;code&gt;jenkins&lt;/code&gt;),
должны быть установлены интерпретаторы Ruby всех необходимых версий. Для этого
оказалась полезной &lt;a href=&quot;https://github.com/zzet/ansible-rbenv-role&quot;&gt;ansible-роль zzet.rbenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Убедившись, что всё работает, можно приступать к созданию сборки. Дадим ей
осмысленное имя:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/jenkins_name.png&quot; alt=&quot;Задание имени для сборки&quot; width=&quot;343&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Каждая версия приложения (staging, production, …) потребует отдельной сборки. Затем добавим
нотификацию:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/jenkins_notify.png&quot; alt=&quot;Нотификация&quot; width=&quot;482&quot; /&gt;&lt;/p&gt;

&lt;p&gt;В поле URL должен находиться полный адрес Hubot со специфическим путём:
например, &lt;code&gt;https://my-hubot-instance.herokuapp.com/hubot/jenkins_status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ниже нужно поставить галку «Параметризованная сборка». Параметры, которые мы создадим,
с одной стороны, будут доступны скрипту сборки как переменные окружения,
а, с другой стороны, их можно будет передавать извне,
запуская сборку посредством HTTP POST-запроса. Понадобятся следующие параметры:&lt;/p&gt;

&lt;table class=&quot;table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Имя&lt;/th&gt;
      &lt;th&gt;Тип&lt;/th&gt;
      &lt;th&gt;Значение по умолч.&lt;/th&gt;
      &lt;th&gt;Описание&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;APP&lt;/td&gt;
      &lt;td&gt;Строка&lt;/td&gt;
      &lt;td&gt;&lt;code&gt;coolapp-staging&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Имя приложения Heroku&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BRANCH&lt;/td&gt;
      &lt;td&gt;Строка&lt;/td&gt;
      &lt;td&gt;&lt;code&gt;master&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Имя ветки&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEPLOYER&lt;/td&gt;
      &lt;td&gt;Строка&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;(пусто)&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;Инициатор деплоя&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;QUICK&lt;/td&gt;
      &lt;td&gt;Булев.&lt;/td&gt;
      &lt;td&gt;выкл.&lt;/td&gt;
      &lt;td&gt;Быстрый режим — без миграций&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;EXTRA&lt;/td&gt;
      &lt;td&gt;Строка&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;(пусто)&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;Доп. параметры&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;В разделе «Source Code Management» следует выбрать Git и указать адрес репозитория,
а в поле выбора ветки поставить &lt;code&gt;origin/$BRANCH&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/jenkins_scm.png&quot; alt=&quot;Настройка SCM&quot; width=&quot;544&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Это позволит нам деплоить любую ветку, которая есть в Git.&lt;/p&gt;

&lt;p&gt;В разделе «Build Triggers» задайте токен для внешней связи, а все остальные галки
отключите:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/jenkins_build_trigger.png&quot; alt=&quot;Build Triggers&quot; width=&quot;422&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Останется лишь добавить шаг сборки («Execute Shell»):&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Используем динамическое задание версии Ruby. Не забудьте указать версию&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# в вашем Gemfile! Например, ruby &amp;#39;2.2.3&amp;#39;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ruby_version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;grep &lt;span class=&quot;s1&quot;&gt;&amp;#39;^ruby&amp;#39;&lt;/span&gt; Gemfile&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;cut -d &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;#39;&amp;quot;&lt;/span&gt; -f 2&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Домашний каталог пользователя jenkins – /var/lib/jenkins. Если у вас другой,&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# поправьте.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/lib/jenkins/.rbenv/versions/&lt;span class=&quot;nv&quot;&gt;$ruby_version&lt;/span&gt;/bin:/var/lib/jenkins/.rbenv/shims:&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/var/lib/jenkins

&lt;span class=&quot;c&quot;&gt;# Делаем параметр EXTRA доступным внутри bin/deploy.sh&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;EXTRA

&lt;span class=&quot;c&quot;&gt;# А это ключи доступа для закачки assets на S3:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;ключ&amp;gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;секретный ключ&amp;gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_BUCKET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;coolapp-prod-assets

&lt;span class=&quot;c&quot;&gt;# Задайте правильное имя окружения&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;staging

&lt;span class=&quot;c&quot;&gt;# В некоторых версиях Rails команда rake assets:precompile зачем-то требует&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# соединение с БД. Оказалось легче уступить, для чего я создал пустую БД&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# и пользователя jenkins с паролем jenkins.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;postgresql://jenkins:jenkins@127.0.0.1/jenkins_empty

&lt;span class=&quot;c&quot;&gt;# Пришлось столкнуться и с тем, что Devise требует ключ. Хотя мы всего лишь&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# делаем rake assets:precompile! Как гласит мудрость, зануде легче отдаться…&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DEVISE_SECRET_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fb02df94e6fb4

&lt;span class=&quot;c&quot;&gt;# Эти команды будут полезны, если что-то не будет работать. Раскомментируйте&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# для отладки:&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#gem env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#bundle env&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; -f bin/deploy.sh &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;bin/deploy.sh
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Итак, здесь мы настраиваем среду и передаём управление скрипту
&lt;code&gt;bin/deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;К слову сказать, если вы создаёте вторую, третью и т.д. сборку, не забивайте
всё заново, а воспользуйтесь режимом копирования:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/jenkins_copy.png&quot; alt=&quot;Копирование сборки&quot; width=&quot;725&quot; /&gt;&lt;/p&gt;

&lt;p&gt;А пока возьмёмся за Hubot.&lt;/p&gt;

&lt;h4 id=&quot;hubot-1&quot;&gt;Настройка Hubot&lt;/h4&gt;

&lt;p&gt;Hubot ставится &lt;a href=&quot;https://hubot.github.com/docs/&quot;&gt;по инструкции&lt;/a&gt;. Кстати, подберите
подходящее имя для бота вместо «hubot». Например, в Shuttlerock у нас его
зовут &lt;em&gt;rodney&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;В каталог scripts нужно будет добавить файл
&lt;a href=&quot;https://gist.github.com/be9/87727f2f41c8709036e2#file-deploy-coffee&quot;&gt;deploy.coffee&lt;/a&gt;. Эта
версия работает для Slack, для других адаптеров могут потребоваться минимальные правки.&lt;/p&gt;

&lt;p&gt;В &lt;code&gt;deploy.coffee&lt;/code&gt; вам потребуется поменять только одну строку:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-coffee&quot; data-lang=&quot;coffee&quot;&gt;&lt;span class=&quot;nv&quot;&gt;APPS = &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;staging&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Укажите здесь все приложения и/или их версии, которые вы будете деплоить. Например,
эта строка могла бы выглядеть так:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-coffee&quot; data-lang=&quot;coffee&quot;&gt;&lt;span class=&quot;nv&quot;&gt;APPS = &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;staging&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;monitoring production&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;monitoring staging&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Здесь подразумеваются две версии основного приложения и две версии приложения
monitoring.&lt;/p&gt;

&lt;p&gt;Остальные настройки делаются посредством изменения переменных окружения
у инстанса Hubot. Рассмотрим настройки, необходимые для работы деплоя.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Переменная &lt;code&gt;HUBOT_JENKINS_URL&lt;/code&gt; должна содержать полный адрес Jenkins (например,
&lt;code&gt;https://jenkins.example.com&lt;/code&gt;).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Для &lt;code&gt;HUBOT_JENKINS_BUILD_TOKEN&lt;/code&gt; задайте
значение, которое вы указывали при конфигурации сборки (в нашем примере это
&lt;code&gt;0W5CT73cFV4ia89N9Sa87S644v3twA9P&lt;/code&gt;). Предполагается, что этот токен одинаков
для всех приложений и их версий.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Теперь для каждого приложения из массива &lt;code&gt;APPS&lt;/code&gt; задайте четыре переменных:&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;code&gt;HUBOT_PRODUCTION_APP&lt;/code&gt;. Это имя Heroku-приложения. В нашем примере &lt;code&gt;coolapp-staging&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;HUBOT_PRODUCTION_DEFAULT_BRANCH&lt;/code&gt;. Ветка, которая будет деплоиться по умолчанию (если явно не указана).
Например, &lt;code&gt;master&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;HUBOT_PRODUCTION_JOB&lt;/code&gt;. Имя сборки у Jenkins. У нас это &lt;code&gt;coolapp-staging-deploy&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;HUBOT_PRODUCTION_ACL&lt;/code&gt;. Эта переменная управляет доступом к деплою. Здесь
нужно задать либо список E-mail допущенных пользователей через запятую, либо &lt;code&gt;everyone&lt;/code&gt;.
Реальные E-mail адреса можно посмотреть с помощью команды бота &lt;code&gt;hubot show users&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Имена переменных получаются из имени приложения. Например, для &lt;em&gt;monitoring staging&lt;/em&gt;
они будут называться &lt;code&gt;HUBOT_MONITORING_STAGING_APP&lt;/code&gt; и т.д.&lt;/p&gt;

&lt;p&gt;Итак, если всё сконфигурировано верно, команда боту (&lt;code&gt;hubot deploy to staging&lt;/code&gt;)
запустит на Jenkins сборку с соответствующими параметрами. Теперь давайте
посмотрим в самую сердцевину процесса — на скрипт &lt;code&gt;deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;bindeploysh&quot;&gt;bin/deploy.sh&lt;/h4&gt;

&lt;p&gt;Скрипт находится в том же &lt;a href=&quot;https://gist.github.com/be9/87727f2f41c8709036e2#file-deploy-sh&quot;&gt;gist&lt;/a&gt;,
но давайте разберём, что там происходит. Пойдём по отдельным функциям.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;set_extra_flags&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;set_extra_flags&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$EXTRA&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~ &lt;span class=&quot;s1&quot;&gt;&amp;#39;reupload assets&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLOUD_ASSETS_REUPLOAD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$EXTRA&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~ &lt;span class=&quot;s1&quot;&gt;&amp;#39;recompile assets&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLOUD_ASSETS_RECOMPILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$EXTRA&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~ &lt;span class=&quot;s1&quot;&gt;&amp;#39;cleanup assets&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLOUD_ASSETS_REMOTE_DELETE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$EXTRA&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~ &lt;span class=&quot;s1&quot;&gt;&amp;#39;skip heroku&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SKIP_HEROKU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$EXTRA&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~ &lt;span class=&quot;s1&quot;&gt;&amp;#39;clear cache&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CLEAR_CACHE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Здесь видно, как используется «хвост» команды боту: в нём можно передать
дополнительные параметры. Просто команда &lt;code&gt;hubot deploy to staging&lt;/code&gt; —
это одно. А если вы наберёте &lt;code&gt;hubot deploy to staging and recompile assets and clear cache&lt;/code&gt;,
будут установлены переменные &lt;code&gt;CLOUD_ASSETS_RECOMPILE&lt;/code&gt; и &lt;code&gt;CLEAR_CACHE&lt;/code&gt;, что повлияет на процесс
(см. дальше).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;compile_assets&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;compile_assets&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;current_sha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;git log -n &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; --pretty&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;format:%H app/assets vendor/assets&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; -f public/assets/CURRENT_SHA &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;cat public/assets/CURRENT_SHA&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$current_sha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$CLOUD_ASSETS_REUPLOAD&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$CLOUD_ASSETS_RECOMPILE&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Assets did not change (SHA $current_sha)&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Recompiling assets&amp;quot;&lt;/span&gt;

    bundle install --quiet --without&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;    &lt;/span&gt;rm -rf public/assets

    &lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rake assets:precompile cloud_assets:sync

    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$current_sha&lt;/span&gt; &amp;gt; public/assets/CURRENT_SHA
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В файле &lt;code&gt;public/assets/CURRENT_SHA&lt;/code&gt; лежит последний коммит, изменявший содержимое &lt;code&gt;app/assets&lt;/code&gt; или &lt;code&gt;vendor/assets&lt;/code&gt; (если
ваши assets лежат где-то ещё, добавьте эти каталоги в аргументы &lt;code&gt;git log&lt;/code&gt;). Если
изменений не было, то ничего перекомпилироваться не будет.&lt;/p&gt;

&lt;p&gt;В противном случае происходит перекомпиляция (для надёжности выполняется
  &lt;code&gt;rm -rf public/assets&lt;/code&gt;, хотя это и не является необходимым) и заливка на S3.&lt;/p&gt;

&lt;p&gt;Чтобы заливка работала, добавьте в исходный код проекта файлы
&lt;a href=&quot;https://gist.github.com/be9/87727f2f41c8709036e2#file-cloud_assets-rake&quot;&gt;lib/tasks/cloud_assets.rake&lt;/a&gt;
и &lt;a href=&quot;https://gist.github.com/be9/87727f2f41c8709036e2#file-fog_cloud_assets-rb&quot;&gt;lib/fog_cloud_assets.rb&lt;/a&gt;.
Для работы последнего также понадобится гем fog_aws, не забудьте его добавить в &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;fog-aws&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;fog/aws&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Модуль FogCloudAssets осуществляет инкрементальную закачку файлов на S3.
Если режим &lt;code&gt;recompile assets&lt;/code&gt;, как мы видели выше, осуществляет полную перекомпиляцию,
то &lt;code&gt;reupload assets&lt;/code&gt; делает полную закачку на S3: заливается даже то, что уже есть.
А &lt;code&gt;cleanup assets&lt;/code&gt; удаляет все assets, которые отсутствуют в текущем манифесте.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;save_deploy_information&lt;/strong&gt; и &lt;strong&gt;commit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Из-за того, что мы компилируем assets до деплоя, встаёт вопрос, как обеспечить
наличие актуального манифеста в задеплоенном коде. Я
решил этот вопрос так: при каждом деплое создаётся временная ветка,
куда в &lt;code&gt;public/assets&lt;/code&gt; коммитится файл манифеста, а при деплое вызывается
&lt;code&gt;git push --force&lt;/code&gt;. Heroku же, увидев этот манифест, не будет
вызывать &lt;code&gt;rake assets:precompile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Это не очень элегантно, зато даёт новые возможности. Функция &lt;code&gt;save_deploy_information&lt;/code&gt;,
например, генерирует файл &lt;code&gt;lib/deploy_info.rb&lt;/code&gt; следующего формата:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;DeployInfo&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;feature/5852-core-can-upload-to-boards-via-the-api-when-submissions-false&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;GIT_COMMIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;833a167e8bfd747816eb7337a45394f288ce813f&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BUILD_NUMBER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;1217&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BUILD_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;1217&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;DEPLOYER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;dave&amp;#39;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/[_]_END__(.*)$/m&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;module_function&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;__END__&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;commit 833a167e8bfd747816eb7337a45394f288ce813f&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;Author: John Doe &amp;lt;johndoe@example.org&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;Date:   Thu Nov 12 18:02:04 2015 +0300&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;    #5852: Updated specs for Api::V1::BoardItemsController&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А это позволяет легко понять, что именно задеплоено. Мы используем
&lt;a href=&quot;https://github.com/active_admin/active_admin&quot;&gt;ActiveAdmin&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/admin/dashboards.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ActiveAdmin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;register_page&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Dashboard&amp;quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;panel&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Deploy Information&amp;quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;deploy_info&amp;#39;&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;github&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;https://github.com/CoolCompany/coolapp/&amp;quot;&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;attributes_table_for&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Branch&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link_to&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;tree/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Commit&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link_to&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GIT_COMMIT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;commit/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GIT_COMMIT&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Build Number&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BUILD_NUMBER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Build ID&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BUILD_ID&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;Deployer&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DEPLOYER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pre&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DeployInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# panel&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# column&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# columns&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# content&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Получается довольно красиво:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku-deploy/active_admin_deploy_info.png&quot; alt=&quot;Информация о задеплоенном коде в ActiveAdmin&quot; width=&quot;1035&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Чтобы ActiveAdmin не падал при локальном запуске приложения, положите в дерево исходных кодов
такой &lt;code&gt;lib/deploy_info.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# NOTE: Этот файл будет перезаписан в процессе деплоя!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;DeployInfo&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`git rev-parse --abbrev-ref HEAD`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;GIT_COMMIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`git rev-parse HEAD`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BUILD_NUMBER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;dev&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BUILD_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;dev&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;DEPLOYER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`git config user.name`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;sb&quot;&gt;`git log -1 --pretty=medium`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;module_function&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Рассмотрев отдельные детали, перейдём к изучению общей логики деплоя.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Основная логика bin/deploy.sh&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;set_extra_flags
compile_assets
save_deploy_information
commit

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$QUICK&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;true&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$SKIP_HEROKU&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
      git_push
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Skipping heroku push as requested&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;#if [[ &amp;quot;$CLEAR_CACHE&amp;quot; == &amp;#39;1&amp;#39; ]]; then&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#  echo &amp;quot;Clearing cache per request&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#  heroku run rake cache:clear --app $APP&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#fi&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;QUICK mode, not running migrations&amp;quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;$SKIP_HEROKU&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
      heroku maintenance:on --app &lt;span class=&quot;nv&quot;&gt;$APP&lt;/span&gt;

      git_push

      heroku run rake db:migrate --app &lt;span class=&quot;nv&quot;&gt;$APP&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#cache:clear db:migrate --app $APP&lt;/span&gt;

      heroku maintenance:off --app &lt;span class=&quot;nv&quot;&gt;$APP&lt;/span&gt;

      heroku restart --app &lt;span class=&quot;nv&quot;&gt;$APP&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Skipping heroku push as requested&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Здесь в зависимости от параметра &lt;code&gt;QUICK&lt;/code&gt; мы идём либо по короткому, либо по длинному пути.
Помимо этого показаны следующие вещи:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Режим &lt;code&gt;skip heroku&lt;/code&gt; позволяет скомпилировать и залить assets, но не трогать Heroku.
Крайне редко, но бывает нужно.&lt;/li&gt;
  &lt;li&gt;У нас в текущем проекте &lt;code&gt;rake cache:clear&lt;/code&gt; &lt;a href=&quot;https://gist.github.com/be9/87727f2f41c8709036e2#file-cache-rake&quot;&gt;вызывает &lt;code&gt;Rails.cache.clear&lt;/code&gt;&lt;/a&gt;.
В закоментированной части показано, как этим пользоваться. Полный деплой по умолчанию
вызывает &lt;code&gt;cache:clear&lt;/code&gt; вместе с &lt;code&gt;db:migrate&lt;/code&gt;, а в быстром режиме можно сказать боту &lt;code&gt;... and clear cache&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Следуя этой логике, можно легко добавлять собственные флаги для деплоя.
Для этого не придётся трогать ни Hubot, ни Jenkins, а лишь добавить обработку
в &lt;code&gt;bin/deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;deploy&quot;&gt;Инструкция по пользованию командой deploy&lt;/h3&gt;

&lt;p&gt;Итак, вы всё настроили. Что можно теперь делать?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hubot deploy to production&lt;/code&gt; деплоит production (используется ветка по умолчанию).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hubot quick deploy feature/something-really-cool to staging&lt;/code&gt; деплоит указанную ветку (&lt;code&gt;feature/…&lt;/code&gt;) на staging
в быстром режиме, без прогона миграций.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hubot deploy to production and recompile assets&lt;/code&gt; деплоит production, обязательно
 перекомпилируя assets, даже если они с прошлого раза не менялись.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hubot disable deploys to staging&lt;/code&gt; временно запрещает деплой на staging (полезно,
если ведутся технические работы или вы глубоко в отладке и не хотите,
чтобы вам мешали). Можно указать причину:
&lt;code&gt;hubot disable deploys to staging because it hurts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hubot enable deploys to staging&lt;/code&gt; включает возможность деплоя обратно.&lt;/p&gt;

&lt;p&gt;Обратите внимание, что для устойчивой работы полезно подключить к Hubot хранилище Redis
с помощью плагина &lt;a href=&quot;https://github.com/hubot-scripts/hubot-redis-brain&quot;&gt;hubot-redis-brain&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;section-4&quot;&gt;Заключение&lt;/h3&gt;

&lt;p&gt;Получилась классная модульная система деплоя! Её преимущества для Rails:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Удобный и управляемый деплой на Heroku.&lt;/li&gt;
  &lt;li&gt;Компиляция assets происходит отдельно и не замедляет деплой. Если assets не менялись,
на компиляцию и синхронизацию время не тратится вообще.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Но система не привязана к Rails! Общие преимущества для всех технологий:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Все понимают, что происходит.&lt;/li&gt;
  &lt;li&gt;Сохраняется история успешных и неуспешных прогонов.&lt;/li&gt;
  &lt;li&gt;Деплоить может любой человек, кому это разрешено, в т.ч. новичок или тестировщик,
которому трудно и долго устанавливать, настраивать и обновлять локальные зависимости (Heroku Toolbelt,
Ruby, bundler, node.js и проч.).&lt;/li&gt;
  &lt;li&gt;Деплоить можно любые ветки Git.&lt;/li&gt;
  &lt;li&gt;Соответствующий скрипт несложно написать для любой технологии.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В Shuttlerock мы стали деплоить с помощью системы все активные проекты. Например, для деплоя приложения на
&lt;a href=&quot;https://angularjs.org/&quot;&gt;angular.js&lt;/a&gt; с помощью &lt;a href=&quot;http://bower.io/&quot;&gt;bower&lt;/a&gt; и &lt;a href=&quot;http://gulpjs.com/&quot;&gt;gulp&lt;/a&gt;
нужно было лишь написать соответствующий скрипт.&lt;/p&gt;

&lt;p&gt;Мои коллеги говорят, что это самая лучшая система, которую им доводилось использовать.
Я им верю 😄 Попробуйте и вы!&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Nov 2015 00:00:00 +0000</pubDate>
        <link>http://be9.ru/2015/11/16/heroku-deployment.html</link>
        <guid isPermaLink="true">http://be9.ru/2015/11/16/heroku-deployment.html</guid>
        
        
      </item>
    
      <item>
        <title>Вышел fix для Redcarpet</title>
        <description>&lt;p&gt;Наконец-то вышла версия &lt;a href=&quot;https://rubygems.org/gems/redcarpet/versions/3.3.3&quot;&gt;Redcarpet 3.3.3&lt;/a&gt;,
в которую включён &lt;a href=&quot;/2015/09/12/memory-leak.html&quot;&gt;мой фикс&lt;/a&gt;.
Если вы используете Redcarpet, поставьте в Gemfile:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;redcarpet&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;~&amp;gt; 3.3.3&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;…и забудьте об утечках!&lt;/p&gt;
</description>
        <pubDate>Tue, 29 Sep 2015 00:00:00 +0000</pubDate>
        <link>http://be9.ru/2015/09/29/redcarpet-fix-released.html</link>
        <guid isPermaLink="true">http://be9.ru/2015/09/29/redcarpet-fix-released.html</guid>
        
        
      </item>
    
      <item>
        <title>Память без утечек</title>
        <description>&lt;p&gt;В продолжение &lt;a href=&quot;/2015/09/12/memory-leak.html&quot;&gt;поста про поиск утечки&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Пока редкарпетовцы &lt;a href=&quot;https://github.com/vmg/redcarpet/pull/516&quot;&gt;даже не шевелятся&lt;/a&gt;,
мы, конечно, давно уже задеплоили фикс на production. И вот сравните.&lt;/p&gt;

&lt;p&gt;Потребление памяти курильщика (картинка из предыдущего поста):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku_metrics_memleak.png&quot; alt=&quot;Потребление памяти web-процессами с утечкой&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Потребление памяти здорового человека:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku_metrics_noleak.png&quot; alt=&quot;Потребление памяти web-процессами без утечек&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Никакого тренда. Как говорят трейдеры, «боковик».&lt;/p&gt;

&lt;h1 id=&quot;ps&quot;&gt;P.S.&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;29 сентября 2015&lt;/em&gt;. &lt;a href=&quot;/2015/09/29/redcarpet-fix-released.html&quot;&gt;Вышел фикс для Redcarpet&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 15 Sep 2015 10:11:38 +0000</pubDate>
        <link>http://be9.ru/2015/09/15/no-memory-leak.html</link>
        <guid isPermaLink="true">http://be9.ru/2015/09/15/no-memory-leak.html</guid>
        
        
      </item>
    
      <item>
        <title>Как я провёл две недели в поисках утечки памяти</title>
        <description>&lt;h1 id=&quot;section&quot;&gt;Предисловие&lt;/h1&gt;

&lt;p&gt;Это история о поисках утечки памяти. Она довольно длинная, потому что я привожу
массу подробностей.&lt;/p&gt;

&lt;p&gt;Почему я решил описать свои приключения? Дело не только в практическом стремлении
сохранить все мелкие скрипты и куски кода. Мне на минуточку показалось, что это и
есть &lt;em&gt;UNIX way&lt;/em&gt; – то, что меня вело. Каждый шаг был связан с очередной
небольшой утилитой или библиотекой, которая хорошо решает свою задачу. И я в итоге
достиг успеха.&lt;/p&gt;

&lt;p&gt;Также мне было интересно! Порой я засыпал и просыпался с мыслями о том, что
происходит с памятью.&lt;/p&gt;

&lt;p&gt;Остаётся выразить благодарность моим коллегам из компании &lt;a href=&quot;https://www.shuttlerock.com/&quot;&gt;Shuttlerock&lt;/a&gt;,
которые &lt;em&gt;работали&lt;/em&gt;, пока я занимался детективной деятельностью. И самой компании, конечно.&lt;/p&gt;

&lt;h1 id=&quot;section-1&quot;&gt;Введение&lt;/h1&gt;

&lt;p&gt;Итак, обнаружилась неприятная данность: у нашего Rails-приложения, работающего на
платформе &lt;a href=&quot;http://heroku.com&quot;&gt;Heroku&lt;/a&gt;, «течёт» память. Это заметно на графике:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/heroku_metrics_memleak.png&quot; alt=&quot;Потребление памяти web-процессами&quot; /&gt;&lt;/p&gt;

&lt;p&gt;После каждого деплоя или перезапуска потребление памяти резко возрастает (см. в
районе 3 AM, 8 AM), и это нормально, ведь Ruby язык динамический. Срабатывают
какие-то &lt;code&gt;require&lt;/code&gt;, что-то догружается. После этого потребление памяти должно
болтаться вокруг горизонтальной линии. Обработали запрос, насоздавали объектов
— вверх. Сработал сборщик мусора — вниз. По идее, в таком динамическом балансе
приложение может находится сколь угодно долго.&lt;/p&gt;

&lt;p&gt;Однако в нашем случае на графике заметен стабильный тренд вверх. Значит, есть
утечка! Наш Heroku 2X dyno (мы используем &lt;a href=&quot;http://puma.io&quot;&gt;puma&lt;/a&gt; в кластерном
режиме с 2 процессами) добирается до границы 1 Гб, после чего начинает безбожно
тормозить до перезапуска.&lt;/p&gt;

&lt;h1 id=&quot;section-2&quot;&gt;Локальное повторение проблемы&lt;/h1&gt;

&lt;p&gt;Production-сервер — не место для экспериментов. Поэтому первое, что я сделал —
скачал дамп production-базы и запустил приложение в production-окружении на моём
тестовом Linux-сервере, стоящем в кладовке (у вас же, конечно, есть такой?). Наше
приложение — SaaS-продукт, в специальной middleware по домену определяется сайт
клиента, поэтому пришлось немного подправить код, чтобы можно было делать запросы
вида &lt;code&gt;curl http://localhost:3000/…&lt;/code&gt;. Здесь очень удобны переменные окружения.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;QUERY_SITE_ID&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;find_site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_by_id_cached&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:site_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Private: Look up for site.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Tries to find by domain, subdomain, or use canned site in test environment.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Returns Site instance or falsey value.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;find_site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# …&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видно, если установлена переменная окружения &lt;code&gt;QUERY_SITE_ID&lt;/code&gt;, то ID сайта
будет определяться из параметра запроса &lt;code&gt;site_id&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl http://localhost:3000/?site_id=123&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Также в &lt;code&gt;config/environments/production.rb&lt;/code&gt; понадобится поставить
&lt;code&gt;config.force_ssl = false&lt;/code&gt;,
установить переменную &lt;code&gt;DEVISE_SECRET_KEY&lt;/code&gt;, и возможно что-то ещё. Нужно добиваться,
чтобы приведённая выше команда &lt;code&gt;curl&lt;/code&gt; сработала.&lt;/p&gt;

&lt;p&gt;Итак, сервер запустился, что дальше? Теперь нужно обеспечить поток запросов.  Для
этого есть прекрасная утилита &lt;a href=&quot;https://www.joedog.org/siege-manual/&quot;&gt;siege&lt;/a&gt;,
позволяющая нагружать сервера в разных режимах и собирать статистику.&lt;/p&gt;

&lt;p&gt;Для чистоты эксперимента я решил не долбить по одному URL, а собрать реальные адреса, по которым
заходят клиенты.  Это сделать несложно: запускаем на какое-то время &lt;code&gt;heroku
logs -t | tee log/production.log&lt;/code&gt;, потом из лога останется выкусить адреса. Я
написал для этого небольшую утилиту, которая парсила лог, собирала адреса,
site_id, печатаемые нашей middleware, и генерировала файл urls.txt в формате:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;http://localhost:3000/foo
http://localhost:3000/bar
http://localhost:3000/baz&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Такой файл также можно сделать вручную, или воспользоваться коктейлем из awk, grep, sed.&lt;/p&gt;

&lt;p&gt;Запускаем siege:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;siege -v -c 15  --log=/tmp/siege.log -f urls.txt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Здесь siege создаст 15 параллельных клиентов и будет заходить по адресам из &lt;code&gt;urls.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Если всё было сделано правильно, память должна начать «течь». Это можно увидеть
с помощью утилит &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt; — соответствующий показатель называется RSS (Resident Set Size).
Чтобы не мучаться с их запуском, я добавил в приложение &lt;a href=&quot;http://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby&quot;&gt;вот такой код&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;MEMORY_REPORTING&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;rss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`ps -eo pid,rss | grep &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt; | awk &amp;#39;{print $2}&amp;#39;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_i&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;MEMORY[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]: rss: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rss&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, live objects &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:heap_live_slots&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В логе стали появляться аккуратные записи с растущим RSS… Про &lt;code&gt;GC.stat[:heap_live_slots]&lt;/code&gt;
см. дальше.&lt;/p&gt;

&lt;p&gt;Позже я отказался от кластерного режима puma, потому что утечка проявлялась и в
single mode, а с одним процессом проще иметь дело.&lt;/p&gt;

&lt;h1 id=&quot;ruby-&quot;&gt;Поиск утечки в Ruby коде&lt;/h1&gt;

&lt;p&gt;Убедившись в реальности утечки, я приступил к её поиску.&lt;/p&gt;

&lt;p&gt;Здесь нужно немного остановиться на том, как устроена память в MRI вообще. Объекты
хранятся в куче, которой управляет интерпретатор. Куча состоит из отдельных страниц размером
16 килобайт, на каждый объект выделяется 40 байт. При создании объекта идёт поиск свободной ячейки,
если таковых нет, создаётся новая страница. Конечно, не все объекты помещаются в 40 байт. Если памяти требуется
больше, дополнительная память выделяется отдельно (с помощью &lt;code&gt;malloc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Освобождение памяти происходит автоматически в процессе работы сборщика мусора (GC). Современные
версии MRI
имеют довольно эффективный инкрементальный GC с поколениями объектов и двумя фазами: малой и большой. Опираясь на
эвристический принцип «большинство объектов имеют небольшое время жизни», &lt;em&gt;малый цикл сборки мусора&lt;/em&gt; ищет и освобождает
ненужные объекты только среди недавно созданных. Это позволяет реже запускать &lt;em&gt;большой цикл&lt;/em&gt;, который
подвергает классическому алгоритму Mark-and-Sweep &lt;strong&gt;ВСЕ&lt;/strong&gt; объекты.&lt;/p&gt;

&lt;p&gt;Нужно отметить, что для поиска утечек тонкости различных поколений совершенно не
важны. Существенно одно: будут ли освобождены все объекты, порождаемые в процессе
обработки запроса, или нет. Заметим, что в контексте веб-сервера все порождаемые
объекты можно разделить на три группы:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;Статика&lt;/em&gt;. Это все загруженные гемы, особенно Rails, а также код приложения. В production-окружении
всё это грузится один раз и практически не изменяется.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Медленная динамика&lt;/em&gt;. Есть некоторое количество долгоживущих объектов, например, кеш
prepared SQL-запросов в ActiveRecord. Этот кеш, по умолчанию имеющий размер 1000 элементов
на каждое соединение с БД, будет потихоньку заполняться, и количество объектов будет расти,
пока не дойдёт до предела (2000 строк * количество соединений).&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Быстрая динамика&lt;/em&gt;. Это все объекты, порождаемые в процессе обработки запроса и генерации ответа.
После того, как ответ готов, объекты могут быть уничтожены.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Если в третьем случае какой-то объект не будет освобожден, будет утечка. Вот пример-иллюстрация:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;FOO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FOO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;haha&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Константы не подлежат сборке мусора, и последовательные вызовы &lt;code&gt;MyController#index&lt;/code&gt;
будут приводить к распуханию массива &lt;code&gt;FOO&lt;/code&gt;. При этом куча будет расти из-за того,
что ячейки будут забиваться новыми и новыми строками &lt;code&gt;&quot;haha&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Если утечек нет, размер кучи будет колебаться. Минимальный размер кучи соответствует
объектам из п.1 и п.2 (см. выше). Например, у нашего приложения это чуть больше 500000 объектов
(пустое приложение сразу после &lt;code&gt;rails new app&lt;/code&gt; даёт где-то 300000). Максимальный размер кучи
зависит от того, насколько будет успевать срабатывать большой цикл GC. &lt;strong&gt;Но:&lt;/strong&gt; после
большого цикла количество объектов будет всегда возвращаться к нижней границе, которая изменяться не будет.
Утечки будут приводить к тому, что нижняя граница поплывёт вверх. Показатель
&lt;code&gt;GC.stat[:heap_live_slots]&lt;/code&gt; как раз отражает текущий размер кучи.&lt;/p&gt;

&lt;p&gt;Удобнее всего исследовать эти вещи с помощью гема &lt;a href=&quot;https://github.com/ko1/gc_tracer&quot;&gt;gc_tracer&lt;/a&gt;,
созданного Koichi Sasada, членом команды разработчиков Ruby и автором инкрементального сборщика мусора
в Ruby 2.1 и 2.2. Добавив строки&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;rack/gc_tracer&amp;#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GCTracerMiddleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;view_page_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;/gc_tracer&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;log/gc.log&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;в &lt;code&gt;config/application.rb&lt;/code&gt;, мы получим файл &lt;code&gt;log/gc.log&lt;/code&gt;, который будет наполняться статистикой работы
сборщика мусора и результатами вызова &lt;code&gt;getrusage&lt;/code&gt; (последнее полезно, потому что одно из полей содержит
интересующую нас цифру RSS).&lt;/p&gt;

&lt;p&gt;В каждой строке этого лога около 50 значений и глаза разбегаются, однако на то есть несложная магия UNIX.
Вот команда, которую я запускал параллельно с &lt;code&gt;puma&lt;/code&gt; и &lt;code&gt;siege&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tail -f log/gc.log | cut -f 2,4,7,8,9,15,16,19,21,22,25,26,27,28,29,30,36&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;1441967588854360        2450    998618  997652  966     2450    0       0       5151    665     581634  1052512 2490888 16777216        0       newobj 277196
1441967588879589        2450    998618  997652  966     2450    0       0       5152    665     598912  1052512 2490888 16777216        0       newobj 277196
1441967590064377        2450    998618  997618  999     2450    0       503688  5152    665     598912  1052512 2103264 16777216        0       newobj 277196
1441967590064684        2450    998618  997808  810     2450    0       0       5152    665     598912  1052512 2107280 16777216        0       newobj 277196
1441967590088032        2450    998618  997808  810     2450    0       0       5153    665     613199  1052512 2107280 16777216        0       newobj 277196&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Самое первое значение в строке — timestamp в миллисекундах. Второе — количество страниц. Третье — общий размер кучи в единицах объектов.
Одиннадцатое (581634…613199)— количество «старых» объектов, т.е. тех, которые не обрабатываются в малом цикле сборки мусора. Самое последнее — RSS.&lt;/p&gt;

&lt;p&gt;Но даже так цифр слишком много. Что ж, построим график! Конечно, лог можно
непосредственно загнать в Excel (простите, LibreOffice Calc), но это не наш путь. Гораздо лучше воспользоваться
&lt;a href=&quot;http://www.gnuplot.info/&quot;&gt;gnuplot&lt;/a&gt;, который умеет рисовать графики прямо из файлов.&lt;/p&gt;

&lt;p&gt;К сожалению, gnuplot не поддерживает формат времени с миллисекундами, поэтому пришлось
написать маленький скрипт для преобразования времени:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

cat &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; awk &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;s1&quot;&gt;&amp;#39;{if($1==&amp;quot;end_sweep&amp;quot;||FNR==1) {if(FNR&amp;gt;1) {$2=substr($2,1,length($2)-6)} else {$2=$2}; print $0}}&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Помимо приведения временных отсчётов к секундам здесь отбрасывается лишняя информация. gc_tracer генерирует данные
на всех фазах работы сборки мусора, но нас будет интересовать только конец (end_sweep).&lt;/p&gt;

&lt;p&gt;С помощью вот такого gnuplot-скрипта:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gnuplot&quot; data-lang=&quot;gnuplot&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;xdata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timefmt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;y2tics&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;y2label&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Kilobytes&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ylabel&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Objects&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;gc.log.22&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columnhead&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;blue&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;columnhead&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;axes&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x1y2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;получаем картинку:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/olds_vs_rss.png&quot; alt=&quot;Количество «старых» объектов и RSS от времени&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Красная кривая (левая шкала) — количество «старых» объектов. Оно ведёт себя так, будто никакой
утечки памяти нет. Синяя кривая (правая шкала) — RSS. А оно растёт…&lt;/p&gt;

&lt;p&gt;Вывод простой: &lt;strong&gt;в Ruby коде утечек нет&lt;/strong&gt;. Однако я не сразу догадался до этого
и сходил еще по ложному пути, не приведшему к результату.&lt;/p&gt;

&lt;h1 id=&quot;section-3&quot;&gt;Ложный путь. Возня с дампами кучи&lt;/h1&gt;

&lt;p&gt;Современные версии MRI оснащены мощнейшими средствами анализа работы с памятью.
Например, можно включить трассировку создания объектов, и для каждого вновь
создаваемого объекта будет сохранено место (имя файла и номер строки), в котором
он был создан:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;objspace&amp;#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ObjectSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trace_object_allocations_start&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Также можно сделать полный дамп кучи в файл:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;my_dump.txt&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ObjectSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dump_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для объектов, созданных после включения трассировки, в дамп попадет информация о
месте создания объекта. Сам дамп представляет собой файл в формате
&lt;a href=&quot;http://jsonlines.org/&quot;&gt;JSON Lines&lt;/a&gt;: данные о каждом объекте представлены в формате
JSON и расположены на отдельной строке.&lt;/p&gt;

&lt;p&gt;Уже появились гемы, использующие возможность трассировки, например,
&lt;a href=&quot;https://github.com/SamSaffron/memory_profiler&quot;&gt;memory_profiler&lt;/a&gt; и
&lt;a href=&quot;https://github.com/schneems/derailed_benchmarks&quot;&gt;derailed&lt;/a&gt;. Вот и я решил
поисследовать, что в нашем приложении происходит с кучей. Не добившись внятного
результата от memory_profiler, я решил сгенерировать и проанализировать дампы сам.&lt;/p&gt;

&lt;p&gt;Поначалу я написал свой бенчмарк аналогично тому, как сделано в &lt;a href=&quot;https://github.com/schneems/derailed_benchmarks/blob/master/lib%2Fderailed_benchmarks%2Ftasks.rb#L58&quot;&gt;derailed&lt;/a&gt;,
но потом перешел на генерацию дампов из живого приложения с помощью &lt;a href=&quot;https://github.com/tmm1/rbtrace&quot;&gt;rbtrace&lt;/a&gt;.
Если в &lt;code&gt;Gemfile&lt;/code&gt; есть &lt;code&gt;gem &#39;rbtrace&#39;&lt;/code&gt;, то дамп может быть сгенерирован следующим образом:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rbtrace -p &lt;span class=&quot;nv&quot;&gt;$PID&lt;/span&gt; -e &lt;span class=&quot;s1&quot;&gt;&amp;#39;Thread.new{GC.start;require &amp;quot;objspace&amp;quot;;File.open(&amp;quot;heapdump.jsonl&amp;quot;, &amp;quot;w&amp;quot;){|f| ObjectSpace.dump_all(output: f) }}&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь предположим, что у нас есть три дампа (1, 2, 3), сгенерированные в разные моменты времени. Как распознать утечку?
Для этого предлагается &lt;a href=&quot;http://blog.skylight.io/hunting-for-leaks-in-ruby/&quot;&gt;следующая схема&lt;/a&gt;: возьмём дамп 2 и уберём из него
все объекты, которые уже встречались в дампе 1. После этого уберём из него все объекты, которых &lt;strong&gt;нет&lt;/strong&gt; в дампе 3.
То, что останется — объекты, которые, возможно, «утекли» во время, прошедшее между созданием дампов 1 и 2.&lt;/p&gt;

&lt;p&gt;Я даже написал &lt;a href=&quot;https://github.com/Shuttlerock/memory-dump-analyzer&quot;&gt;свою утилиту&lt;/a&gt; для описанного
дифференциального анализа дампов. Она реализована на… Clojure, потому что мне нравится Clojure.&lt;/p&gt;

&lt;p&gt;Всё, что обнаружил анализ, — это упомянутый выше кеш prepared SQL-запросов. Его содержимое — строки с
запросами — вовсе не «утекли», просто они прожили достаточно долго и проявились в дампе 3.&lt;/p&gt;

&lt;p&gt;Таким образом, я окончательно убедился, что утечек памяти в Ruby коде нет, и был вынужден
искать утечки в другом месте.&lt;/p&gt;

&lt;h1 id=&quot;jemalloc&quot;&gt;Знакомство с jemalloc&lt;/h1&gt;

&lt;p&gt;Я сформулировал следующую гипотезу: если в Ruby утечек нет, но память куда-то
девается, возможно, утечки есть в каком-то коде на C. Это либо native code гемов,
либо сам MRI. В таком случае C-куча должна расти.&lt;/p&gt;

&lt;p&gt;Но как искать утечки в C? Первое, что я попробовал, это &lt;code&gt;valgrind&lt;/code&gt; и утилита &lt;code&gt;leaks&lt;/code&gt;
под OS X. Они ничего интересного не нашли. И тогда поиски привели меня к &lt;a href=&quot;http://www.canonware.com/jemalloc/&quot;&gt;jemalloc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;jemalloc – это кастомная реализация &lt;code&gt;malloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt; и &lt;code&gt;realloc&lt;/code&gt;, которая пытается быть эффективнее стандартной
системной (не считая FreeBSD, где она и является системной). Для этого применена масса
трюков. Там и своя система страниц с выделением через &lt;code&gt;mmap&lt;/code&gt;, и разделение аллокатора на независимые «арены»,
к которым привязываются потоки; независимость позволяет избежать межпотоковой синхронизации. Выделяемые блоки
разделяются на три класса по размеру: &lt;em&gt;small&lt;/em&gt; (&amp;lt; 3584 байт), &lt;em&gt;large&lt;/em&gt; (&amp;lt; 4 Мб) и &lt;em&gt;huge&lt;/em&gt; –
для каждого класса память выделяется по-особенному. Но,
что самое важное, — в jemalloc есть сбор статистики и профилировка. И, наконец, в MRI 2.2.0 появилась поддержка jemalloc
во время конфигурации! Хак с использованием &lt;code&gt;LD_PRELOAD&lt;/code&gt; становится не нужен (а у меня он и не заработал, кстати).&lt;/p&gt;

&lt;p&gt;Я побежал ставить jemalloc, а потом и MRI с включенным jemalloc. Наступил на пару грабель.
В Ubuntu и Homebrew jemalloc собран без профилировки. С самым свежим jemalloc 4.0.0, вышедшим в августе 2015 г.,
Ruby не собирается: некоторые гемы (&lt;code&gt;pg&lt;/code&gt;) пугаются &lt;code&gt;&amp;lt;stdbool.h&amp;gt;&lt;/code&gt;, включаемого в &lt;code&gt;&amp;lt;jemalloc/jemalloc.h&amp;gt;&lt;/code&gt;. Зато с
версией 3.6.0 всё работает. Собрать Ruby можно по &lt;a href=&quot;http://groguelon.fr/post/106221222318/how-to-install-ruby-220-with-jemalloc-support&quot;&gt;инструкции&lt;/a&gt; с помощью rbenv, хотя
я в итоге собирал сам:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;ruby-2.2.3
./configure --with-jemalloc --prefix&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/od/.rbenv/versions/2.2.3-dbg --disable-install-doc
make
make install&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для Homebrew понадобится ещё ключ &lt;code&gt;--with-openssl-dir=/usr/local/opt/openssl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Этот Ruby работает как обычный. Но он реагирует на переменную среды &lt;code&gt;MALLOC_CONF&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;MALLOC_CONF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;narenas:1,stats_print:true&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;После завершения работы интерпретатора на &lt;code&gt;stderr&lt;/code&gt; выдаётся обширная статистика
по выделенной памяти. С такой установкой я и запустил &lt;code&gt;puma&lt;/code&gt; на ночь (с &lt;code&gt;siege&lt;/code&gt;, конечно). К утру
RSS достиг 2 Гб. Нажав Ctrl-C и поглядев на статистику, я увидел, что общее количество памяти, выделенной
через jemalloc, примерно такое же. Эврика! Предположение об утечке в C-куче подтвердилось!&lt;/p&gt;

&lt;h1 id=&quot;section-4&quot;&gt;Профилировка и сбор статистики&lt;/h1&gt;

&lt;p&gt;Следующий вопрос был таков: в каком месте C-кода выделяется вся эта память?
Здесь помогла профилировка. Профилировщик, встроенный в jemalloc, запоминает адреса, с которых
делались вызовы &lt;code&gt;malloc&lt;/code&gt;, подсчитывает, сколько с каждого адреса было выделено памяти, и сохраняет
статистику в дамп. Включить его можно через тот же &lt;code&gt;MALLOC_CONF&lt;/code&gt;, указав ключ &lt;code&gt;prof:true&lt;/code&gt;, тогда
после завершения работы процесса будет сгенерирован финальный дамп.
Дамп позже анализируется с помощью скрипта &lt;code&gt;pprof&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;К сожалению, &lt;code&gt;pprof&lt;/code&gt; не смог нормально разобраться с адресами, выдав нечто вроде:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Total: 105.3 MB
43.5  41.3%  41.3%     43.5  41.3% 0x00007f384fd2b5f9
23.4  22.2%  63.6%     23.4  22.2% 0x00007f384fd2b773
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Пришлось самостоятельно вычитать начало кодового сегмента (информация о сегментах
печатается при завершении процесса) и запускать команду &lt;code&gt;info symbol 0x2b5f9&lt;/code&gt; в
&lt;code&gt;gdb&lt;/code&gt;. Так я узнал, что этот адрес соответствует функции
&lt;a href=&quot;https://github.com/ruby/ruby/blob/v2_2_3/gc.c#L7359&quot;&gt;objspace_xmalloc&lt;/a&gt; (она объявлена
как &lt;code&gt;static&lt;/code&gt;, наверное, потому и не показывалась). Более-менее представительный профиль, снятый за пару
часов работы &lt;code&gt;puma&lt;/code&gt; под нагрузкой, показал, что из этой функции было выделено
&lt;strong&gt;97.9 %&lt;/strong&gt; всей памяти. Итак, утечка действительно имеет отношение к Ruby!&lt;/p&gt;

&lt;p&gt;Укрепившись в области поиска, я решил исследовать статистические закономерности
выделяемых блоков. Дабы не мучаться с парсингом текстовой статистики, выдаваемой
jemalloc, сел и написал свой гем &lt;a href=&quot;https://github.com/be9/jemal&quot;&gt;jemal&lt;/a&gt;. Основной
его функционал сокрыт в методе &lt;code&gt;Jemal.stats&lt;/code&gt;, который возвращает всю интересную
статистику в одном большом хэше.&lt;/p&gt;

&lt;p&gt;Осталось добавить небольшой кусок кода в приложение:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;JEMALLOC_STATS&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;STDERR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;JEMALLOC_STATS enabled&amp;quot;&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;jemal&amp;#39;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Jemal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jemalloc_builtin?&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;STDERR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;jemalloc found&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Jemal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_i&lt;/span&gt;

        &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;log/jemalloc.log&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;w&amp;#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_json&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;…запустить &lt;code&gt;puma&lt;/code&gt; и &lt;code&gt;siege&lt;/code&gt; на ночь и по обычаю лечь спать.&lt;/p&gt;

&lt;p&gt;К утру нарос неплохой &lt;code&gt;log/jemalloc.log&lt;/code&gt;, и можно было браться за анализ. Здесь
незаменимую помощь оказала утилита &lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;jq&lt;/a&gt;. Для
начала я решил посмотреть, как растёт память:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;jq &lt;span class=&quot;s1&quot;&gt;&amp;#39;.ts,.allocated&amp;#39;&lt;/span&gt; log/jemalloc.log &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; paste - - &amp;gt; allocated.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание, как здорово работает UNIX way! &lt;code&gt;jq&lt;/code&gt; разбирает JSON в каждой строке
и попеременно выводит то значение ключа ts, то значение ключа allocated:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;ts1
allocated1
ts2
allocated2
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Далее &lt;code&gt;paste - -&lt;/code&gt; собирает это построчно, разделяя табуляцией:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;ts1 allocated1
ts2 allocated2
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А такой файл уже годится на вход &lt;code&gt;gnuplot&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gnuplot&quot; data-lang=&quot;gnuplot&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;xdata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timefmt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;allocated.txt&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Allocated&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/allocated.png&quot; alt=&quot;Вся выделенная память в зависимости от времени&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Рост от времени линейный! А что с размером блоков?&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;jq &lt;span class=&quot;s1&quot;&gt;&amp;#39;.ts,.allocated,.arenas[0].small.allocated,.arenas[0].large.allocated&amp;#39;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
   log/jemalloc.log &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; paste - - - - &amp;gt; allocated.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; умеет доставать данные даже из глубоко вложенных структур.&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gnuplot&quot; data-lang=&quot;gnuplot&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;xdata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timefmt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;allocated.txt&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Allocated&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Small&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Large&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/allocated_classes.png&quot; alt=&quot;Память, выделенная по разным классам, в зависимости от времени&quot; /&gt;&lt;/p&gt;

&lt;p&gt;График красноречиво свидетельствует: утекают &lt;em&gt;small&lt;/em&gt;-объекты.
Но и это не всё: jemalloc предлагает статистику для разных размеров блоков!
В классе &lt;em&gt;small&lt;/em&gt; любой выделяемый блок путём округления размера вверх попадает в
один из 28 фиксированных размеров:
8, 16, 32, 48, 64, 80, …, 256, 320, 384, …, 3584. Для каждого из них ведётся своя статистика.
Приглядевшись к логу, я увидел аномальные значения для размера 320. Попробуем нарисовать и его:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;jq &lt;span class=&quot;s1&quot;&gt;&amp;#39;.ts,.allocated,.arenas[0].small.allocated,.arenas[0].large.allocated,.arenas[0].bins[&amp;quot;320&amp;quot;].allocated&amp;#39;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
   log/jemalloc.log &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; paste - - - - - &amp;gt; allocated.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gnuplot&quot; data-lang=&quot;gnuplot&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;xdata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timefmt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;allocated.txt&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Allocated&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Small&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Large&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;Small (257..320)&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/alloc_320.png&quot; alt=&quot;Память, выделенная по разным классам, в зависимости от времени&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ух ты! Память съедают объекты одного-единственного размера. Всё остальные составляют собой константу,
что следует из параллельности линий на графике. Что же происходит с размером 320?
Помимо общей суммы выделенной памяти для каждого размера jemalloc вычисляет
8 других показателей, в частности, количество выделений памяти (вызовов &lt;code&gt;malloc&lt;/code&gt;)
и количество освобождений. Что с ними? Рисуем:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;jq &lt;span class=&quot;s1&quot;&gt;&amp;#39;.ts,.arenas[0].bins[&amp;quot;320&amp;quot;].nmalloc,.arenas[0].bins[&amp;quot;320&amp;quot;].ndalloc,.arenas[0].bins[&amp;quot;256&amp;quot;].nmalloc,.arenas[0].bins[&amp;quot;256&amp;quot;].ndalloc&amp;#39;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
   jemalloc.log &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; paste - - - - - &amp;gt; malloc_dalloc.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gnuplot&quot; data-lang=&quot;gnuplot&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;xdata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timefmt&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;%H:%M&amp;#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;malloc_dalloc.txt&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;320 nmalloc&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;320 ndalloc&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;256 nmalloc&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; \
     &lt;span class=&quot;s&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;256 ndalloc&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/malloc_free.png&quot; alt=&quot;Количество выделений и освобождений для блоков 320 и 256, в зависимости от времени&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Для сравнения на графике приведены значения для блоков соседнего размера: 256. Видно,
что голубая и оранжевая кривая слились воедино, что означает, что количество освобождений
для размера 256 примерно соответствует количеству выделений. А вот для размера 320
количество выделений (фиолетовая кривая) уходит в отрыв от количества освобождений
(зелёная кривая). Что окончательно подтверждает существование утечки памяти.&lt;/p&gt;

&lt;h1 id=&quot;section-5&quot;&gt;Откуда утекает?&lt;/h1&gt;

&lt;p&gt;Мы выжали из статистики всё, что могли. Осталось найти виновника утечки.&lt;/p&gt;

&lt;p&gt;Тут я не нашёл ничего лучше, чем вставить отладочную печать в модуль &lt;code&gt;gc.c&lt;/code&gt;.
Такими стали функции &lt;code&gt;objspace_xmalloc&lt;/code&gt; и &lt;code&gt;objspace_xfree&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;objspace_xmalloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;rb_objspace_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orig_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objspace_malloc_prepare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;TRY_WITH_GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;malloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objspace_malloc_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;objspace_malloc_increase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MEMOP_TYPE_MALLOC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objspace_malloc_fixup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;objspace_xmalloc: %zu =&amp;gt; %p&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orig_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;objspace_xfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;rb_objspace_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#if CALC_EXACT_MALLOC_SIZE&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#endif&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objspace_malloc_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;objspace_xfree: %p&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;objspace_malloc_increase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MEMOP_TYPE_FREE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Чтобы не потонуть в море информации, я отказался от &lt;code&gt;siege&lt;/code&gt; и стал запускать &lt;code&gt;curl&lt;/code&gt;
вручную. Меня заинтересовало, сколько блоков утекает в процессе обработки одного запроса.
В код приложения была добавлена дополнительная middleware:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# lib/rack/memory_analyzer_middleware.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;jemal&amp;#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MemoryAnalyzerMiddleware&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;st1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Jemal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;-------------------------&amp;quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;st2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Jemal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;st1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:arenas&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bin2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;st2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:arenas&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;MAM BEFORE: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats_line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;MAM AFTER: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats_line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stats_line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:nmalloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ndalloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&amp;quot;Allocated &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:allocated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; Mallocs &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:nmalloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; Dallocs &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ndalloc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; M-D &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; Nreqs &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:nrequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# config/initializers/memanalyzer.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;#39;MEMANALYZER&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;rack/memory_analyzer_middleware&amp;#39;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unshift&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MemoryAnalyzerMiddleware&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Initializer (а не &lt;code&gt;config/application.rb&lt;/code&gt;) здесь нужен для того, чтобы middleware
встала в самую вершину стека.&lt;/p&gt;

&lt;p&gt;Запустив несколько раз &lt;code&gt;curl&lt;/code&gt;, я увидел что величина &lt;code&gt;M-D&lt;/code&gt; прирастает с каждым
запросом на 80-90. Вместе с этим на &lt;code&gt;stderr&lt;/code&gt; (и в лог благодаря &lt;code&gt;tee&lt;/code&gt;)
стала валиться куча информации о выделяемых и освобождаемых блоках. Выкусив последнюю часть
лога между &lt;code&gt;-------------------------&lt;/code&gt; и &lt;code&gt;MAM BEFORE ...&lt;/code&gt;, я прогнал её через
несложный скрипт:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;pp&amp;#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ARGV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;blocks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each_line&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/objspace_xmalloc: (\d+) =&amp;gt; 0x([0-9a-f]+)/&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_i&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/objspace_xfree: 0x([0-9a-f]+)/&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pp&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blocks&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И вот они, адреса потенциальных утечек:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed15c08500&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;288&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed15c0c4c0&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;296&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed15c0d500&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed15c0f440&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;264&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed15c0f580&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;304&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;# ещё 190 строк&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195cee40&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195cf840&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195cfd40&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195cffc0&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195d0b00&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195d0d80&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&amp;quot;7fed195e1640&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;312&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Оказалось довольно много блоков размера 312. О чём это говорит? Да ни о чём!
Конечно, захотелось посмотреть на содержимое – тут помог gdb, которым я подключился
прямо к живому процессу. Берём какой-нибудь адрес и смотрим, что там:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;(gdb) x/312xb 0x7f1138a83340
0x7f1138a83340: 0x34    0xe1    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83348: 0x97    0xe2    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83350: 0xb8    0xe3    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83358: 0xd9    0xe4    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83360: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7f1138a83368: 0xf4    0xe6    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83370: 0x98    0xe8    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83378: 0x3c    0xea    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a83380: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7f1138a83388: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7f1138a83390: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7f1138a83398: 0xd6    0xef    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a833a0: 0xf7    0xf0    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a833a8: 0x2f    0xf2    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a833b0: 0xd9    0xf3    0xde    0x39    0x11    0x7f    0x00    0x00
0x7f1138a833b8: 0x04    0xf5    0xde    0x39    0x11    0x7f    0x00    0x00&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Похоже на кучу указателей. В Intel используется little-endian, поэтому
старший байт в конце, и первая строка представляет из себя число
&lt;code&gt;0x7f1139dee134&lt;/code&gt;. Полезно? Что-то не очень.&lt;/p&gt;

&lt;p&gt;Тогда мне захотелось увидеть backtrace вызовов, которые выделяют эти блоки. На
просторах Интернетов был найден рабочий код для gcc:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;execinfo.h&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_depth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stack_depth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack_addrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_depth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack_strings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;stack_depth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backtrace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack_addrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_depth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stack_strings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backtrace_symbols&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack_addrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stack_depth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;Call stack from %s:%d:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stack_depth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;    %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stack_strings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack_strings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// malloc()ed by backtrace_symbols&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fflush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Условие в &lt;code&gt;objspace_xmalloc&lt;/code&gt; дополнилось вызовом этой чудо-функции:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;quot;objspace_xmalloc: %zu =&amp;gt; %p&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orig_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;print_trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__LINE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Я повторил всю последовательность действий, запустил скрипт обработки адресов,
а потом стал по одному искать выявленные адреса в логе, ведь рядом с ними печатались backtrace.&lt;/p&gt;

&lt;p&gt;И тут открылось…&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;objspace_xmalloc: 312 =&amp;gt; 0x7fed195e1640
Call stack from gc.c:7394:
    puma 2.11.3 (tcp://0.0.0.0:3000) [shuttlerock](+0x46e7a) [0x7fed31a6fe7a]
    puma 2.11.3 (tcp://0.0.0.0:3000) [shuttlerock](ruby_xmalloc+0x2c) [0x7fed31a70077]
    /home/od/.rbenv/versions/2.2.3-dbg/lib/ruby/gems/2.2.0/extensions/x86_64-linux/2.2.0-static/redcarpet-3.3.0/redcarpet.so(+0x7a00) [0x7fed17df0a00]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Около десятка адресов засветились с &lt;code&gt;redcarpet.so&lt;/code&gt;. &lt;strong&gt;Так вот кто съел всю нашу память!!!&lt;/strong&gt;
Это оказался гем &lt;a href=&quot;https://github.com/vmg/redcarpet&quot;&gt;redcarpet&lt;/a&gt; — рендерер Markdown в HTML.&lt;/p&gt;

&lt;h1 id=&quot;section-6&quot;&gt;Проверка и починка&lt;/h1&gt;

&lt;p&gt;Дальше было дело техники. В консоли запустил 10000 рендерингов — утечка подтверждается.
Сделал то же с гемом отдельно, без Rails – есть утечка!&lt;/p&gt;

&lt;p&gt;Единственное место, где в native code гема выделялась память Ruby-средствами, нашлось
в функции &lt;code&gt;rb_redcarpet_rbase_alloc&lt;/code&gt;, представляющей собой конструктор класса
&lt;code&gt;Redcarpet::Render::Base&lt;/code&gt;, написанный на C. Эта функция выделяла память под структуру,
которая при сборке мусора не освобождалась. Быстрый гуглинг выявил &lt;a href=&quot;http://tenderlovemaking.com/2010/12/11/writing-ruby-c-extensions-part-2.html&quot;&gt;пример
корректного написания такого конструктора&lt;/a&gt; в блоге tenderlove. Фикс оказался &lt;a href=&quot;https://github.com/vmg/redcarpet/pull/516/files&quot;&gt;простым&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Бинго!&lt;/p&gt;

&lt;h1 id=&quot;section-7&quot;&gt;Выводы&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;Наверное, можно было не тратить две недели. Сейчас я бы точно потратил меньше времени.&lt;/li&gt;
  &lt;li&gt;Ложные пути уводят в сторону — приходится возвращаться. Кроме возни с дампами, я потратил некоторое
время на попытки настроить сборщик мусора с помощью переменных окружения. Единственное,
что мне из этого пригодилось: &lt;code&gt;RUBY_GC_HEAP_INIT_SLOTS=1000000&lt;/code&gt;. С такой настройкой
наше приложение полностью влезает в кучу.&lt;/li&gt;
  &lt;li&gt;Кажется, в наше время можно отладить всё, что угодно. Количество полезных утилит и библиотек огромно.
Если не получается, нужно просто пробовать дальше.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;ps&quot;&gt;P.S.&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;15 сентября 2015&lt;/em&gt;. &lt;a href=&quot;/2015/09/15/no-memory-leak.html&quot;&gt;Здесь&lt;/a&gt; можно
посмотреть, как выглядит график памяти, когда утечки нет.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;23 сентября 2015&lt;/em&gt;. А вот &lt;a href=&quot;http://www.be9.io/2015/09/21/memory-leak/&quot;&gt;перевод&lt;/a&gt;
поста в &lt;a href=&quot;http://www.be9.io&quot;&gt;англоязычной версии&lt;/a&gt; блога.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;29 сентября 2015&lt;/em&gt;. &lt;a href=&quot;/2015/09/29/redcarpet-fix-released.html&quot;&gt;Вышел фикс для Redcarpet&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sat, 12 Sep 2015 13:40:38 +0000</pubDate>
        <link>http://be9.ru/2015/09/12/memory-leak.html</link>
        <guid isPermaLink="true">http://be9.ru/2015/09/12/memory-leak.html</guid>
        
        
      </item>
    
      <item>
        <title>Новый блог</title>
        <description>&lt;p&gt;Я решил заново активировать свой технологический блог, перенеся его на новую площадку.&lt;/p&gt;

&lt;p&gt;Старые посты пусть остаются на &lt;a href=&quot;http://be9.tumblr.com/&quot;&gt;Tumblr&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 11 Sep 2015 17:20:38 +0000</pubDate>
        <link>http://be9.ru/2015/09/11/welcome-to-jekyll.html</link>
        <guid isPermaLink="true">http://be9.ru/2015/09/11/welcome-to-jekyll.html</guid>
        
        
      </item>
    
  </channel>
</rss>
