Ведение Node.js быстро: инструменты, методы и советы для создания высокопроизводительных Узлов

Если вы строили что-нибудь с Node.js достаточно долго, то вы, несомненно, испытали боль неожиданных проблем скорости. JavaScript — это насыщенный, асинхронный язык. Это может сделать рассуждения о производительности сложно,как станет очевидным. Растущая популярность Node.js обнажила необходимость в инструментарии, методах и мышлении, подходящих для ограничений сервера JavaScript.

Когда дело доходит до производительности, то, что работает в браузере, не обязательно подходит Node.js. Итак, как мы можем убедиться, что реализация Node.js быстра и подходит для целей? Давайте пройдем через практический пример.

Инструменты

Узла является очень универсальной платформой, но одним из преобладающих приложений является создание сетевых процессов. Мы сосредоточимся на профилировании наиболее распространенных из них: http веб-серверов.

Нам понадобится инструмент, который может взорвать сервер с большим количеством запросов при измерении производительности. Например, мы можем использовать AutoCannon:

npm install -g autocannon

Другие хорошие инструменты бенчмаркинга HTTP включают Apache bench (ab) и wrk2, но AutoCannon написан в узлах, обеспечивает аналогичное (а иногда и большее) давление нагрузки, и очень легко установить на Windows, Linux и Mac OS X.

После того как мы установили базовые измерения производительности, если мы решим, что наш процесс может быть быстрее, нам понадобится какой-то способ диагностировать проблемы с процессом. Отличным инструментом для диагностики различных проблем производительности является клиника узлов,которая также может быть установлена с npm:

npm install -g clinic

Это фактически устанавливает набор инструментов. Мы будем использовать клиники доктор и клиника пламя (обертка вокруг 0x), как мы идем.

Примечание: Для этого практического примера нам понадобится узла 8.11.2 или выше.

Кодекс

Наш пример — это простой сервер REST с одним ресурсом: большая полезная нагрузка JSON, выставленная в качестве маршрута /seed/v1 GET. Сервер представляет собой app папку, которая состоит из файла package.json (в зависимости restify 7.1.0 от), файла index.js и файла util.js.

Файл index.js для нашего сервера выглядит так:

'use strict'

const restify = require('restify')
const { etagger, timestamp, fetchContent } = require('./util')()
const server = restify.createServer()

server.use(etagger().bind(server))

server.get('/seed/v1', function (req, res, next) {
  fetchContent(req.url, (err, content) => {
    if (err) return next(err)
    res.send({data: content, url: req.url, ts: timestamp()})
    next()
  })
})

server.listen(3000)

Этот сервер является репрезентативным для общего случая обслуживания динамического контента, кэшированного клиентом. Это достигается с помощью etagger промежуточного посуды, которая вычисляет ETag заголовок для последнего состояния содержимого.

Файл util.js предоставляет части реализации, которые обычно используются в таком сценарии, функцию для получения соответствующего содержимого из бэкэнда, промежуточного посуды etag и функции метки времени, которая поставляет метки времени на поминутной основе:

'use strict'

require('events').defaultMaxListeners = Infinity
const crypto = require('crypto')

module.exports = () => {
  const content = crypto.rng(5000).toString('hex')
  const ONE_MINUTE = 60000
  var last = Date.now()

  function timestamp () {
    var now = Date.now()
    if (now — last >= ONE_MINUTE) last = now
    return last
  }

  function etagger () {
    var cache = {}
    var afterEventAttached = false
    function attachAfterEvent (server) {
      if (attachAfterEvent === true) return
      afterEventAttached = true
      server.on('after', (req, res) => {
        if (res.statusCode !== 200) return
        if (!res._body) return
        const key = crypto.createHash('sha512')
          .update(req.url)
          .digest()
          .toString('hex')
        const etag = crypto.createHash('sha512')
          .update(JSON.stringify(res._body))
          .digest()
          .toString('hex')
        if (cache[key] !== etag) cache[key] = etag
      })
    }
    return function (req, res, next) {
      attachAfterEvent(this)
      const key = crypto.createHash('sha512')
        .update(req.url)
        .digest()
        .toString('hex')
      if (key in cache) res.set('Etag', cache[key])
      res.set('Cache-Control', 'public, max-age=120')
      next()
    }
  }

  function fetchContent (url, cb) {
    setImmediate(() => {
      if (url !== '/seed/v1') cb(Object.assign(Error('Not Found'), {statusCode: 404}))
      else cb(null, content)
    })
  }

  return { timestamp, etagger, fetchContent }

}

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

Чтобы получить полный источник для нашей отправной точки, медленный сервер можно найти здесь.

Профилирования

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

В одном терминале, в app папке , мы можем запустить:

node index.js

В другом терминале мы можем профилировать его так:

autocannon -c100 localhost:3000/seed/v1

Это откроет 100 одновременных подключений и бомбардирует сервер запросами на десять секунд.

Результаты должны быть чем-то похожим на следующие (Запуск 10s тест http://localhost:3000/seed/v1 — 100 соединений):

Стат Авг Стдев Макс
Задержка (ms) 3086.81 1725.2 5554
Req/Sec 23.1 19.18 65
Байты/Сек 237,98 кВ 197,7 кВ 688,13 кВ
231 запросов в 10s, 2.4 MB читать
Результаты будут варьироваться в зависимости от машины. Однако, учитывая, что сервер Node.js «Hello World» легко способен выполнять тридцать тысяч запросов в секунду на той машине, которая давала эти результаты, 23 запроса в секунду со средней задержкой, превышающей 3 секунды, удручает.

Диагностики

Обнаружение проблемной зоны

Мы можем диагностировать приложение с помощью одной команды, благодаря команде Clinic Doctor’s-on-port. В app папке мы запускаем:

clinic doctor --on-port=’autocannon -c100 localhost:$PORT/seed/v1’ -- node index.js

Это позволит создать HTML файл, который будет автоматически открыт в нашем браузере, когда профилирование завершено.

Результаты должны выглядеть примерно так:

Clinic Doctor has detected an Event Loop issue
Результаты клиники Доктор

Доктор говорит нам, что у нас, вероятно, был вопрос о петле событий.

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

Мы видим, что процессор находится на уровне или выше 100%, так как процесс прилагает все усилия для обработки запросов в очереди. В этом случае в javaScript-движке JavaScript узла (V8) используются два ядра процессора, поскольку машина многоядерная, а V8 — два потока. Один для цикла событий, а другой для сбора мусора. Когда мы видим, что в некоторых случаях процессор набирает до 120%, процесс собирает объекты, связанные с обрабатываемыми запросами.

Мы видим, что это коррелирует в графике Памяти. Твердая линия в диаграмме памяти — это метрика Heap Used. Каждый раз, когда есть всплеск в процессоре мы видим падение в куче Используется линия, показывая, что память в настоящее время deallocated.

Активные ручки не зависят от задержки петли событий. Активная ручка — это объект, представляющий либо ввог или разъем или рукоятка файла), либо таймер (например, setInterval ). Мы поручили AutoCannon, чтобы открыть 100 соединений ( -c100 ). Активные ручки остаются неизменным подсчетом 103. Остальные три являются ручками для STDOUT, STDERR и ручкой для самого сервера.

Если мы нажмем панель Рекомендаций в нижней части экрана, мы увидим что-то вроде следующего:

Clinic Doctor recommendations panel opened
Просмотр конкретных рекомендаций по выпуску

Краткосрочное смягчение

Анализ корневой причины серьезных проблем с производительностью может занять много времени. В случае живого развернутого проекта стоит добавить защиту от перегрузок на серверы или службы. Идея защиты от перегрузок заключается в мониторинге задержки цикла событий (среди прочего) и в ответ на вопрос «503 Служба недоступна» в случае пройдения порога. Это позволяет балансирустам нагрузки не перейти на другие экземпляры, или в худшем случае означает, что пользователям придется обновить. Модуль защиты от перегрузок может обеспечить минимальные накладные расходы для Express, Koa и Restify. Счастливый фреймвор консора имеет настройки конфигурации нагрузки, которая обеспечивает ту же защиту.

Понимание проблемной области

Как поясняется в кратком пояснении в Clinic Doctor, если цикл событий задерживается до уровня, который мы наблюдаем, очень вероятно, что одна или несколько функций «блокируют» петлю события.

Особенно важно, когда Node.js распознает эту первичную характеристику JavaScript: асинхронные события не могут произойти до завершения в настоящее время выполнения кода.

Вот почему setTimeout не может быть точным.

Например, попробуйте запустить следующее в DevTools браузера или узла REPL:

console.time('timeout')
setTimeout(console.timeEnd, 100, 'timeout')
let n = 1e7
while (n--) Math.random()

В результате измерения времени никогда не будет 100ms. Скорее всего, он будет находиться в диапазоне от 150 мс до 250 мс. setTimeoutЗапланированная асинхронная операция (), но в console.timeEnd настоящее время выполняющий код еще не завершен; есть еще две строки. В настоящее время код выполнения известен как текущий «тик». Для того, чтобы тик был завершен, Math.random необходимо вызвать десять миллионов раз. Если это занимает 100 мс, то общее время до тайм-аута разрешится будет 200ms (плюс как долго он принимает setTimeout функцию на самом деле очереди тайм-аут заранее, как правило, пару миллисекунд).

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

Пример сервера имеет некоторый код, который блокирует цикл событий, так что следующий шаг заключается в том, чтобы найти этот код.

Анализ

Одним из способов быстрой идентификации плохо работающего кода является создание и анализ графика пламени. График пламени представляет функциональные вызовы как блоки, сидящие друг на друге — не с течением времени, а в совокупности. Причина, по которой это называется «пламя график» потому, что он обычно использует оранжевый красный цветовой гамме, где красный блок «горячее» функция, то есть, тем больше он, вероятно, будет блокировать цикл события. Захват данных для графика пламени осуществляется с помощью выборки процессора — это означает, что снимок функции, которая в настоящее время выполняется, и это стек берется. Тепло определяется процентом времени во время профилирования, что данная функция находится в верхней части стека (например, функция в настоящее время выполняется) для каждого образца. Если это не последняя функция, когда-либо вызванная в этом стеке, то, скорее всего, блокирует цикл событий.

Давайте использовать clinic flame для создания графика пламени примерного приложения:

clinic flame --on-port=’autocannon -c100 localhost:$PORT/seed/v1’ -- node index.js

Результат должен открыться в нашем браузере с чем-то вроде следующего:

Clinic’s flame graph shows that server.on is the bottleneck
Визуализация огнеграфического графика клиники

Ширина блока отражает, сколько времени он провел на процессоре в целом. Три основных стеки можно наблюдать, занимая больше всего времени, все они выделения server.on в качестве горячей функции. По правде говоря, все три стеки одинаковы. Они расходятся, потому что при профилировании оптимизированные и неоптимизированные функции рассматриваются как отдельные кадры вызовов. Функции, закрепленные в приложении, * оптимизированы движком JavaScript, а функции ~ неоптимизированы. Если оптимизированное состояние не важно для нас, мы можем упростить график, нажав кнопку Слияния. Это должно привести к просмотру, аналогичному следующему:

Merged flame graph
Слияние графика пламени

С самого начала можно сделать вывод, что код-нарушитель находится в util.js файле кода приложения.

Медленная функция также является обработчиком событий: функции, ведущие к функции, являются частью основного events модуля, и server.on является резервным названием для анонимной функции, предоставляемой в качестве функции обработки событий. Мы также видим, что этот код не в том же тик, как код, который на самом деле обрабатывает запрос. Если бы это было, функции из http ядра, net и stream модули будут в стеке.

Такие основные функции могут быть найдены путем расширения других, гораздо меньших, частей графика пламени. Например, попробуйте использовать вход поиска в правом верхнем правом диапазоне uI для поиска send (название как, так restify и http внутренних методов). Он должен быть справа от графика (функции сортируются в алфавитном порядке):

Flame graph has two small blocks highlighted which represent HTTP processing function
Поиск графика пламени для функций обработки HTTP

Обратите внимание, насколько сравнительно малы все фактические блоки обработки HTTP.

Мы можем нажать один из блоков, выделенных в циане, который будет расширяться, чтобы показать функции, такие как writeHead и write в файле http’outgoing.js (часть http основной библиотеки узлов):

Flame graph has zoomed into a different view showing HTTP related stacks
Расширение графика пламени в соответствующие стеки HTTP

Мы можем нажать все стеки, чтобы вернуться к основному представлению.

Ключевым моментом здесь является то, что, даже если функция не находится в том server.on же тик, как фактический код обработки запроса, это по-прежнему влияет на общую производительность сервера, задерживая выполнение в противном случае выполнения кода.

Отладки

Из графика пламени мы знаем, что проблемная функция — это обработчик событий, передаваемый server.on в файле util.js.

Давайте взглянем:

server.on('after', (req, res) => {
  if (res.statusCode !== 200) return
  if (!res._body) return
  const key = crypto.createHash('sha512')
    .update(req.url)
    .digest()
    .toString('hex')
  const etag = crypto.createHash('sha512')
    .update(JSON.stringify(res._body))
    .digest()
    .toString('hex')
  if (cache[key] !== etag) cache[key] = etag
})

Хорошо известно, что криптография, как правило, дорого, как и сериализация JSON.stringify (), но почему они не появляются в графике пламени? Эти операции находятся в захваченных образцах, но они скрыты за cpp фильтром. Если мы нажмем cpp на кнопку, мы должны увидеть что-то вроде следующего:

Additional blocks related to C++ have been revealed in the flame graph (main view)
Выявление сериализации и криптографии кадров СЗ

Внутренние инструкции V8, касающиеся как сериализации, так и криптографии, теперь отображаются как самые горячие стеки и занимают большую часть времени. JSON.stringifyМетод напрямую вызывает код СЗ; именно поэтому мы не видим функцию JavaScript. В случае криптографии функции, как createHash и update находятся в данных, либо встроены (что означает, что они исчезают в объединенном представлении), либо слишком малы для визуализации.

Как только мы начинаем рассуждать о коде в etagger функции, может быстро стать очевидным, что он плохо спроектирован. Почему мы берем server экземпляр из контекста функции? Там очень много хэширования происходит, все это необходимо? В реализации также отсутствует If-None-Match поддержка заголовка, которая смягчает часть нагрузки в некоторых реальных сценариях, поскольку клиенты будут делать только запрос на освежение.

Давайте проигнорируем все эти точки на данный момент и проверить вывод о том, что фактическая работа, выполняемая в server.on действительно узкое место. Это может быть достигнуто путем установки server.on кода на пустую функцию и генерации нового фламента.

Измените etagger функцию на следующее:

function etagger () {
  var cache = {}
  var afterEventAttached = false
  function attachAfterEvent (server) {
    if (attachAfterEvent === true) return
    afterEventAttached = true
    server.on('after', (req, res) => {})
  }
  return function (req, res, next) {
    attachAfterEvent(this)
    const key = crypto.createHash('sha512')
      .update(req.url)
      .digest()
      .toString('hex')
    if (key in cache) res.set('Etag', cache[key])
    res.set('Cache-Control', 'public, max-age=120')
    next()
  }
}

Функция слушателя события передана server.on в настоящее время не-оп.

Давайте запустим clinic flame еще раз:

clinic flame --on-port='autocannon -c100 localhost:$PORT/seed/v1' -- node index.js

Это должно привести к производству огневого графика, аналогичного следующему:

Flame graph shows that Node.js event system stacks are still the bottleneck
График пламени сервера, когда server.on является пустой функцией

Это выглядит лучше, и мы должны были заметить увеличение запроса в секунду. Но почему код, излучающий события, так горяч? На данный момент мы ожидаем, что код обработки HTTP займет большую часть времени процессора, в этом случае нет ничего, что будет server.on выполнено.

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

Следующий подозрительный код в верхней части util.js может быть подсказкой:

require('events').defaultMaxListeners = Infinity

Давайте удалим эту строку и начнем наш процесс с --trace-warnings флага:

node --trace-warnings index.js

Если мы профилировать с AutoCannon в другом терминале, так:

autocannon -c100 localhost:3000/seed/v1

Наш процесс выводит что-то похожее на:

(node:96371) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 after listeners added. Use emitter.setMaxListeners() to increase limit
  at _addListener (events.js:280:19)
  at Server.addListener (events.js:297:10)
  at attachAfterEvent 
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/util.js:22:14)
  at Server.
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/util.js:25:7)
  at call
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:164:9)
  at next
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:120:9)
  at Chain.run
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:123:5)
  at Server._runUse
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:976:19)
  at Server._runRoute
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:918:10)
  at Server._afterPre
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:888:10)

Узла говорит нам, что к объекту сервера прикрепляется множество событий. Это странно, потому что есть boolean, что проверяет, если событие было прикреплено, а затем возвращается рано существенно сделать attachAfterEvent не-оп после первого события прилагается.

Давайте взглянем на attachAfterEvent функцию:

var afterEventAttached = false
function attachAfterEvent (server) {
  if (attachAfterEvent === true) return
  afterEventAttached = true
  server.on('after', (req, res) => {})
}

Условная проверка неверна! Он проверяет, является ли attachAfterEvent это правдой, а не afterEventAttached . Это означает, что к экземпляру прикрепляется новое событие server по каждому запросу, а затем все предыдущие прилагаемые события увольняются после каждого запроса. Вопиет!

Оптимизации

Теперь, когда мы обнаружили проблемные области, давайте посмотрим, сможем ли мы сделать сервер быстрее.

Низко висячие фрукты

Давайте вернем server.on код слушателя (вместо пустой функции) и используем правильное имя булеана в условной проверке. Наша etagger функция выглядит следующим образом:

function etagger () {
  var cache = {}
  var afterEventAttached = false
  function attachAfterEvent (server) {
    if (afterEventAttached === true) return
    afterEventAttached = true
    server.on('after', (req, res) => {
      if (res.statusCode !== 200) return
      if (!res._body) return
      const key = crypto.createHash('sha512')
        .update(req.url)
        .digest()
        .toString('hex')
      const etag = crypto.createHash('sha512')
        .update(JSON.stringify(res._body))
        .digest()
        .toString('hex')
      if (cache[key] !== etag) cache[key] = etag
    })
  }
  return function (req, res, next) {
    attachAfterEvent(this)
    const key = crypto.createHash('sha512')
      .update(req.url)
      .digest()
      .toString('hex')
    if (key in cache) res.set('Etag', cache[key])
    res.set('Cache-Control', 'public, max-age=120')
    next()
  }
}

Теперь мы проверяем наше исправление путем профилирования снова. Запустите сервер в одном терминале:

node index.js

Затем профиль с AutoCannon:

autocannon -c100 localhost:3000/seed/v1

Мы должны увидеть результаты где-то в диапазоне 200-кратное улучшение (Запуск 10s тест http://localhost:3000/seed/v1 — 100 соединений):

Стат Авг Стдев Макс
Задержка (ms) 19.47 4.29 103
Req/Sec 5011.11 506.2 5487
Байты/Сек 51,8 Мб 5.45 МБ 58.72 Мб
50k запросов в 10s, 519.64 МБ читать
Важно сбалансировать потенциальное снижение затрат сервера с затратами на разработку. Мы должны определить, в наших собственных ситуационных контекстах, как далеко мы должны пойти в оптимизации проекта. В противном случае, это может быть слишком легко положить 80% усилий в 20% от повышения скорости. Оправдывают ли это ограничения проекта?

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

Одним из способов управления расходами ресурсов является установление цели. Например, 10 раз улучшение, или 4000 запросов в секунду. Основываясь на бизнес-потребностях, имеет наибольший смысл. Например, если затраты на сервер на 100% выше бюджета, мы можем установить цель улучшения в 2 x.

Принимая его дальше

Если мы производим новый график пламени нашего сервера, мы должны увидеть нечто похожее на следующее:

Flame graph still shows server.on as the bottleneck, but a smaller bottleneck
График пламени после исправления ошибки производительности был сделан

Слушатель события по-прежнему узкое место, он по-прежнему занимает одну треть времени процессора во время профилирования (ширина составляет около одной трети всего графика).

Какие дополнительные выгоды могут быть сделаны, и являются изменения (наряду с их связаны нарушения) стоит сделать?

С оптимизированной реализацией, которая, тем не менее, немного более ограничена, следующие характеристики производительности могут быть достигнуты (Запуск 10s тест http://localhost:3000/seed/v1 — 10 соединений):

Стат Авг Стдев Макс
Задержка (ms) 0.64 0.86 17
Req/Sec 8330.91 757.63 8991
Байты/Сек 84.17 МБ 7,64 Мб 92,27 Мб
92k запросы в 11s, 937.22 МБ читать
Хотя улучшение в 1,6 м является значительным, оно, согласно проблеме, зависит от того, оправданы ли усилия, изменения и сбои в работе кода, необходимые для создания этого улучшения. Особенно по сравнению с 200x улучшение на исходной реализации с одной ошибки исправить.

Для достижения этого улучшения, тот же итеративный метод профиля, генерировать фламеграф, анализировать, отладить и оптимизировать был использован, чтобы прийти к окончательному оптимизированный сервер, код для которого можно найти здесь.

Окончательные изменения, достигаемые 8000 req/s, были:

Эти изменения немного более вовлечены, немного более разрушительны для базы кода, и оставить etagger промежуточное программное обеспечение немного менее гибким, потому что это кладет бремя на маршруте, чтобы обеспечить Etag значение. Но он достигает дополнительных 3000 запросов в секунду на машине профилирования.

Давайте взглянем на график пламени для этих окончательных улучшений:

Flame graph shows that internal code related to the net module is now the bottleneck
Здоровый график пламени после всех улучшений производительности

Самая горячая часть графика пламени является частью ядра узла в net модуле. Это идеальный вариант.

Предотвращение проблем с производительностью

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

Использование инструментов производительности в качестве неофициальных контрольных точек во время разработки может отфильтровать ошибки производительности, прежде чем они сделают его в производство. Рекомендуется сделать AutoCannon и Clinic (или эквиваленты) частью повседневного инструментария разработки.

При покупке в рамках, узнайте, что это политика на производительность. Если фреймворк не определяет производительность, важно проверить, соответствует ли это инфраструктурным практикам и бизнес-целям. Например, Restify явно (с момента выпуска версии 7) инвестировал в повышение производительности библиотеки. Однако, если низкая стоимость и высокая скорость является абсолютным приоритетом, рассмотреть Fastify, который был измерен как 17% быстрее вкладчика Restify.

Следите за другими широко влияющих библиотек выбор — особенно рассмотреть лесозаготовки. По мере устранения проблем разработчики могут добавить дополнительный вывод журнала, чтобы помочь отладить связанные с ними проблемы в будущем. Если используется неэффективный лесозаготовитель, это может задушить производительность с течением времени после моды кипящей лягушки басни. Регистратор pino является самым быстрым новым разграничением регистратором JSON, доступным для Node.js.

Наконец, всегда помните, что цикл событий является общим ресурсом. Сервер Node.js в конечном счете ограничен самой медленной логикой в самом горячем пути.

Источник: smashingmagazine.com

Великолепный Журнал

Великолепный, сокрушительный, разящий (см. перевод smashing) независимый журнал о веб-разработке. Основан в 2006 году в Германии. Имеет няшный дизайн и кучу крутых авторов, которых читают 2 млн человек в месяц.

Добавить комментарий

%d такие блоггеры, как: