Галоўная / Артыкулы / Адказ пра справжнім узгодкам у повольным канцэнтры Node.js

Адказ пра справжнім узгодкам у повольным канцэнтры Node.js

Выучыце систематычны метод для выявлення затрымкіў у частыні сервера па маршруту запиту — ад коду Node.js да запытоў да базы дадзеных — за дапамойю відліку часу і команд EXPLAIN ANALYZE.

1671 слоў

Канцэнтры бэкенду працюець медленна, і рэакцыя ў такім случае практычна завжды аднаковая:

"Node.js працюе медленна."

Раней гэта таксама была стандартным адказам.

Але пасля таго, як было выдзялена час на анаіз рэальных проблем з выдатковаючымыя спроможнасцю, з’явілася іншая наука:

Месца, дзе пазірае медленнае працюванне, не завжды є тымі месцамі, якія яго спрычыняюць.

Сервіс Node.js можа працюваць абсалютна так, як планавалася, тады калі рэальная затримка выходзіць з базы дадзеных, API трэцьяго сторонньага апарату, сеті чыста з-за адной паслаба оптымізаванай запитоўкі, якая знаходзіцца дзе-небудзь у шляху запита.

Нижчэй паказаны метод, які варта застаўляць кожны раз, калі канцэнтра бэкенду пазірае неразумна медленным.

1. Пачніце з рэальной проблемы

Уявіце сабе такі маршрут:

GET /api/users?email=user@example.com

Адказ правільны.

Але гэта зазвычай трывае прыблізна 2–3 секунды.

Перша, інстынктыўная рэакцыя зазвычай включае такія ідэі:

  • Настроўка коду Node.js
  • Введэнне шара кешавання
  • Падвышэнне магчымасцей сервера
  • Стварэнне дадатковых экземпляраў
  • Перапісва частака коду JavaScript

Нічога з гэтага яшчо не падтрымлена доказамі — гэта толькі спекуляцыі.

Перш за ўсё трэба запытацца:

Дзе на самай працоўгэ выкананы час?

2. Абліковваць показнікі прычымо да змян

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

Напрыклад:

console.time("getUsers");
const users = await getUsers();console.timeEnd("getUsers");

Якщо выведзены рэзультат:

getUsers: 2720ms

гэтае адно число вже дае вам корыстную інфармацыю.

Праблема, што ўтрудняе роботу, верагодна, не ёсць сам шар HTTP.

Гэта адбываецца где-небудзь усередине getUsers().

Падзей час занурыцца ўшчэ на адны слой глęбей.

3. Апыляйце запит да базы дадзеных

Спачатку пазірэце, што выглядае функцыя, якая выконваецца:

console.time("db-query");
const users = await prisma.user.findMany({
  where: {
    email: email
  }
});console.timeEnd("db-query");

А час выконання паказваецца так:

db-query: 2680ms

Гэта значна скаржае масштабы пошуку.

Node.js не ёсць тым, хто витрачае 2,6 секунды на обробку запиту.

Це робіць сам запыт да базы дадзеных.

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

4. Тепер запытайце PostgreSQL, што ён робіць

Самэй гэтага моманту трэба вжыць EXPLAIN ANALYZE.

Напрыклад:

EXPLAIN ANALYZE
SELECT *
FROM users
WHERE email = 'user@example.com';

Рэзультаты можа паказаць кашто-та з такімі деталямі:

Seq Scan on users
(actual time=0.025..2720.532 rows=1)

Ключовая деталь, якую трэба звернуць увагу, — гэта:

Seq Scan

PostgreSQL працюе з усіма рядкамі таблы поштоўна, замест таго каб адразу перайсці да вялікога запису за дапамогою індэкса.

Калі таблы становяцца дастаткова великімі, такі последоўны скан стае справжнья прычыной великіх затрат.

5. Рашэнне не ў тым, каб «оптымаізаваць Node.js»

Якщо пошук па email выкарыстоўваецца часта, стварэнне належныя індэкса можа значна зменшыць вартасць запиту.

Напрыклад:

CREATE INDEX idx_users_email
ON users(email);

Пасля чаго зноў запустіце той самы запит:

EXPLAIN ANALYZE
SELECT *
FROM users
WHERE email = 'user@example.com';

План выконання тепер должен паказваць ўсё болей прыбліжанае да:

Index Scan using idx_users_email

замест таго, каб:

Seq Scan

Тачная цифра ў мілісекундах тут насправды не мае значэння.

Важлівае тое, што стратэгія выконання была зменена на аднойчыных дадзэннях, а не за прыпускамі.

6. Большая наука

Усё гэта фундаментальна не стосуецца PostgreSQL.

Это урок як систематычна дэбагаваць.

Калі запит відкладаецца, не трэба адразу звычайваць фреймворк.

Уявіце цэлы шлях, які праходзіць запит:

Client
  ↓
HTTP
  ↓
Node.js
  ↓
Business Logic
  ↓
Redis / Database / External API
  ↓
Response

Будзь-які з етапаў у гэтым ланцузе можа быць справжнім вузкам местам.

Ваша задача — точна выявіць, які самэў.

7. Просты процес дэбагавання

Калі канец-пункт працюе повольна, трэба следаваць прыблізна такой последовасці.

Шаг 1 — Звярнуцца цэлы запит

Request: 2.8

Шаг 2 — Разбіць запит на складовыя

Authentication: 20ms
Business logic: 50ms
Database: 2.6s
Response serialization: 15ms

Калі у вас ёсць такая структура, знаходжыць прычыну стае набагато простягчэй.

Шаг 3 — Аналізаваць найповольнейшую складовую

Якщо частка базы дадзеных займае 2,6 секунды:

Не трэба вяртайць гадзіну на налагоджэнне JavaScript.

Ідзіце безпосередна да базы дадзеных.

Крок 4 — Анаіз запиту

Уважна перагляд:

EXPLAIN ANALYZE

Таксама пераканайцеся ў:

  • Парадны анаіз
  • Чы рэальна выкарыстоўваюцца індэксы
  • З’еўрання
  • Операцыі сортавання
  • Умовы фільтрацыі
  • Сколькі рэядкоў аналізуецца
  • Сколькі рэядкоў насправды вяртаецца

Крок 5 — Вылечыце адну проблему

Калькія можлівыя способы вылечэння:

  • Дадзіце пасяродны індэкс
  • Перапішыце запит, які структураваны неэфектыва
  • Адмовіцеся ад непатрэбнага з’еўрання
  • Усуніце патэрн запита N+1
  • Зменшыце колькасць непатрэбных дадзеных, якія запрашуюцца

Крок 6 — Зноў змерыце

Ніколі не верыце без падтверджэння, што ваша палечка прыняцьгала рэшэнне.

Падтвердзіце гэта новымі замерамі.

8. Не забывайце аб зовнішніх API

Затрымка не завжды выклікана базай дадзеных.

Разглядзім такій сцэнарый:

const user = await getUser();
const payment = await getPaymentDetails();const orders = await getOrders();return {
  user,
  payment,
  orders
};

Якщо кожны окалечны выклік займае:

getUser()          → 100ms
getPaymentDetails() → 900ms
getOrders()         → 700ms

тады канцэнтр працы будзе працаваць значна медленней, чым трэба.

І тут адказ не ў тым, "зробіць Node.js працоўнай быстрей".

Можа, гэта проста пытанне змены спосабу выконання незалежных выклікаў.

Напрыклад:

const [user, payment, orders] = await Promise.all([
  getUser(),
  getPaymentDetails(),
  getOrders()
]);

Такім чынам, аперацыі, якія не залежаць адзінадзе другага, выконваюцца паралельна, а не адна за іншай.

Пры тым, тут ёсць важлівая застерэжнае паўтарэнне:

Не вжывайцеPromise.all()без рэштрыкавання.

Калі аперацыі залежаць адзінадзе другага, трэбаяць ліміты паралелізма або існуе рызык перавантажэння службы, якая працуе пасля, выконанне всього паралельна насправды можа толькі пагаршыць ситуацыю, а не павысіць якасць.

Правыя спосабы паслідчага падвышэння каркамасу завжды залежаць ад конкрэтнай навантажэнняй, з якой вы стаўіцеся.

9. Следзіце за запытамі типу N+1

Існуе ўжо адна прычына падвышэння каркамасу, якая на першы погляд здаецца безнэбезпечной.

Возьмім такі прыклад:

const users = await getUsers();
for (const user of users) {
  user.orders = await getOrders(user.id);
}

Якщо вы працуе з 100 пользователямі, такі патэрн таямна стварае:

1 query → get users
100 queries → get orders

Это дае аж 101 аднародны запыт да базы дадзеных для адпаведнага вызову API.

Это ўжо вядомая проблема запытамі типу N+1.

Залежна ад вашай ситуацыі, кращыя стратэгіі можаў быць такімі:

  • Блакаванне табелей безпосередна
  • Іспытанне функцый загрузкі адносаў ORM
  • Загружанне записаў пакетамі, а не по аднай
  • Іспытанне клазулі WHERE IN
  • Перэсвядчэнне структуры адпаведзі
  • Адаптаванне кэшавання там, дзе гэта мае сенс
  • Як і завжды, выбор падходу залежыць абсалютна ад навантажэння.

    10. Не збільшайце размер сервера занадта рано

    Частая спрэчная реакцыя на повольную API ёсьць:

    "Давайце дадзім яшчэ CPU і RAM.“

    У дзеярых случаях гэта можа дапамогчы.

    Але частаю часу вы проста витрачаеце больш грошэй, не рашучы самой проблемы.

    Якщо запит да базы дадзеных напісаны па-бяднаму, адно толькі падсиленне сервера Node.js не зробіць гэты запит працоўнай шыбкей.

    Перш чым расширваць апаратную частку, запытайце сябе:

    Чы права прычына сповольнення — CPU чы памяць?

    Якщо ні, дадзенне большай капацытаты сервера, верагацельна, не рашыць справжню проблему.

    11. Падход да дыбагавання, які я намагаюся адстаўляць

    Корыстны псіхалогічны модэль для рашэння проблем сповольнення бэкэнду выглядае так:

    Is the API slow?
           ↓
    Measure it
           ↓
    Which layer is slow?
           ↓
    Measure that layer
           ↓
    Find the actual bottleneck
           ↓
    Make one change
           ↓
    Measure again
    

    Замест таго:

    API slow
       ↓
    Optimize Node.js
       ↓
    Add Redis
       ↓
    Increase server
       ↓
    Hope it gets faster
    

    Другі шляхы ўскладненыя для прыметкі.

    Першы шлях – гэта справжняі інжынерны падход.

    12. Мой чаклетк для перагляду карыстоўнасці бэкенду

    Перш чым прыступіць да якой-небудзь оптымізацыі, варта падтвердзіць:

    • Калі самае практычнае час адпаведзення з початку да канца?
    • Чы не ўскладнюецца робота завяршэнням CPU?
    • Чы сам запит да базы дадзеных ўплошчаны?
    • Чы гдзе-небудзь не схована модэлі N+1?
    • Чы ўстановленыя правильныя індэксы?
    • Што паказвае EXPLAIN ANALYZE?
    • Чы API трэціх сторон дадае затрымкі?
    • Чы незалежныя задачы выкананыя па спрабе, калі гэта не трэба?
    • Чы код запрашвае больш дадзеных, чым на самай працэ?
    • Чы Redis выкарыстоўваецца там, дзе гэта неабходна?
    • Чы пул з’язоў налаштаваны правільна?
    • Чы змяны, якія вы адбылі, справжньа зменилі паказаныя цифры?

    Заключныя меркіі

    Аднам з найцэнныях урокаў у роботе з бэкендам ёсць такі:

    Вы рэдкая разв’язуеце проблемы з выконаннем, спадзяючыся на здагадкі.

    Памедзелыя API на базе Node.js не значыць автаматычна, што прычына ў самай сістэме Node.js.

    Рэальныя прычыны можу быць:

    PostgreSQL
    Redis
    External APIs
    Network
    N+1 queries
    Poor indexes
    Serialization
    Connection pools
    Application logic
    

    Знанне таго, як настраіваць кожную з гэтых тэхналогій окрема, — гэта не асновная навык.

    Асновны навык — гэта здатнасць вызначыць, дзе самэй настаўляеся узгорнутасць.

    Калі вы точна знаеце, дзе зникае час, рашэнне заўсёды прыходзіце сама сабою.

    Спачатку змерыце. Знайдзіце узгорнутасць. Выправіце ёю. Змерыце знова.

    Гэты падход зараз вядомы для рашэння проблем з выконаннем бэкенду.

    Спадзяючыся літаратура

  • Стратэгія апдзейвання токена для систем аутантыкаціі Node.js — Дазвольце вам дазнацца, як проектаваць, змінваць, анулюваць і безпечна хранэння токенаў апдзейвання ў Node.js, каб кража токенаў і выход з системы працавалі так, як і планавалася.
  • Структураваная логгірацыя ў Node.js: Как ператварыць хаос у процесе дэбаггінгу ў продакшыне на яснасць — Дазвольце вам дазнацца, чым вызвана няэфектыўнасць console.log у прыкладах коду Node.js у продакшыне, і як структураваная логгірацыя, рэвэлі логаў і ID-ы кореляцыі дапамагаюць шыбка адлучвацца да прычын складных багоў.
  • Контроль канарыцтва у Node.js: Как узбегаць зламоў API за дапамой p-map і Bottleneck — Дазвольце вам дазнацца, як саюз p-map і Bottleneck у Node.js запобегае бягама, вызваным лімітамі частоты, і перавантажэнню системы па мэркі канарыцтва і часу обработкі запытаў.