Alex L. Demidov

DevOps/SRE consultant

Hunt for the Bug

Spent three days last week hunting for mysterious bug which caused factory_girl factories randomly fail with Trait not registered: class message during full test suite run, but when you run all controller or model tests separately – everything is fine and all tests which were failing during full run worked perfectly.

At first I ignored this issue – I had just added two new factories, which coincidentally used class parameter in their definition to specify generated class explicitly, and I needed these factories to test code I was working on and I though that I’ll fix or just remove these factories later.

But as it usually happens it wasn’t simple as I thought. Suddenly I discovered that tests started failing with the same symptoms on common develop branch and not on only topic branch. And I broke tests already in two other places, so clean up was really needed.

First two days I spent trying to find out what happening in factory_girl internals using old-school print logging and later pry-debugger but without much success except that I was able to locate single spec file in spec/workers/ which caused failure of all consecutive factory calls. Then I started looking at git history trying to find commit which introduced this issue. Luckily, in spite of heavy rebasing and few backported commits, my master branch didn’t have this issue and I was able to pinpoint this to single commit. At first glance this commit looked almost innocent – it just extracted code from model and moved it to app/workers/. But there were two tests added to failing spec file and they were the tests which caused cascading failure of all remaining tests in suite. After reviewing the code under test I had found that real culprit was memory leak debugging code I quickly slapped in without running tests:

counts = Hash.new { 0 } ObjectSpace.each_object do |o| counts[o.class] += 1 end counts.reject! { |_k, v| v < 100 }

It seems that FactoryGirl::DefinitionsProxy undefines all methods including class and method_missing in this class adds any calls as traits to factory so walking through ObjectSpace and calling class on every object wreaks havoc on factories.

Сборка русской версии Movable Type

Для сборки руссифицированной версии Movable Type нам опять понадобятся исходники из svn-репозитария. Делаем чекаут, как описано в предыдущей статье о работе с файлом перевода и прикладываем к полученным исходникам патч patch-rubuild.gz, который добавляет возможность сборки русской версии.

Следующим шагом вносим в исходники все необходимые изменения для поддержки русского языка:

  • patch-rudate41.gz — добавляет русский формат дат
  • patch-rudirify41.gz — добавляет русские символы в таблицы преобразования заголовков статей в имена файлов (в отличии от патча Алексея Тутубалина изменяется также и Javascript код, а преобразование русских символов в латиницу сделано в соответствии с ГОСТ 7.79-2000)
  • patch-nofollow.gz — добавляет поддержку тэга необходимого для российских поисковиков (автор Алексей Тутубалин)
  • patch-monday-mt41.gz — делает первым днем недели в календаре понедельник (автор Алексей Тутубалин)

После внесения изменений в код и шаблоны осталось добавить собственно файл перевода lib/MT/L10N/ru.pm ( перевод проекта movable-type.ru, либо мой, менее полный перевод), файл стилей mt-static/styles_ru.css и два HTML-файла — index.html.ru и readme.html.ru (они есть в репозитарии русской версии Movable Type на Google Code). Файл перевода необходимо обработать в соответствии с инструкциями в предыдущей статье. В завершении можно изменить некоторые настройки по умолчанию (часовой пояс, кодировки, ссылки на новости, портал и техподдержку) для собираемого пакета, отредактировав файл build/mt-dists/ru.mk.

Сделав все необходимые изменения выполняем сборку пакета MTOS:

env LANG=C ./build/exportmt.pl --local --pack=MTOS --lang=ru --prod

В результате выполнения данной команды получим два архива с русской версией Movable Type — MTOS-4.1-ru.tar.gz и MTOS-4.1-ru.zip.

Работа с файлом перевода Movable Type

Совпадение, но через два дня после публикации последней статьи на тему руссификации Movable Type, вышел перевод от проекта movable-type.ru. В сравнении с моим переводом, он более полный, но делался, судя по всему, по французскому файлу трансляции от версии 4.0 и в нем отсутствует более 300 строк, а также полтора десятка строк остались на французском.

Для того, чтобы синхронизировать файл перевода с текущим релизом (а также будущими релизами) и добавить в него отсутствующие строки, нужен полный комплект исходников Movable Type из svn-репозитария — в них пристутствует набор скриптов для манипуляций с файлами переводов (для работы скриптов нужен perl и набор unix-утилит - shell, awk, find и т.д.). Сначала делаем чекаут исходников из репозитария:

svn co https://code.sixapart.com/svn/movabletype/latest mtos-latest

После выполнения данной команды в каталоге mtos-latest будет три подкаталога: dev, stable и release. Нам нужен подкаталог release, в котором находится последний релиз Movable Type с которым мы будем работать. Первым делом кладем наш файл перевода ru.pm в подкаталог lib/MT/L10N/. Следующим шагом запускаем собственно генерацию обновленного файла перевода из каталога release:

sh build/l10n/make-l10n ru

В результате работы данного скрипта в каталоге /tmp будет сгенерировано четыре файла:

  • ru-base.pm — файл со всеми строками трансляции (в том числе дублирующиеся строки)
  • ru-nodupe.pm — файл с удаленными дубликатами строк
  • ru-old.pm —строки трансляции которые более не используются
  • ru.pm —новый файл перевода

В новом файле перевода /tmp/ru.pm будут добавлены отсутствующие строки перевода (отмечены комментарием «Translate - New») и будут отчемены строки с регистром перевода отличающимся от оригинала — например, переводы следующего вида:

'video' => 'Видео'

Этот файл уже готов к использованию, но для полноценной поддержки русского языка я сделал дополнительный фильтр ru-filter.pl, через который надо пропустить файл перевода:

cat /tmp/ru.pm | perl ru-filter.pl > lib/MT/L10N/ru.pm

Данный фильтр делает следующее:

  • отмечает комментарием «# Translate - No translation» непереведенные строки, в которых оригинал и перевод совпадают
  • отмечает комментарием «# Translate - No russian char» строки перевода в которых не встречаются русские буквы
  • отключает строки с пустым переводом
  • добавляет perl’овый код для правильной поддержки множественного числа

Поддержка множественного числа сделана через переопределение функцииquant из Locale::Maketext. Теперь второй и третий параметры этой функции принимают формы слова для значений числительного от 2 до 4 и от 5 до 0 соответственно. Таким образом можно писать «[quant, _1,минута,минуты,минут]» и соответствующий текст будет генерироваться как «1 минута, 2 минуты, 5 минут» вместо «1 минута, 2 минут., 3 минут.»

Руссификация Movable Type 4.1

Продолжаем работу с Movable Type. Руссификацию пришлось делать в два захода. На первом заходе, после предварительного гугленья, был применен рецепт для руссификации Movable Type версии 4 от Алексея Тутубалина с небольшими изменениями:

  • приложен патч patch-monday-mt41.gz для того чтобы в календаре неделя начиналась с понедельника
  • приложен патч patch-dirify.gz для конвертации русских символов в заголовках статей в латиницу при генерации имени файла
  • приложен патч patch-nofollow.gz для поддержки тэга для Яндекса

Патч patch-rudate.gz не прикладывал, но посмотрев в него, сделал свой. Вместо того, чтобы использовать итальянский формат, добавил русский в список форматов, изменив lib/MT/Util.pm и tmpl/cms/cfg_entry.tmpl. Изменен также и формат отображения — вместо 24.03.2008 будет Март, 24 2008. Патч patch-rudate41.gz прилагается.

Далее последовал наиболее нудный этап — перевод шаблонов. Разбор со скриптом Алексея Тутубалина для экспорта/импорта шаблонов я оставил на потом (этот скрипт был сделан до выхода версии 4.1 и неизвестно как он с ней работает, к тому же он требует установки плугина TemplateInstaller), поэтому шаблоны перевел вручную через админку Movable Type. Хотя шаблонов около полусотни, но сами они немногословны и переводить можно не все, а только те, которые реально используются. Поэтому первые результаты появился сравнительно быстро, но в процессе перевода увлекся файн-тюнингом оформления и внес слишком много изменений в шаблоны, так что собравшись создавать блог для своей SO, обнаружил что мне надо либо вычищать свое оформление, либо возвращаться к стандартным шаблонам и переводить их опять с нуля.

Немного поразмышляв, решил выбрать другой подход. В Movable Type есть в наличии полная инфраструктура для локализации базирующаяся на perl’овом Locale::Maketext и документация рекомендуют именно этот подход. Проект movable-type.ru после выхода версии 4.0 в августе 2007 вроде бы огранизовал коллективную попытку сделать русский перевод и месяц назад даже заявлена 90% готовность, но реальных результатов пока не наблюдается. Конечно объем текста в более чем 4 тысячи строк на первый взгляд выглядит ужасающе, но ничто не мешает переводить текст по частям, например, для начала только те строки, которые используются в самих блогах, а не в админке.

За пару вечеров у меня был готов перевод следующих компонентов используемых в опубликованном блоге:

  • шаблоны default_templates/*.mtml которые используются при генерации шаблонов нового блога
  • шаблоны plugins/WidgetManager/default_widgets/*.mtml для widget’ов
  • php-скрипты динамической публикации

Все это занимает где-то чуть более 600 строк ( ru-published-only.pm). Данный файл необходимо переименовать в ru.pm и положить в lib/MT/L10N/, при этом русский язык автоматически станет доступен для выбора в профиле пользователя. Стоит иметь в виду, что перевод фраз в шаблонах на русский производится в момент генерации шаблонов и только если у пользователя выбран русский язык в профиле. Если блог уже создан, то можно перегенерировать шаблоны заново, зайдя в своем блоге через меню «Design» → «Manage Templates» → «Refresh Blog Templates». При перегенерации большинство новых руссифицированных шаблонов будут иметь русские имена и у меня лично слетели Archive Maps и наборы widget’ов. Нужно будет их пересоздать, предварительно удалив все старые шаблоны и widget’ы.

Все остальное пока находится в процессе перевода, хотя наиболее часто используемые части админки уже переведены практически полностью (можно скачать Файл перевода). В качестве основы для этого файла взят es.pm, поэтому непереведенные строки будут по испански. Также добавлен скрипт mt_ru.js который содержит локализованные строки для JavaScript и локализованную таблицу для динамической dirtif’икации заголовков в имена файлов. Файл стилей styles_ru.css содержит одну строчку — модификация ширины колонок в виджете календаря.

По мере дальнейшего перевода я планирую выкладывать обновленную версию файла ru.pm, следите за обновлениями.

Ускорение Movable Types ( Apache и FastCGI )

После установки Movable Type обнаружилось что работа с его админкой (CMS) достаточно некомфортна из-за её существенной тормознутости — время загрузки главной страницы составляет около 15 секунд. Впрочем, это не удивительно, поскольку админка реализована как достаточно тяжелый perl’овый скрипт со множеством зависимостей которые при работе в режиме стандратного CGI подгружаются при каждом HTTP-запросе.

Первой мыслью было попробовать запустить его под mod_perl, который у меня уже есть, но как оказалось Movable Type поддерживает только mod_perl 1.x, а я уже третий год как переехал на Apache 2.x. Поэтому пришлось обратиться к альтернативном варианту — FastCGI, отличие которого от классического CGI именно в том, что скрипт обслуживает не один, а множество HTTP-запросов, чем минимизируются временные затраты на запуск и инициализацию скрипта.

Сначала обеспечиваем поддержку FastCGI в Apache — это делается с помощь модуля mod_fastcgi (или альтернативная реализация mod_fcgid). Ставим модуль и подключаем его в httpd.conf.настройки для mod_fastcgi:

LoadModule fastcgi_module libexec/apache22/mod_fastcgi.so
<IfModule mod_fastcgi.c>
  AddHandler fastcgi-script .fcgi
  FastCgiIpcDir /var/run/fcgidsock/
</IfModule>

настройки для mod_fcgid:

LoadModule fcgid_module libexec/apache22/mod_fcgid.so
<IfModule mod_fcgid.c>
  AddHandler fcgid-script .fcgi 
  SocketPath /var/run/fcgidsock/
</IfModule>

Сам Movable Type начиная с версии 4 поддерживает работу через FastCGI из коробки, поэтому для его настойки достаточно указать для каталога где находятся скрипты mt*.cgi следующую директиву:

AddHandler fcgid-script .cgi

или

AddHandler fastcgi-script .cgi

После включения данной опции тестирование с помощью ApacheBench показало на моей домашней машинке ускорение более чем на порядок — с 0.75 запросов в секунду до 15. Но особого ускорения при работе с админкой не прозошло. Налицо классический случай «premature optimization», которая «root of all evil» © C.A.R. Hoare.

Поэтому пришлось все-таки более внимательно посмотреть в YSlow и последовательно применить предлагаемые рекомендации. Основная проблема оказалась в большом количестве объектов на странице и как следствие, большом количестве HTTP запросов (около 50 в данном случае), причем большинство запросов были If-Modified-Since на файлы из mt-static, в осовном изображения. Лечиться это добавлением заголовков Expires для каталога mt-static с помощью mod_expires:

ExpiresActive On
ExpiresDefault "access plus 1 month"

С включением данной опции броузер перестал обращаться к каждой картинке при каждой загрузке страницы, что сократило количество запросов с полусотни до ровно одного — обращение собственно к mt.cgi. Общий объем передаваемых данных также сократился с 100Kb (уже с учетом включеной компрессии для text/*) до 10Kb. Общее же время загрузки страницы сократилось с 15 секунд до 5-6 секунд по таймеру YSlow и до 3-4 секунд по секундомеру с отключеным FireBug‘ом и YSlow.

Достигнутый результат меня вполне устроит и тему оптимизации можно отложить. Дальше нужно будет заниматься уже переводом на русский и переносом на боевой сервер в тесный jail. А пока в процессе первых дней эксплуатации выяснилось пара нюансов. Во-первых, не все скрипты MT работают в режиме FastCGI, поэтому лучше использовать следующие настройки подсмотренные в документации Movable Type:

<FilesMatch "^mt(?:-(?:comments|search|tb|view))?\.cgi$">
     AddHandler fcgid-script .cgi
</FilesMatch>

Во-вторых, процесс mt.cgi со временем может разрастаться до достаточно неприличиных размеров (более 100 Mb) и поэтому лучше настроить mod_fastcgi так, чтобы он переодические его перезапускал:

FastCgiConfig -maxProcesses 10 -killInterval 3600

и для mod_fcgid

ProcessLifeTime 3600
   MaxProcessCount 10
   MaxRequestsPerProcess 500

И в третьих, при работе через FastCGI возникают неудобства с изменением кода MT на ходу — необходимо постоянно перезапускать mt.cgi чтобы он перечитал измененные модули.

Продолжение следует