На счет горячего обновления кода я, пожалуй, погорячился

17 июня 2013

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

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

Обе проблемы решаются при помощи горячего обновления кода. Если вы внесли изменения в модуле some_module.erl, можно просто ввести в REPL команду:

c("path/to/some_module.erl", [export_all, {parse_transform, lager_transform}, {i, "some/include/path"}]).

Код программы обновляется, и при этом ее не приходится перезапускать или полностью перекомпилировать. Параметры компиляции, передаваемые вторым аргументом, не обязательны, но часто — желательны. Параметр «export_all» говорит компилятору экспортировать из модуля все имеющиеся функции. Тем самым мы сможем вызывать их из REPL. Параметр «i» задает путь к include-файлам. Параметр «parse_transform» задает parse_transform’ер, применяемый при компиляции. В данном примере в проекте используется lager. Параметры могут использоваться многократно.

Все это автоматизировано и снабжено куда более простым интерфейсом в sync.

Второй случай — выкатка хотфиксов. Обычно в этом случае просто выкатывается новая версия приложения, после чего программа перезапускается. Как и при разработке, здесь возникает проблема долгой инициализации после старта. Помимо этого, временная недоступность нашего приложения может существенно затронуть работу других приложений. Например, после восстановления связи может происходить обмен большими объемами данных.

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

erl -pa ./deps/lager/ebin -name hotfix -setcookie prod-cookie

Компилируем модуль:

c("path/to/some_module.erl", [{parse_transform, lager_transform}, {i, "deps"}, {i, "apps"}]).

Соединяемся с нодой в боевом окружении:

net_kernel:connect('myapp@example.com').

Получаем байткод модуля:

{Mod, Bin, File} = code:get_object_code(some_module).

Заменяем байткод модуля на удаленной ноде:

rpc:call('myapp@example.com', code, load_binary, [Mod, File, Bin]).

Готово! Обновленная версия модуля проживет до рестарта ноды. Таким образом, выкатку новой версии приложения такое обновление не отменяет.

Альтернативный вариант выкатки хотфикса заключается в копировании пропатченного beam-файла на сервер и загрузке его через remsh с помощью вызова l(module_name). Узнать, куда именно нужно копировать файл, поможет команда m(module_name). Эта же команда отображает время компиляции модуля, что позволяет убедиться, что его обновленная версия действительно загрузилась. Также вместо m(module_name) можно сказать module_name:module_info().

ВАЖНО! С хотфиксами нужно соблюдать крайнюю осторожность. Например, вы можете собрать модуль с зависимостями, версии которых немного отличаются от версий в продакте. Представьте, что у одной из зависимостей есть инклудник с определением рекордов или каких-то макросов. В этом случае приложение будет отлично работать на тестовом сервере (потому что все модули используют одинаковые определения макросов и рекордов). Но когда вы выкатите хотфис в продакт, приложение разорвет на много маленьких кусочков. По возможности всегда обновляйте приложение обычным способом, без хотфиксов.

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

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

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

Возможно, вам известна еще парочка случаев, когда горячее обновление кода оказывается полезным? Тогда непременно поведайте о них в комментариях!

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.