Баг у Node.js, який не помічають ваші системи моніторингу

Ваш health check каже, що все гаразд. Він займає одну мілісекунду. Потім трафік зростає. Раптом ваш p99 latency стрибає до 400 мс. Ви дивитесь на свої дашборди. Все виглядає зеленим.

Використання CPU помірне. Event loop lag стабільний. З пам'яттю все добре. Ваш APM показує повільний запит, але не каже, чому. Немає повільних викликів бази даних. Немає помилок.

Час витрачається в libuv thread pool.

Стандартна спостережуваність Node зосереджена на event loop. Pool — це окрема черга. Вона знаходиться поза межами вашого контролю.

Node виконує JavaScript в event loop. Він передає важкі завдання в libuv thread pool. Це включає:

  • Роботу з файловою системою (fs.readFile, fs.writeFile).
  • Криптографічні завдання (bcrypt, scrypt, pbkdf2).
  • Стиснення (zlib gzip, deflate).
  • DNS-запити (dns.lookup).

За замовчуванням pool має лише чотири потоки. Це справедливо незалежно від того, скільки ядер CPU має ваша машина.

Чотирьох потоків недостатньо. Ось три способи, якими pool може стати причиною проблем:

  1. Bcrypt під час входу. Один хеш bcrypt може займати 250 мс. Якщо чотири користувачі входять одночасно, усі слоти зайняті. П'ята людина чекає в черзі. Вона витрачає 250 мс лише на те, щоб почати. Ваша затримка подвоюється.

  2. Великі операції gzip. Стиснення великої відповіді займає слот у pool. Якщо чотири запити роблять це одночасно, кожне інше завдання чекає. DNS-запити та читання файлів застрягають у черзі.

  3. DNS-запити. Більшість Node-додатків використовують dns.lookup. Це використовує блокуючий системний виклик. Він поміщає завдання в pool. Якщо у вашій мережі стався збій, ці запити зупиняють увесь pool.

Запит, що застряг у черзі pool, невидимий. Він не використовує CPU. Він не виконує JavaScript. Він просто очікує.

Як це знайти:

Якщо ваш p99 latency зростає під навантаженням, але event loop lag залишається стабільним, перевірте pool.

Найшвидший тест: збільште UV_THREADPOOL_SIZE. Встановіть його на 64 у вашому оточенні та перезапустіть застосунок. Якщо затримка зменшиться, ви знайшли проблему.

Як виправити це правильно:

  • Використовуйте worker_threads для важкої криптографії, як-от bcrypt. Це дозволить тримати їх поза libuv pool.
  • Використовуйте dns.resolve замість dns.lookup. Це справжній асинхронний резолвер.
  • Використовуйте streams для роботи з zlib, щоб швидше звільняти слоти.
  • Уникайте важкої роботи з файловою системою на основних шляхах запитів.

Досить дивитися на зелені дашборди, поки ваші користувачі чекають.

Джерело: https://dev.to/r9v/the-nodejs-bug-thats-invisible-to-your-monitoring-oo8