- PVSM.RU - https://www.pvsm.ru -
От переводчика: Это первая статья из цикла о Node.js [1] от команды Mozilla Identity, которая занимается проектом Persona [2]. Как клиентская, так и серверная часть Persona написаны на JavaScript. В ходе работы команда проекта создала несколько инструментов на все случаи жизни — от локализации до отладки, управления зависимостями и многого другого. В этой серии статей разработчики Mozilla делятся с сообществом своим опытом и этими инструментами, которые пригодятся любому, кто пишет высоконагруженный сервис на Node.js.
Первая статья цикла посвящена распространённой проблеме Node.js — утечкам памяти, особенностям утечек в высоконагруженных проектах и библиотеке node-memwatch, которая помогает найти и устранить такие утечки в Node.
Вы можете спросить, зачем вообще отслеживать утечки памяти? Неужели нет более важных дел? Почему бы просто не перезапускать процесс время от времени, или просто добавить памяти на сервер? Есть три причины, по которым устранять утечки всё-таки важно:
В сложном приложении есть много мест, где могут возникать утечки. Наверное, самое известное и наболевшее место — замыкания. Так как замыкания могут долго хранить ссылки на переменные из их области видимости, они становятся самым частым местом протечки.
Некоторые утечки можно рано или поздно обнаружить, просто поискав их в коде, но в асинхронном мире Node мы постоянно создаём множество замыканий в виде колбэков. И если они отрабатывают медленнее, чем создаются, фрагменты памяти каждого колбэка нагромождаются так, что код, который вовсе не выглядит текущим, ведёт себя как текущий. Такие утечки отследить гораздо труднее.
Приложение также может течь из-за ошибки в чужом коде, от которого оно зависит. Иногда вы можете найти место у себя в программе, вызывающее утечку, но бывает так, что вам остаётся только растерянно глядеть на свой идеально отлаженный код, недоумевая, как вообще он может течь?
Именно такие, трудные для отслеживания, утечки создали потребность в node-memwatch. Легенда гласит, что давным-давно, несколько месяцев назад, наш программист Ллойд Хилайель заперся в чулане на два дня, пытаясь выследить утечку, которая проявлялась только под очень большой нагрузкой (кстати, он автор следующей статьи в этой серии, посвящённой как раз нагрузочному тестированию).
Через два дня поисков он обнаружил, что искомая ошибка была в ядре Node: обработчики событий в http.ClientRequest не освобождали память как следует (патч для исправления этой ошибки добавлял всего два маленьких, но очень важных символа [3] в код Node.js). Перенесённые мучения заставили Ллойда написать инструмент, который помогал бы искать подобные утечки.
Для поиска утечек в приложениях Node.js уже есть хорошая и постоянно растущая подборка инструментов. Вот некоторые из них:
Нам нравятся эти и многие другие инструменты, но ни один из них не подходил идеально для нашего окружения. Web Inspector великолепен для приложений в разработке, но труден в использовании на боевом, работающем приложении, особенно когда используется много серверов и процессов. Поэтому с его помощью трудно ловить ошибки, которые проявляются не сразу и только под большой нагрузкой. Такие инструменты, как dtrace или libumem тоже прекрасны, но не работают во всех ОС.
Нам нужна была кроссплатформенная библиотека для отладки, не требующая никаких дополнительных инструментов, с помощью которой мы могли бы находить места и причины утечек памяти. И поэтому мы написали node-memwatch [9].
Он предоставляет три вещи:
'leak'
memwatch.on('leak', function(info) {
// Взглянуть на info, и выяснить, есть ли утечка
});
'stats'
var memwatch = require('memwatch');
memwatch.on('stats', function(stats) {
// изучить статистику использования памяти после сборки мусора
});
var hd = new memwatch.HeapDiff();
// код приложения ...
var diff = hd.end();
Кроме того, есть функция для принудительного вызова сборщика мусора, что может оказаться полезным при тестировании. То есть всё-таки четыре вещи, а не три.
var stats = memwatch.gc();
node-memwatch может выдавать статистику использования кучи непосредственно после окончания работы сборщика мусора, до того, как выделяется память любым новым объектам. Для этого используется хук V8::AddGCEpilogueCallback.
В статистику включены поля:
Вот пример, который показывает, как выглядит изменение статистики использования памяти у подтекающего приложения. Зигзагообразная зелёная линия показывает использование памяти по данным process.memoryUsage(), а красная — значение current_base из статистики node-memwatch. Внизу слева приведены дополнительные данные.
-2.png)
Обратите внимание, как часто происходит инкрементальная сборка мусора. Это тревожный знак, указывающий на то, что V8 приходится попотеть, вычищая память.
Мы используем простую эвристику чтобы предупредить о возможном появлении утечки. Если после пяти сборок мусора подряд использование памяти растёт, срабатывает событие 'leak'. Информация о возможной утечке выводится в удобной читабельной форме:
{ start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr' }
Наконец, node-memwatch умеет сравнивать снимки кучи, включающие имена количество объектов, занимающих память. Diff помогает найти нарушителей.
var hd = new memwatch.HeapDiff();
// Код приложения ...
var diff = hd.end();
Содержимое объекта diff выглядит так:
{
"before": {
"nodes": 11625,
"size_bytes": 1869904,
"size": "1.78 mb"
},
"after": {
"nodes": 21435,
"size_bytes": 2119136,
"size": "2.02 mb"
},
"change": {
"size_bytes": 249232,
"size": "243.39 kb",
"freed_nodes": 197,
"allocated_nodes": 10007,
"details": [
{
"what": "Array",
"size_bytes": 66688,
"size": "65.13 kb",
"+": 4,
"-": 78
},
{
"what": "Code",
"size_bytes": -55296,
"size": "-54 kb",
"+": 1,
"-": 57
},
{
"what": "LeakingClass",
"size_bytes": 239952,
"size": "234.33 kb",
"+": 9998,
"-": 0
},
{
"what": "String",
"size_bytes": -2120,
"size": "-2.07 kb",
"+": 3,
"-": 62
}
]
}
}
HeapDiff вызывает сборщик мусора перед тем, как собирать статистику, чтобы в данных не было слишком много ненужной чепухи. При этом событие 'stats' не возникает, так что вы можете спокойно вызывать HeapDiff внутри обработчика 'stats'.
Вот пример статистики, в которую мы добавили перечень объектов, занимающих больше всего места в куче
-3.png)
Возможности node-memwatch:
Мы хотим большего. В частности, хотим, чтобы node-memwatch умел приводить конкретные данные протекающих объектов — имена переменных, индексы массивов, куски кода замыканий.
Мы надеемся, что node-memwatch будет полезен вам при отладке подтекающих приложений Node.js, вы форкнете его и примете участие в его улучшении.
Продолжение следует...
Автор: ilya42
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/node-js/44467
Ссылки в тексте:
[1] цикла о Node.js: https://hacks.mozilla.org/category/a-node-js-holiday-season/
[2] Persona: http://ru.wikipedia.org/wiki/Mozilla_Persona
[3] два маленьких, но очень важных символа: https://github.com/vvo/node/commit/e138f76ab243ba3579ac859f08261a721edc20fe
[4] node-mtrace: https://github.com/Jimbly/node-mtrace
[5] node-heap-dump: https://github.com/davepacheco/node-heap-dump
[6] v8-profiler: https://github.com/dannycoates/v8-profiler
[7] руководство по утечкам памяти: https://github.com/felixge/node-memory-leak-tutorial
[8] поиска и устранения утечек в Node.js: http://dtrace.org/blogs/bmc/2012/05/05/debugging-node-js-memory-leaks/
[9] node-memwatch: https://github.com/lloyd/node-memwatch
[10] Источник: http://habrahabr.ru/post/195494/
Нажмите здесь для печати.