Адказ пра справжнім узгодкам у повольным канцэнтры Node.js
Выучыце систематычны метод для выявлення затрымкіў у частыні сервера па маршруту запиту — ад коду Node.js да запытоў да базы дадзеных — за дапамойю відліку часу і команд EXPLAIN ANALYZE.
Канцэнтры бэкенду працюець медленна, і рэакцыя ў такім случае практычна завжды аднаковая:
"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, праектаваць спецыяльную іерархію памілак, централізаваць кераванне асінхроннымі памілкамі та захоўваць стэк-трэйсы для падтрымкі надзеянай працы дапыткаў.
- 20 шаблонаў Node.js, якія запобегаюць перыядам недзеяння сервера ў працэсе вырабоцтва — Дазвольце вам пазнакоміцца з 20 практычнымі шаблонамі Node.js — ад керавання памілкамі да гракцязнага завершэння роботы та кэшавання з’яўленняў — якія запобегаюць збоям прычынам, што становяць патрэбу ў перзапуску дапыткаў.