Нагрузочный тест
Cравнил express (8 middleware + handler) и micro (8 функций + handler). В обоих случаях работа имитировалась CPU и задержкой.
RPS/Latency
CPU / RAM / Event Loop
Micro даёт +4.5% RPS, короче хвосты и меньший p99, потребляет меньше памяти. Цена — чуть больший CPU.
Express выигрывает только по CPU-нагрузке на запрос, но проигрывает по latency и RAM.
Cравнил express (8 middleware + handler) и micro (8 функций + handler). В обоих случаях работа имитировалась CPU и задержкой.
RPS/Latency
Path RPS p50 p95 p99 OK
/express 2332 85.6 89.1 91.7 46641
/micro 2440 82.1 84.0 85.9 48800
CPU / RAM / Event Loop
Path CPUms ELDp99 RSSΔMB HeapΔMB
/express 3645 11.7 53.2 15.4
/micro 4873 11.9 7.2 1.0
Micro даёт +4.5% RPS, короче хвосты и меньший p99, потребляет меньше памяти. Цена — чуть больший CPU.
Express выигрывает только по CPU-нагрузке на запрос, но проигрывает по latency и RAM.
🔥3
Что в итоге?
«Cтоимость фреймворка» есть всегда, и далеко не всегда эта стоимость оправдана.
Middleware — это магия которую тебе навязали.
Хорошо выглядит на старте, плохо живёт в системах с нагрузкой, SLA и трейсингом.
Явная обработка может выглядеть примитивно, скучно, зато баги сразу на поверхности.
В middleware цепочке баг — это квест: «угадай, кто не вызвал next()».
Middleware допустимы только как транспортные фильтры без побочных эффектов и зависимости от порядка: CORS, compression, статика. Всё, что связано с бизнес-логикой и observability — выносить в явный пайплайн.
Один обработчик вместо middleware-зоопарка. Прозрачность вместо инверсии. Контроль вместо фреймворка. Проще – лучше.
Что почитать?
- Micro
- fastify
- Understanding the Impact of Middleware on Node.js Performance
«Cтоимость фреймворка» есть всегда, и далеко не всегда эта стоимость оправдана.
Middleware — это магия которую тебе навязали.
Хорошо выглядит на старте, плохо живёт в системах с нагрузкой, SLA и трейсингом.
Явная обработка может выглядеть примитивно, скучно, зато баги сразу на поверхности.
В middleware цепочке баг — это квест: «угадай, кто не вызвал next()».
Middleware допустимы только как транспортные фильтры без побочных эффектов и зависимости от порядка: CORS, compression, статика. Всё, что связано с бизнес-логикой и observability — выносить в явный пайплайн.
Один обработчик вместо middleware-зоопарка. Прозрачность вместо инверсии. Контроль вместо фреймворка. Проще – лучше.
Что почитать?
- Micro
- fastify
- Understanding the Impact of Middleware on Node.js Performance
❤2🔥2
Намного об event loop
Мажорный релиз, деплой на прод. Сидишь, смотришь плотно в графану. Красота: CPU ровный, память без пиков, p99 по hot path зелёный. Ещё парочка «нужных» графиков для самоуспокоения.
И вдруг — никогда такого не было, и вот опять. Задержка растёт. Число отказов ползёт вверх. 429-ответы сыпятся, сыпятся алерты по SLA.
Что делаешь? Логи. Всегда логи. Смотришь сервис, который увёл SLA в кино. Логи чистые, ошибок нет. А задержка продолжает расти.
Окей, «много бед — один ресет». Перезапускаешь сервис — всё нормализовалось. Ушёл налить кофе. Возвращаешься — та же херня. CPU норм, память норм, логи чистые, а бизнес уже пишет: «почему не грузится ничего?».
Магия? Нет. В 99% случаев виновата не база, не сеть и не «плохой деплой». Виноват event loop. Тот самый, про который все слышали на первых лекциях по node, и даже на собеседованиях спрашивают про его стейджи, но забывают включить в дашборд.
Сейчас разберём, что это, как он уронил твой прод, как и зачем его мониторить.
Надеюсь, ты помнишь, что Node однопоточный. Да там можно пойти в worker threads. Но это костыль для спец-кейсов. Нужна настоящая многопоточность – бери c++, rust, go. Но для 99% процентов задач, хватает и «всего» одного потока Node.
А как это? На сцену с ноги залетает event loop. Именно он позволяет поверить в магию однопоточной многозадачности. Как будто всё параллельно: и запросы летят, и таймеры тикают, и промисы резолвятся. На самом деле – нет. Всё идёт строго по плану, в одном потоке и последовательно.
Документацию цитировать не буду — об неё уже немало цифровых перьев сломано. Дальше кратко по делу.
Event loop — это механизм планирования. В Node он крутится в libuv: есть очереди для разных задач — timers, I/O, poll, check, close. Каждая итерация проходит по очередям и вытаскивает колбэки. Между фазами Node гоняет микротаски (Promise), а process.nextTick сидит в отдельной стопке с самым высоким приоритетом. Всё последовательно.
В браузере похожая схема, но приоритет другой: tasks, microtasks, плюс обязательная отрисовка и обработка ввода. Кадр всё равно будет перерисован. В Node такого «страховочного рендера» нет: задушил цикл синхронщиной — он так и повиснет в фазе, пока ты не перезапустишь.
Мажорный релиз, деплой на прод. Сидишь, смотришь плотно в графану. Красота: CPU ровный, память без пиков, p99 по hot path зелёный. Ещё парочка «нужных» графиков для самоуспокоения.
И вдруг — никогда такого не было, и вот опять. Задержка растёт. Число отказов ползёт вверх. 429-ответы сыпятся, сыпятся алерты по SLA.
Что делаешь? Логи. Всегда логи. Смотришь сервис, который увёл SLA в кино. Логи чистые, ошибок нет. А задержка продолжает расти.
Окей, «много бед — один ресет». Перезапускаешь сервис — всё нормализовалось. Ушёл налить кофе. Возвращаешься — та же херня. CPU норм, память норм, логи чистые, а бизнес уже пишет: «почему не грузится ничего?».
Магия? Нет. В 99% случаев виновата не база, не сеть и не «плохой деплой». Виноват event loop. Тот самый, про который все слышали на первых лекциях по node, и даже на собеседованиях спрашивают про его стейджи, но забывают включить в дашборд.
Сейчас разберём, что это, как он уронил твой прод, как и зачем его мониторить.
Надеюсь, ты помнишь, что Node однопоточный. Да там можно пойти в worker threads. Но это костыль для спец-кейсов. Нужна настоящая многопоточность – бери c++, rust, go. Но для 99% процентов задач, хватает и «всего» одного потока Node.
А как это? На сцену с ноги залетает event loop. Именно он позволяет поверить в магию однопоточной многозадачности. Как будто всё параллельно: и запросы летят, и таймеры тикают, и промисы резолвятся. На самом деле – нет. Всё идёт строго по плану, в одном потоке и последовательно.
Документацию цитировать не буду — об неё уже немало цифровых перьев сломано. Дальше кратко по делу.
Event loop — это механизм планирования. В Node он крутится в libuv: есть очереди для разных задач — timers, I/O, poll, check, close. Каждая итерация проходит по очередям и вытаскивает колбэки. Между фазами Node гоняет микротаски (Promise), а process.nextTick сидит в отдельной стопке с самым высоким приоритетом. Всё последовательно.
В браузере похожая схема, но приоритет другой: tasks, microtasks, плюс обязательная отрисовка и обработка ввода. Кадр всё равно будет перерисован. В Node такого «страховочного рендера» нет: задушил цикл синхронщиной — он так и повиснет в фазе, пока ты не перезапустишь.
🔥3❤2👍1
Соответственно, чтобы измерить, насколько твой event loop загружен, нужно посчитать задержку. По факту это будет дреф между постановкой таймера и вызовом колбэка этого таймера.
Для этого в Node уже есть встроенный инструмент:
Но также можно и старым дедовским методом:
ELD смотри по p95/p99, среднее бесполезно. Если у тебя воркеры — снимай метрику с каждого процесса, иначе «плохой» утонет в усреднении. Держи ELD рядом с HTTP p95/p99 и RPS: ходят синхронно — проблема в GC/коде, а не в сети/БД. Алерты: eldP99 > 100ms (2+ мин), eldP95 > 50ms (5+ мин), плюс «сдвиг базы» — p95 вырос ×2 против недельной медианы.
Такой джентльменский набор поможет поймать плохой релиз и отреагировать в случае начала деградации.
Для этого в Node уже есть встроенный инструмент:
import { monitorEventLoopDelay } from 'node:perf_hooks'
const h = monitorEventLoopDelay({ resolution: 10 })
h.enable()
setInterval(() => {
const p95 = h.percentile(95)
const p99 = h.percentile(99)
const max = h.max
console.log({
eldP95: +p95.toFixed(2),
eldP99: +p99.toFixed(2),
eldMax: +max.toFixed(2)
})
h.reset()
}, 1000)
Но также можно и старым дедовским методом:
import { performance } from 'node:perf_hooks'
const interval = 100
let t = performance.now()
setInterval(() => {
const diff = performance.now() - t - interval
t = performance.now()
}, interval)
ELD смотри по p95/p99, среднее бесполезно. Если у тебя воркеры — снимай метрику с каждого процесса, иначе «плохой» утонет в усреднении. Держи ELD рядом с HTTP p95/p99 и RPS: ходят синхронно — проблема в GC/коде, а не в сети/БД. Алерты: eldP99 > 100ms (2+ мин), eldP95 > 50ms (5+ мин), плюс «сдвиг базы» — p95 вырос ×2 против недельной медианы.
Такой джентльменский набор поможет поймать плохой релиз и отреагировать в случае начала деградации.
🔥2✍1👍1🙈1
Как задушить event-loop и как с этим бороться?
Тяжелый cpu-bound sync в hot path.
-
- zlib.*Sync, crypto.*Sync, bcrypt в sync-режиме.
-
-
Симптомы: ступеньки на ELD p95/p99, совпадающие с графиком конкретного эндпоинта. HTTP p95 ходит синхронно с ELD, CPU «вроде норм», но один воркер красный.
Что делать
- Любой *Sync из hot path — выносить: использовать async API (zlib.gzip, crypto.pbkdf2, fs.promises/стримы).
- На тяжёлое — threadpool libuv (это уже делает async API). Если узко — подними UV_THREADPOOL_SIZE до разумного (8–32).
- Избегать мегабаитов JSON «целиком». Или NDJSON, или стримить чанками и парсить.
- execSync переписать на spawn + стримы.
Микротаски/nextTick
- Длинные цепочки await/Promise.then в tight-loop.
- Неконтролируемые queueMicrotask.
- Злоупотребление
Симптомы: высокая ELD при «нормальном» CPU, «пульсирующие» лаги на миллисекунды–десятки мс, таймеры и setImmediate поздно исполняются.
Что делать
- Если нужно «уступить», не nextTick, а setImmediate/await new Promise(setImmediate) — это даёт воздуха poll-фазе.
- Для циклов — yield каждые N итераций:
GC-паузы (stop the world V8)
- Большой heap, бурст аллокаций, массивы/буферы, которые живут чуть дольше нужного.
- «Незаметные» замыкания и держатели ссылок, мешающие сборке.
Симптомы: редкие, но жирные пики ELD (100–500 мс), --trace-gc показывает Mark-Compact/Scavenge с большими паузами; корреляция с ростом heap и количеством аллокаций.
Что делать
- Снижать текучку объектов: переиспользовать буферы (пулы), не плодить временные объекты в критическом пути.
- Дробить большие структуры/ответы; не держать «один гигантский объект» в памяти.
- Убедиться, что нет «липких» ссылок (кеши без TTL, замыкания на большие данные).
- Не лечить всё «бОльшим heap»: чаще увеличение old-space увеличивает длительность паузы. Стоит начать с профиля.
Тяжелый cpu-bound sync в hot path.
-
JSON.parse()`/`JSON.stringify() на мегабайты.- zlib.*Sync, crypto.*Sync, bcrypt в sync-режиме.
-
fs.readFileSync`/`writeFileSync (да, диск тоже блокирует цикл).-
child_process.execSync, Atomics.wait, любые «подвесы» CPU.Симптомы: ступеньки на ELD p95/p99, совпадающие с графиком конкретного эндпоинта. HTTP p95 ходит синхронно с ELD, CPU «вроде норм», но один воркер красный.
Что делать
- Любой *Sync из hot path — выносить: использовать async API (zlib.gzip, crypto.pbkdf2, fs.promises/стримы).
- На тяжёлое — threadpool libuv (это уже делает async API). Если узко — подними UV_THREADPOOL_SIZE до разумного (8–32).
- Избегать мегабаитов JSON «целиком». Или NDJSON, или стримить чанками и парсить.
- execSync переписать на spawn + стримы.
Микротаски/nextTick
- Длинные цепочки await/Promise.then в tight-loop.
- Неконтролируемые queueMicrotask.
- Злоупотребление
process.nextTick() (приоритет выше промисов, можно навсегда «запереть» следующие фазы).Симптомы: высокая ELD при «нормальном» CPU, «пульсирующие» лаги на миллисекунды–десятки мс, таймеры и setImmediate поздно исполняются.
Что делать
- Если нужно «уступить», не nextTick, а setImmediate/await new Promise(setImmediate) — это даёт воздуха poll-фазе.
- Для циклов — yield каждые N итераций:
async function badLoop(N) {
console.time('badLoop')
for (let i = 0; i < N; i++) {
await Promise.resolve() // остаёмся в пуле микротасок
if (i % 1000 === 0) {
console.log('progress', i)
}
}
console.timeEnd('badLoop')
}
setTimeout(() => console.log('timer fired'), 0)
badLoop(1e6).then(() => console.log('done'))
/**
progress 0
...
progress 900000
badLoop: 50ms
done
timer fired
**/
import { setImmediate as yieldImmediate } from "node:timers/promises"
async function goodLoop(N) {
console.time("goodLoop")
for (let i = 0; i < N; i++) {
await Promise.resolve()
if (i % 1000 === 0) {
// каждые 1000 итераций уступаем управление event loop
await yieldImmediate()
console.log("progress", i)
}
}
console.timeEnd("goodLoop")
}
setTimeout(() => console.log("timer fired"), 0)
goodLoop(1e6).then(() => console.log("done"))
/**
progress 0
timer fired
progress 1000
...
progress 999000
goodLoop: 50ms
done
**/
GC-паузы (stop the world V8)
- Большой heap, бурст аллокаций, массивы/буферы, которые живут чуть дольше нужного.
- «Незаметные» замыкания и держатели ссылок, мешающие сборке.
Симптомы: редкие, но жирные пики ELD (100–500 мс), --trace-gc показывает Mark-Compact/Scavenge с большими паузами; корреляция с ростом heap и количеством аллокаций.
Что делать
- Снижать текучку объектов: переиспользовать буферы (пулы), не плодить временные объекты в критическом пути.
- Дробить большие структуры/ответы; не держать «один гигантский объект» в памяти.
- Убедиться, что нет «липких» ссылок (кеши без TTL, замыкания на большие данные).
- Не лечить всё «бОльшим heap»: чаще увеличение old-space увеличивает длительность паузы. Стоит начать с профиля.
❤2🔥2✍1👍1
Забитая фаза poll/шторм колбэков
- Всплеск сетевых событий, бурст таймеров, «вентилятор» ретраев.
- Цикл тратит всё время на вычерпывание очереди, не успевает «вздохнуть».
Симптомы: ELD растёт линейно с RPS, p95 задержка гуляет вместе. CPU занят явно, но профайлер показывает «колбэковое трясучее месиво».
Что делать
- Ретраи — только с джиттером и потолком; убрать «вентилятор».
- Лимиты на конкурентность обработки, мутексы, семафоры.
- Таймеры группировать, добавить дебаунс, не запускать 10k setTimeout в один тик.
- Контролировать backpressure: не читать и не писать быстрее, чем можешь обработать.
Контейнерные квоты/ throttling
- CGroup режет квоту, «сосед» шумит на ноде.
Симптомы: Высокий ELD при «не высоком» CPU — смотри cgroups: nr_throttled, throttled_usec. В прометеусе: container_cpu_cfs_throttled_seconds_total. Тебя могут душить квотой, даже если график CPU зелёный.
Что делать
- Поднять квоту/лимиты или распределить воркеры по нодам без буйных соседей.
- Следить за threadpool saturation: async crypto, zlip, gzip упрётся в 4 потока — поднимать UV_THREADPOOL_SIZE.
- Всплеск сетевых событий, бурст таймеров, «вентилятор» ретраев.
- Цикл тратит всё время на вычерпывание очереди, не успевает «вздохнуть».
Симптомы: ELD растёт линейно с RPS, p95 задержка гуляет вместе. CPU занят явно, но профайлер показывает «колбэковое трясучее месиво».
Что делать
- Ретраи — только с джиттером и потолком; убрать «вентилятор».
- Лимиты на конкурентность обработки, мутексы, семафоры.
- Таймеры группировать, добавить дебаунс, не запускать 10k setTimeout в один тик.
- Контролировать backpressure: не читать и не писать быстрее, чем можешь обработать.
Контейнерные квоты/ throttling
- CGroup режет квоту, «сосед» шумит на ноде.
Симптомы: Высокий ELD при «не высоком» CPU — смотри cgroups: nr_throttled, throttled_usec. В прометеусе: container_cpu_cfs_throttled_seconds_total. Тебя могут душить квотой, даже если график CPU зелёный.
Что делать
- Поднять квоту/лимиты или распределить воркеры по нодам без буйных соседей.
- Следить за threadpool saturation: async crypto, zlip, gzip упрётся в 4 потока — поднимать UV_THREADPOOL_SIZE.
🔥2✍1
Что в итоге?
Event loop — это сердце Node. И умирает прод именно там, а не «в базе». CPU зелёный, память гладкая, графики красивые — а цикл задушен. И всё, сервис лежит, SLA курит в сторонке, пользователи грустят, бизнес угрожает.
Нет мониторинга ELD — значит, ты слеп. Латаешь симптомы и перезапускаешь процессы, вместо того чтобы видеть причину. Любая sync-операция, лишний nextTick, утечка в замыкании или сосед в контейнере — и твой «highload» превращается в highlag.
Так что забудь сказки про «Node сама справится». Справишься ты — если держишь ELD в дашборде и умеешь читать его вместе с RPS и GC. Всё остальное — магия и надежда. А магия, как ты знаешь, в проде всегда кончается одинаково.
Что почитать?
- How we tamed Node.js event loop lag: a deepdive
- Event Loop Starvation in NodeJS
- What the heck is the event loop anyway? | Philip Roberts | JSConf EU
Event loop — это сердце Node. И умирает прод именно там, а не «в базе». CPU зелёный, память гладкая, графики красивые — а цикл задушен. И всё, сервис лежит, SLA курит в сторонке, пользователи грустят, бизнес угрожает.
Нет мониторинга ELD — значит, ты слеп. Латаешь симптомы и перезапускаешь процессы, вместо того чтобы видеть причину. Любая sync-операция, лишний nextTick, утечка в замыкании или сосед в контейнере — и твой «highload» превращается в highlag.
Так что забудь сказки про «Node сама справится». Справишься ты — если держишь ELD в дашборде и умеешь читать его вместе с RPS и GC. Всё остальное — магия и надежда. А магия, как ты знаешь, в проде всегда кончается одинаково.
Что почитать?
- How we tamed Node.js event loop lag: a deepdive
- Event Loop Starvation in NodeJS
- What the heck is the event loop anyway? | Philip Roberts | JSConf EU
❤3👍3🔥3
ParseInt и parseFloat тормозят твой бэкенд
Ранее я писал, как
Казалось бы, простая задача: превратить строку в число. По старой памяти ты пишешь parseInt, как в jQuery эпоху — можно даже строку с мусором скормить, всё равно что-то вернётся, плюс явный radix, удобно.
Но это удобство обходится дорого. В браузере такие мелочи не критичны, а вот на бэкенде
Как это работает?
Допустим, у тебя строка "322.01kek". Чтобы получить число 322.01, ты вызываешь функцию parseFloat("322.01kek") и происходит следующее:
1. JS - Intrinsic - C++ - StringToDouble.
Твой вызов parseFloat(str) попадает во встроенную функцию V8. Проверяется тип аргумента, выполняются быстрые ветки для простых случаев.
2. Парсер числа
Стейт-машина идёт по строке: пробелы, знак, цифры, точка, дробная часть, экспонента. На первом мусорном символе (kek) парсинг останавливается.
3. Конвертация в double
Мусор обрезается.
Накопленное число конвертируется в double с учётом диапазонов и округления IEEE-754. Используется библиотека «double-conversion» от Google.
4. Возврат полученного числа или NaN в случае ошибки.
Если число помещается в Smi, оно возвращается без аллокации. Иначе создаётся HeapNumber.
Упрощенный callstak следующий (вызов синхронный):
- JS parseFloat - V8 builtin (TurboFan/CodeStubAssembler)
- быстрые проверки (тип/длина/ASCII)
- C++ рантайм (общий парсер)
- double_conversion::StringToDouble(...)
- возврат числа в JS (Smi/HeapNumber).
JIT тут почти не помогает. Для
Порядок цен:
- простой кейс (короткая ASCII, без экспоненты): 50–120 нс;
- обычная дробь/точка через double-conversion: 200–600 нс;
- длинные строки/экспоненты: 0.8–2.0 мкс;
- плюс аллокация HeapNumber: 30–80 нс.
На уровне бэкенда это превращается в ощутимый налог: 100k RPS × 3 вызова parseFloat = до 300 млн нс/с (300 мс CPU/с). Это уже ~30% ядра, которое ты тратишь на разбор строк.
Ранее я писал, как
Date.now() может просаживать RPS на hot path. Продолжу тему микрооптимизации и расскажу про parseInt и parseFloat. Казалось бы, простая задача: превратить строку в число. По старой памяти ты пишешь parseInt, как в jQuery эпоху — можно даже строку с мусором скормить, всё равно что-то вернётся, плюс явный radix, удобно.
Но это удобство обходится дорого. В браузере такие мелочи не критичны, а вот на бэкенде
parseInt и parseFloat — тяжёлые операции, которые легко просаживают производительность.Как это работает?
parseFloat - это не просто приведение типов, это полноценный парсер. Он поддерживает дроби, научную нотацию, Infinity, NaN и весь синтаксис IEEE-754. Алгоритм описан в ECMAScript Spec, В V8 он реализован через С++ функцию StringToDouble.Допустим, у тебя строка "322.01kek". Чтобы получить число 322.01, ты вызываешь функцию parseFloat("322.01kek") и происходит следующее:
1. JS - Intrinsic - C++ - StringToDouble.
Твой вызов parseFloat(str) попадает во встроенную функцию V8. Проверяется тип аргумента, выполняются быстрые ветки для простых случаев.
2. Парсер числа
Стейт-машина идёт по строке: пробелы, знак, цифры, точка, дробная часть, экспонента. На первом мусорном символе (kek) парсинг останавливается.
3. Конвертация в double
Мусор обрезается.
Накопленное число конвертируется в double с учётом диапазонов и округления IEEE-754. Используется библиотека «double-conversion» от Google.
4. Возврат полученного числа или NaN в случае ошибки.
Если число помещается в Smi, оно возвращается без аллокации. Иначе создаётся HeapNumber.
Упрощенный callstak следующий (вызов синхронный):
- JS parseFloat - V8 builtin (TurboFan/CodeStubAssembler)
- быстрые проверки (тип/длина/ASCII)
- C++ рантайм (общий парсер)
- double_conversion::StringToDouble(...)
- возврат числа в JS (Smi/HeapNumber).
JIT тут почти не помогает. Для
parseFloat приходится звать универсальный парсер, который всегда идёт по “медленному пути”.Порядок цен:
- простой кейс (короткая ASCII, без экспоненты): 50–120 нс;
- обычная дробь/точка через double-conversion: 200–600 нс;
- длинные строки/экспоненты: 0.8–2.0 мкс;
- плюс аллокация HeapNumber: 30–80 нс.
На уровне бэкенда это превращается в ощутимый налог: 100k RPS × 3 вызова parseFloat = до 300 млн нс/с (300 мс CPU/с). Это уже ~30% ядра, которое ты тратишь на разбор строк.
🔥3👍2
Какая альтернатива?
Number(str) — это не парсер, а прямое приведение типов. Оно не гоняет строку через стейт машину, а сразу использует быстрый путь в V8:
1. Проверка типа.
2. Попытка инлайновой конверсии (короткая ASCII → fast path).
3. Fallback на общий StringToDouble только в экзотике (научная нотация). Если есть мусор, вернет NaN.
Callstack проще: JS Number - V8 builtin - быстрые ветки - либо сразу Smi/HeapNumber, либо C++ StringToDouble. В обычных кейсах JIT умеет заинлайнить эти проверки и кэшировать паттерн.
Порядок цен на вызов:
– Number("123"): 20–40 нс (инлайн + Smi, без аллокаций).
– Number("123.45"): 60–150 нс (HeapNumber, но без сложного парсинга).
Что по цифрам?
Ну и куда же без "померять". Также как и в прошлый раз, измерял два пути:
И вот результаты нескольких прогонов с разными параметрами нагрузки:
И что же получается. Эндпоинт с Number во всех тестах дает лучший показатель по перцентилям и rps. Помимо этого, ты можешь заметить, что деградация производительности происходит намного плавнее, чем у эндпоинта с parseFloat.
Что в итоге?
Если у тебя контролируемый ввод (JSON, DTO, API) — используй Number().
parseInt/parseFloat оставь только там, где сознательно нужен парсинг «грязных» строк.
На hot path разница ощутимая: Number() в 5–10 раз быстрее, меньше аллокаций, JIT умеет его заинлайнить. parseFloat и parseInt — это всегда вызов тяжёлого парсера с кучей проверок и fallback в C++.
Оптимизация простая: если данные у тебя валидные, используй самый дешёвый путь. Number() или унарный + делают ровно то, что нужно.
parse* — это костыль из браузерной молодости, а не инструмент для высоконагруженного бэкенда
Что еще почитать?
- Date.now() может убить твой RPS
- Engine Analysis: String to Number Conversion in JS
Number(str) — это не парсер, а прямое приведение типов. Оно не гоняет строку через стейт машину, а сразу использует быстрый путь в V8:
1. Проверка типа.
2. Попытка инлайновой конверсии (короткая ASCII → fast path).
3. Fallback на общий StringToDouble только в экзотике (научная нотация). Если есть мусор, вернет NaN.
Callstack проще: JS Number - V8 builtin - быстрые ветки - либо сразу Smi/HeapNumber, либо C++ StringToDouble. В обычных кейсах JIT умеет заинлайнить эти проверки и кэшировать паттерн.
Порядок цен на вызов:
– Number("123"): 20–40 нс (инлайн + Smi, без аллокаций).
– Number("123.45"): 60–150 нс (HeapNumber, но без сложного парсинга).
Что по цифрам?
Ну и куда же без "померять". Также как и в прошлый раз, измерял два пути:
function workloadBad (n) {
for (let i = 0; i < n; i++) {
parseFloat('0.12')
}
}
function workloadGood (n) {
for (let i = 0; i < n; i++) {
Number('0.12')
}
}
И вот результаты нескольких прогонов с разными параметрами нагрузки:
duration=20s, conc=32, loops=1000
> /bad ...
ok=426248 rps=21312.4 p50=1.33 p95=2.60 p99=3.52 ms
> /good ...
ok=646309 rps=32315.5 p50=0.81 p95=2.05 p99=3.05 ms
duration=20s, conc=32, loops=10000
> /bad ...
ok=99127 rps=4956.4 p50=6.10 p95=7.70 p99=12.93 ms
> /good ...
ok=798381 rps=39919.1 p50=0.75 p95=1.43 p99=1.57 ms
duration=20s, conc=32, loops=100000
> /bad ...
ok=11902 rps=595.1 p50=51.63 p95=54.76 p99=104.17 ms
> /good ...
ok=304598 rps=15229.9 p50=2.06 p95=2.17 p99=4.13 ms
duration=20s, conc=32, loops=1000000
> /bad ...
ok=1221 rps=61.0 p50=510.21 p95=541.45 p99=3122.19 ms
> /good ...
ok=50729 rps=2536.4 p50=12.16 p95=12.70 p99=24.41 ms
И что же получается. Эндпоинт с Number во всех тестах дает лучший показатель по перцентилям и rps. Помимо этого, ты можешь заметить, что деградация производительности происходит намного плавнее, чем у эндпоинта с parseFloat.
Что в итоге?
Если у тебя контролируемый ввод (JSON, DTO, API) — используй Number().
parseInt/parseFloat оставь только там, где сознательно нужен парсинг «грязных» строк.
На hot path разница ощутимая: Number() в 5–10 раз быстрее, меньше аллокаций, JIT умеет его заинлайнить. parseFloat и parseInt — это всегда вызов тяжёлого парсера с кучей проверок и fallback в C++.
Оптимизация простая: если данные у тебя валидные, используй самый дешёвый путь. Number() или унарный + делают ровно то, что нужно.
parse* — это костыль из браузерной молодости, а не инструмент для высоконагруженного бэкенда
Что еще почитать?
- Date.now() может убить твой RPS
- Engine Analysis: String to Number Conversion in JS
🔥4👍2
0. System design – это тебе не квадратики рисовать
В начале карьеры все архитектурные вопросы сводятся к проектированию малых частей системы. Все в пределах одного сервиса. Немного думаешь про базу, немного про, прости господи, очереди, чуть-чуть про оптимизацию.
Обо всем остальном думают умные дядьки. Они рассказывают, зачем ты пишешь этот сервис и какие требования к нему. Скажут, какую бд взять, какой язык использовать и как сделать так, чтобы держало нагрузку. Остальное дело техники: пара паттернов, охапка костылей – сервис готов. Можно перекладывать json’ы.
Проходит несколько лет, и твой умный дядька исчезает. PM приходит уже к тебе. И вдруг выясняется: теперь именно ты отвечаешь на неудобные вопросы и должен объяснить, как строить сервис.
Последний год ты много слышал про system design, но не обращал внимания. Думал, это для больших и умных. Слышал, что он про архитектуру. Его хотят на собесах. Там рисуют квадратики и стрелочки: квадратики становятся сервисами, базами, кешами и (прости господи) очередями , стрелочки - каналами связи.
Посмотрел ютуб, посмотрел прошлые сервисы, подумал и нарисовал. Рассказал команде. Они постарались и сделали. Сервис выкатили на прод.
Сервис крутится, бизнес мутится, растет нагрузка. Растут задержки. Сервис начинает задыхаться и падать, downtime растет. Всё чаще тебе приходится отвечать на вопросы, на которые раньше отвечали другие.
Ты идешь советоваться к умными дядьками из других команд и компаний. Они почему-то в разрез с ютубом мало говорят про квадратики и стрелочки. Они говорят про какого-то Клепмана, про Эшби и кибернетику, про Альтшуллера и ТРИЗ.
Тебе говорят, что system design это про компромиссы: надежность, масштабируемость и сопровождаемость. Говорят, что мало один раз нарисовать — нужно возвращаться, пересматривать, менять архитектуру под новые боли и требования. Что нужно собирать эти требования. Что схем должно быть много: для бизнеса — своя, для девопсов — своя, для разработки — своя. Разные уровни абстракции, разные нюансы.
И все это – теперь хотят от тебя. Мрак. Ужас.
Поэтому, пока не поздно, я предлагаю тебе погрузиться со мной в знакомство с system design. Я не буду рассказывать «как пройти собес». Я расскажу тебе: почему system design не про стрелочки и квадратики, зачем тебе он нужен и как он может облегчить твои страдания.
В начале карьеры все архитектурные вопросы сводятся к проектированию малых частей системы. Все в пределах одного сервиса. Немного думаешь про базу, немного про, прости господи, очереди, чуть-чуть про оптимизацию.
Обо всем остальном думают умные дядьки. Они рассказывают, зачем ты пишешь этот сервис и какие требования к нему. Скажут, какую бд взять, какой язык использовать и как сделать так, чтобы держало нагрузку. Остальное дело техники: пара паттернов, охапка костылей – сервис готов. Можно перекладывать json’ы.
Проходит несколько лет, и твой умный дядька исчезает. PM приходит уже к тебе. И вдруг выясняется: теперь именно ты отвечаешь на неудобные вопросы и должен объяснить, как строить сервис.
Последний год ты много слышал про system design, но не обращал внимания. Думал, это для больших и умных. Слышал, что он про архитектуру. Его хотят на собесах. Там рисуют квадратики и стрелочки: квадратики становятся сервисами, базами, кешами и (прости господи) очередями , стрелочки - каналами связи.
Посмотрел ютуб, посмотрел прошлые сервисы, подумал и нарисовал. Рассказал команде. Они постарались и сделали. Сервис выкатили на прод.
Сервис крутится, бизнес мутится, растет нагрузка. Растут задержки. Сервис начинает задыхаться и падать, downtime растет. Всё чаще тебе приходится отвечать на вопросы, на которые раньше отвечали другие.
Ты идешь советоваться к умными дядьками из других команд и компаний. Они почему-то в разрез с ютубом мало говорят про квадратики и стрелочки. Они говорят про какого-то Клепмана, про Эшби и кибернетику, про Альтшуллера и ТРИЗ.
Тебе говорят, что system design это про компромиссы: надежность, масштабируемость и сопровождаемость. Говорят, что мало один раз нарисовать — нужно возвращаться, пересматривать, менять архитектуру под новые боли и требования. Что нужно собирать эти требования. Что схем должно быть много: для бизнеса — своя, для девопсов — своя, для разработки — своя. Разные уровни абстракции, разные нюансы.
И все это – теперь хотят от тебя. Мрак. Ужас.
Поэтому, пока не поздно, я предлагаю тебе погрузиться со мной в знакомство с system design. Я не буду рассказывать «как пройти собес». Я расскажу тебе: почему system design не про стрелочки и квадратики, зачем тебе он нужен и как он может облегчить твои страдания.
❤5
Что же такое «System design»?
Чем System design точно не является: рисованием и инструментом для прохождения интервью. Так его блоггеры на ютубе продают. Так проще. Но реальность гораздо сложнее.
System design - это спор с реальностью, борьба с ограничениями, поиск компромиссов. Это мир, в котором у каждой идеи есть цена, у каждого ограничения есть причина. Это мир, в котором "правильная архитектура" сейчас становится "неправильной" через пару месяцев.
Любая система живет в изменчивой среде, и твоя задача – сделать так, чтобы она не была хрупкой, выгибалась и деформировалась, но не ломалась с треском. Это свойство системы: гибкость. Это способность пережить пик нагрузки, переварить новый источник данных, выдержать новый SLA, предоставить новый контракт. При этом не сломать команду, которая разрабатывает систему.
С осознанием этого, ты скорее всего придешь к первой взрослой мысли: «**нужно собрать требования**». Это на первый взгляд просто. Спросил, чего хотят, сделал что-то похожее. Но нет.
Собирать нужно с умом. Формулировка: «хотим ленту как у Threads” не подойдет. Нужно выбить из бизнеса и аналитиков конкретику: сколько пользователей планируется на старте, сколько через год, сколько времени у тебя на p99, какой сбой считается нормой, чем можно пожертвовать при пиках, важнее истина или скорость, чем бизнес готов жертвовать взамен на экономию.
Если всего этого не узнать в начале, ты рисуешь картинку — сферический сервис в вакууме, который красиво выглядит на схеме, но не имеет ничего общего с реальностью. Подробнее поговорим отдельно. Сбор требований важная большая тема.
После сбора требований логически появится следующая мысль: "**нужно определить технологический стек**". Это не про знать все языки и инструменты, это не про держать в голове энциклопедию, это не про культ «возьмем модный брокер». Это простой расчет.
Требования дают представление: «такой профиль данных, здесь читаем, тут пишем, такие хвосты распределения, такие пики». Отсюда вытекают формат данных, модель консистентности, понимание, что журнала хватит и очередь не нужна, что вот тут может быть узко, а тут проблем быть не должно. Ты уже прикидываешь, нужен ли hot path в памяти, и чем придётся платить за восстановление.
Стек складывается естественно: какие составные части, на чём они будут написаны, какие инструменты лягут в основу. Эти мысли уже можно переносить в ТЗ и схемы.
Что-то уже есть на бумаге и технологический стек первично определен. Начинаешь погружаться глубже. Теперь нужно работать со связями. Мысль три: «**нужно определить поток данных и обратную связь системы**».
На этом этапе зачастую красивые презентации заканчивается. Поток данных это не просто стрелочки от сервиса к сервису. Нужно описать ритм, как система будет жить. Где данные зарождаются, как они движутся по системе, где данные накапливаются, где могут упереться в ботлнек, где задерживаются.
И главное - как система реагирует, когда на нее давят. Не существует «пассивной» системы: если разнообразие воздействий мира на систему больше спектра ее реакций, система уходит в отказ.
Если по-простому: поток данных должен быть сопряжён с обратной связью. Когда вход растёт быстрее, чем ты можешь переварить, ты либо сигнализируешь «стоп, хватит» (backpressure), либо начинаешь управляемо деградировать. Не «всё для всех, но “сдохли”», а «меньше, но вовремя».
И вот здесь начинает появляться настоящая архитектура: система, которая умеет не геройствовать до смерти, а вовремя сказать «нет» и остаться живой.
Если обобщить: System design всегда живёт на четырёх слоях: технологическом, организационном, фундаментальном и изобретательском. Игнорируешь хоть один и система падает не там, где ждёшь, а там, где больнее всего.
Чем System design точно не является: рисованием и инструментом для прохождения интервью. Так его блоггеры на ютубе продают. Так проще. Но реальность гораздо сложнее.
System design - это спор с реальностью, борьба с ограничениями, поиск компромиссов. Это мир, в котором у каждой идеи есть цена, у каждого ограничения есть причина. Это мир, в котором "правильная архитектура" сейчас становится "неправильной" через пару месяцев.
Любая система живет в изменчивой среде, и твоя задача – сделать так, чтобы она не была хрупкой, выгибалась и деформировалась, но не ломалась с треском. Это свойство системы: гибкость. Это способность пережить пик нагрузки, переварить новый источник данных, выдержать новый SLA, предоставить новый контракт. При этом не сломать команду, которая разрабатывает систему.
С осознанием этого, ты скорее всего придешь к первой взрослой мысли: «**нужно собрать требования**». Это на первый взгляд просто. Спросил, чего хотят, сделал что-то похожее. Но нет.
Собирать нужно с умом. Формулировка: «хотим ленту как у Threads” не подойдет. Нужно выбить из бизнеса и аналитиков конкретику: сколько пользователей планируется на старте, сколько через год, сколько времени у тебя на p99, какой сбой считается нормой, чем можно пожертвовать при пиках, важнее истина или скорость, чем бизнес готов жертвовать взамен на экономию.
Если всего этого не узнать в начале, ты рисуешь картинку — сферический сервис в вакууме, который красиво выглядит на схеме, но не имеет ничего общего с реальностью. Подробнее поговорим отдельно. Сбор требований важная большая тема.
После сбора требований логически появится следующая мысль: "**нужно определить технологический стек**". Это не про знать все языки и инструменты, это не про держать в голове энциклопедию, это не про культ «возьмем модный брокер». Это простой расчет.
Требования дают представление: «такой профиль данных, здесь читаем, тут пишем, такие хвосты распределения, такие пики». Отсюда вытекают формат данных, модель консистентности, понимание, что журнала хватит и очередь не нужна, что вот тут может быть узко, а тут проблем быть не должно. Ты уже прикидываешь, нужен ли hot path в памяти, и чем придётся платить за восстановление.
Стек складывается естественно: какие составные части, на чём они будут написаны, какие инструменты лягут в основу. Эти мысли уже можно переносить в ТЗ и схемы.
Что-то уже есть на бумаге и технологический стек первично определен. Начинаешь погружаться глубже. Теперь нужно работать со связями. Мысль три: «**нужно определить поток данных и обратную связь системы**».
На этом этапе зачастую красивые презентации заканчивается. Поток данных это не просто стрелочки от сервиса к сервису. Нужно описать ритм, как система будет жить. Где данные зарождаются, как они движутся по системе, где данные накапливаются, где могут упереться в ботлнек, где задерживаются.
И главное - как система реагирует, когда на нее давят. Не существует «пассивной» системы: если разнообразие воздействий мира на систему больше спектра ее реакций, система уходит в отказ.
Если по-простому: поток данных должен быть сопряжён с обратной связью. Когда вход растёт быстрее, чем ты можешь переварить, ты либо сигнализируешь «стоп, хватит» (backpressure), либо начинаешь управляемо деградировать. Не «всё для всех, но “сдохли”», а «меньше, но вовремя».
И вот здесь начинает появляться настоящая архитектура: система, которая умеет не геройствовать до смерти, а вовремя сказать «нет» и остаться живой.
Если обобщить: System design всегда живёт на четырёх слоях: технологическом, организационном, фундаментальном и изобретательском. Игнорируешь хоть один и система падает не там, где ждёшь, а там, где больнее всего.
❤5
Посмотрим на примере
У тебя небольшой сервис в HFT-экосистеме. Клиенты сами парсят разные внешние источники и приводят данные к формату, нужному системе.
Сервис принимает данные, собирает в пачки и отправляет дальше, в контур принятия решений. Вроде ничего сложного.
Но реальность бьет по твоей картинке цифрами. Бюджет задержки жесткий. Данные устаревают буквально спустя пару сотен миллисекунд, а еще нужно принять решение и это решение обработать. Бизнесу важнее своевременность, чем богатая обвязка вокруг каждого события. И да, падать система может, но подниматься обязана быстро, без долгих реплеев и нескольких минут на холодный старт.
С этого места любая «идеальная» схема превращается в торги с реальностью. Сначала ты, под влиянием общественности, кладёшь между нормализацией и обработкой брокер сообщений. Красиво: есть буфер, есть независимость потребителей, есть история. Но под всплесками у тебя начинается разброд в хвостах: холодные консьюмеры приходят не вовремя, очереди пухнут, рваные задержки делают p99 некрасивым. Да, это отчасти лечится, но чудес не бывает, ты платишь за удобство бэклога лишними миллисекундами и нестабильностью хвостов.
Ок, пробуешь подойти с другой стороны. Выносишь горячий путь целиком в память. Кольцевой буфер, предсказуемость, минимум аллокаций, никакой тяжелой сериализации. Красота, пока всё живо. Как только система падает, выясняется, что воспроизводимость историй тебе нужна не назавтра, а сейчас. Иначе ты либо принимаешь решения «вслепую», либо тормозишь прод ради восстановления. Местами это допустимо, но обычно — нет.
В какой-то момент ты перестаёшь играть в чёрно-белое и делаешь так, как делают взрослые: разделяешь истину и скорость.
Горячий путь идёт по памяти и держит SLA, а рядом ты пишешь минимальный, но надёжный журнал, из которого можно подняться, догнать и перепроверить. Не «всё и сразу», а ровно столько, чтобы после падения не начинать жизнь заново. Это дороже по ресурсам и по мозгам: двойная запись, идемпотентность, продуманная консистентность. Зато вместо религиозного спора «брокер или память» у тебя работает система, объединившая оба подхода.
Дальше больше, завозишь дисциплину. Ты привязываешь поведение к метрикам: когда хвосты начинают расти и превышают допустимые значения, обедняешь обработку, сохраняешь ядро.
Ты не глотаешь бесконечный вход, ты просишь столько, сколько способен обработать, а остальное честно отклоняешь. Выбираешь обратимость действий, чтобы не держаться за «идеальную транзакцию» в распределённой грязи.
Оформляешь эти решения словами, которые команда понимает: здесь у нас компенсирующая логика, тут мы разносим запись и чтение в разные модели, тут у нас предохранитель, тут выбрасываем «дорогое» обогащение при перегреве.
А далее, ты регулярно возвращаешься к схеме, не чтобы «дорисовать рамку», а чтобы проверить, что текущее поведение соответствует изменившимся требованиям.
С этого места становится видно главное. System design не про то, чтобы однажды красиво объяснить «как мы всё построим». Это про способность системы и команды менять форму, не теряя сути. Про честные компромиссы между скоростью и истиной, между простотой и гибкостью, между идеалом и тем, что реально держит прод. И да, про изобретательность по ТРИЗ: не «выбрать единственно верный инструмент», а развернуть противоречие так, чтобы выиграть временем и уменьшить боль.
У тебя небольшой сервис в HFT-экосистеме. Клиенты сами парсят разные внешние источники и приводят данные к формату, нужному системе.
Сервис принимает данные, собирает в пачки и отправляет дальше, в контур принятия решений. Вроде ничего сложного.
Но реальность бьет по твоей картинке цифрами. Бюджет задержки жесткий. Данные устаревают буквально спустя пару сотен миллисекунд, а еще нужно принять решение и это решение обработать. Бизнесу важнее своевременность, чем богатая обвязка вокруг каждого события. И да, падать система может, но подниматься обязана быстро, без долгих реплеев и нескольких минут на холодный старт.
С этого места любая «идеальная» схема превращается в торги с реальностью. Сначала ты, под влиянием общественности, кладёшь между нормализацией и обработкой брокер сообщений. Красиво: есть буфер, есть независимость потребителей, есть история. Но под всплесками у тебя начинается разброд в хвостах: холодные консьюмеры приходят не вовремя, очереди пухнут, рваные задержки делают p99 некрасивым. Да, это отчасти лечится, но чудес не бывает, ты платишь за удобство бэклога лишними миллисекундами и нестабильностью хвостов.
Ок, пробуешь подойти с другой стороны. Выносишь горячий путь целиком в память. Кольцевой буфер, предсказуемость, минимум аллокаций, никакой тяжелой сериализации. Красота, пока всё живо. Как только система падает, выясняется, что воспроизводимость историй тебе нужна не назавтра, а сейчас. Иначе ты либо принимаешь решения «вслепую», либо тормозишь прод ради восстановления. Местами это допустимо, но обычно — нет.
В какой-то момент ты перестаёшь играть в чёрно-белое и делаешь так, как делают взрослые: разделяешь истину и скорость.
Горячий путь идёт по памяти и держит SLA, а рядом ты пишешь минимальный, но надёжный журнал, из которого можно подняться, догнать и перепроверить. Не «всё и сразу», а ровно столько, чтобы после падения не начинать жизнь заново. Это дороже по ресурсам и по мозгам: двойная запись, идемпотентность, продуманная консистентность. Зато вместо религиозного спора «брокер или память» у тебя работает система, объединившая оба подхода.
Дальше больше, завозишь дисциплину. Ты привязываешь поведение к метрикам: когда хвосты начинают расти и превышают допустимые значения, обедняешь обработку, сохраняешь ядро.
Ты не глотаешь бесконечный вход, ты просишь столько, сколько способен обработать, а остальное честно отклоняешь. Выбираешь обратимость действий, чтобы не держаться за «идеальную транзакцию» в распределённой грязи.
Оформляешь эти решения словами, которые команда понимает: здесь у нас компенсирующая логика, тут мы разносим запись и чтение в разные модели, тут у нас предохранитель, тут выбрасываем «дорогое» обогащение при перегреве.
А далее, ты регулярно возвращаешься к схеме, не чтобы «дорисовать рамку», а чтобы проверить, что текущее поведение соответствует изменившимся требованиям.
С этого места становится видно главное. System design не про то, чтобы однажды красиво объяснить «как мы всё построим». Это про способность системы и команды менять форму, не теряя сути. Про честные компромиссы между скоростью и истиной, между простотой и гибкостью, между идеалом и тем, что реально держит прод. И да, про изобретательность по ТРИЗ: не «выбрать единственно верный инструмент», а развернуть противоречие так, чтобы выиграть временем и уменьшить боль.
❤4✍2😁1
Вывод
System design это широко, сложно и очень интересно. Это не про квадратики и стрелочки. Это про то, как разговаривать с реальностью на языке требований, обратной связи и компромиссов. Если твоя схема выдерживает новый источник, новый SLA и новый сбой без капитального ремонта, значит, ты всё сделал правильно. Всё остальное — декор.
Что еще почитать?
- Sean goedecke. Everything I know about good system design
- Intercom. Six principles of system design
System design это широко, сложно и очень интересно. Это не про квадратики и стрелочки. Это про то, как разговаривать с реальностью на языке требований, обратной связи и компромиссов. Если твоя схема выдерживает новый источник, новый SLA и новый сбой без капитального ремонта, значит, ты всё сделал правильно. Всё остальное — декор.
Что еще почитать?
- Sean goedecke. Everything I know about good system design
- Intercom. Six principles of system design
❤4✍1👍1
1. System design начинается с вопросов
Обычное утро понедельника. Ничего не предвещало беды. Ты наливаешь кофе, открываешь бук, пуллишь проект и начинаешь читать ночные коммиты от коллег. Тишина, спокойствие, идиллия.
И вот он. Мерзкий, пронзающий сознание звук уведомления, от которого сводит зубы. Тебе пишет PM. Сворачиваешь проект с опаской и дурным предчувствием, открываешь диалог. А там две строчки.
“Бизнес хочет ленту как у Threads.
Какие сроки?”
И вот тогда это происходит. Момент, когда дыхание замедляется. Ты переживаешь весь спектр эмоций. Сонм мыслей: "Какая вам… лента? Мы же сайт для учёта бобров", "А Твиттер вам не написать?". Мимо пролетают стадии от гнева до принятия.
Вдох, выдох. В такие моменты кажется, что согласиться и разбираться по ходу дела приемлемая стратегия. Но если принять такие правила игры, ошибки могут быть очень дорогими.
За красивой формулировкой про "ленту" нет конкретики. Ничего измеримого. Буквально ты не знаешь ничего.
Если сейчас не докопаться до сути, не собрать конкретные ожидания и измеримые показатели, дальше будет только хуже. Вместо простого проекта получится "челмедведосвин". Ты не сможешь его поддерживать. Вносить изменения будет сложно, развитие станет пыткой.
А что ты можешь сделать? Для начала пишешь PM: “Мне нужно собрать требования. Организуй встречу”.
И начинаешь подготовку к сражению. Твоим оружием будут сотни неудобных, повторяющихся и дотошных вопросов. Твоей победой станет метаморфоза влажной фантазии бизнеса в конкретные требования к проекту.
Вот почему system design начинается с вопросов, а не со схем. Вопросы это первая ступень к архитектуре. Архитектуре, которая будет работать в реальном мире, а не на бумаге.
Какие вопросы задавать и зачем, я сейчас расскажу.
Обычное утро понедельника. Ничего не предвещало беды. Ты наливаешь кофе, открываешь бук, пуллишь проект и начинаешь читать ночные коммиты от коллег. Тишина, спокойствие, идиллия.
И вот он. Мерзкий, пронзающий сознание звук уведомления, от которого сводит зубы. Тебе пишет PM. Сворачиваешь проект с опаской и дурным предчувствием, открываешь диалог. А там две строчки.
“Бизнес хочет ленту как у Threads.
Какие сроки?”
И вот тогда это происходит. Момент, когда дыхание замедляется. Ты переживаешь весь спектр эмоций. Сонм мыслей: "Какая вам… лента? Мы же сайт для учёта бобров", "А Твиттер вам не написать?". Мимо пролетают стадии от гнева до принятия.
Вдох, выдох. В такие моменты кажется, что согласиться и разбираться по ходу дела приемлемая стратегия. Но если принять такие правила игры, ошибки могут быть очень дорогими.
За красивой формулировкой про "ленту" нет конкретики. Ничего измеримого. Буквально ты не знаешь ничего.
Если сейчас не докопаться до сути, не собрать конкретные ожидания и измеримые показатели, дальше будет только хуже. Вместо простого проекта получится "челмедведосвин". Ты не сможешь его поддерживать. Вносить изменения будет сложно, развитие станет пыткой.
А что ты можешь сделать? Для начала пишешь PM: “Мне нужно собрать требования. Организуй встречу”.
И начинаешь подготовку к сражению. Твоим оружием будут сотни неудобных, повторяющихся и дотошных вопросов. Твоей победой станет метаморфоза влажной фантазии бизнеса в конкретные требования к проекту.
Вот почему system design начинается с вопросов, а не со схем. Вопросы это первая ступень к архитектуре. Архитектуре, которая будет работать в реальном мире, а не на бумаге.
Какие вопросы задавать и зачем, я сейчас расскажу.
❤1👍1
Почему вопросы это не просто формальность
Вся эта история с вопросами кажется банальной. Ну спросил, ну ответили, ну записал. Что тут сложного? А сложного тут всё.
Бизнес говорит на языке результатов. "Хотим ленту", "нужна аналитика", "сделайте как у конкурентов", "сделай чтоб работало". Это нормально, они не обязаны знать, что такое eventual consistency или почему Redis не подходит для персистентного хранения.
Твоя задача перевести эти хотелки в технические требования. И вот тут начинается самое интересное.
Каждое "хочу" скрывает десятки неявных предположений. Бизнес не думает о том, что лента должна работать при 10k RPS, что данные могут быть неконсистентными первые 5 секунд, что при падении сервиса пользователи должны видеть кеш, а не белую страницу.
Они просто хотят, чтобы "всё работало". А что значит "работало" это уже твоя головная боль.
Вопросы не придирки и не попытка усложнить простую задачу. Это способ вытащить на свет все те скрытые требования, о которых бизнес даже не подозревает.
Без этого ты будешь строить систему вслепую. Сделаешь что-то, что технически работает, но не решает реальную проблему. Или решает, но с такими компромиссами, что через полгода придётся всё сжечь и написать заново.
Вся эта история с вопросами кажется банальной. Ну спросил, ну ответили, ну записал. Что тут сложного? А сложного тут всё.
Бизнес говорит на языке результатов. "Хотим ленту", "нужна аналитика", "сделайте как у конкурентов", "сделай чтоб работало". Это нормально, они не обязаны знать, что такое eventual consistency или почему Redis не подходит для персистентного хранения.
Твоя задача перевести эти хотелки в технические требования. И вот тут начинается самое интересное.
Каждое "хочу" скрывает десятки неявных предположений. Бизнес не думает о том, что лента должна работать при 10k RPS, что данные могут быть неконсистентными первые 5 секунд, что при падении сервиса пользователи должны видеть кеш, а не белую страницу.
Они просто хотят, чтобы "всё работало". А что значит "работало" это уже твоя головная боль.
Вопросы не придирки и не попытка усложнить простую задачу. Это способ вытащить на свет все те скрытые требования, о которых бизнес даже не подозревает.
Без этого ты будешь строить систему вслепую. Сделаешь что-то, что технически работает, но не решает реальную проблему. Или решает, но с такими компромиссами, что через полгода придётся всё сжечь и написать заново.
❤2👍1
Какие вопросы задавать
Вопросы должны вытаскивать не просто пожелания, а фундаментальные ограничения системы. Архитектура это компромиссы между противоречивыми требованиями. Твоя задача найти эти противоречия до того, как они сломают систему в проде.
Начни с формальных требований, которые можно измерить и потрогать. Сколько пользователей, какие запросы, как долго система может отвечать. Это основа для всех дальнейших решений. Без цифр и строгих таргетов у тебя не проект, а путь самопознания. Если не повезёт, путь закончится не продом, а дуркой.
Но формальные требования это только верхушка айсберга. Под ними лежат неформальные требования, те, что бизнес даже не осознаёт. Что происходит, когда система падает? Пользователи ждут или уходят? Можно ли показать устаревшие данные? Что важнее, скорость или точность?
Эти неформальные требования часто противоречат друг другу. Хочешь скорость, жертвуешь консистентностью. Хочешь надёжность, усложняешь систему. Хочешь простоту, ограничиваешь функциональность. Это неизбежные компромиссы. Их нельзя избежать, можно только осознанно выбирать.
Не существует серебряной пули. Каждое простое решение скрывает за собой сложность. Когда бизнес просит "ленту как у Threads", он просит именно такую пулю, магическое решение, которое решит все проблемы без компромиссов. Твоя задача показать, что такой пули не существует, и помочь выбрать правильные компромиссы.
Здесь вступает в игру кибернетика. Есть закон необходимого разнообразия. Система может справиться с разнообразием внешней среды, только если у неё есть достаточное внутреннее разнообразие реакций. Проще говоря, если мир вокруг сложный, твоя система тоже должна быть сложной.
Но сложность это не хаос. Это структурированная сложность, где каждый элемент имеет роль и ограничения. Твои вопросы должны выявить не только что система должна делать, но и как она должна реагировать на изменения, сбои, пики нагрузки.
Каждое техническое противоречие можно решить, если правильно его сформулировать. Не "нужна и скорость, и надёжность", а "скорость там, где важно не тормозить, надёжность там, где нельзя упасть". Не "нужна и простота, и функциональность", а "простота для тех, кто просто пользуется, функциональность для тех, кому это нужно".
Твои вопросы должны вытаскивать эти противоречия на поверхность. Что важнее: время разработки или время отклика? Стоимость инфраструктуры или доступность? Простота поддержки или богатство функций?
Каждый ответ на твой вопрос должен приводить к архитектурному решению. Если бизнес говорит "нужна скорость", ты спрашиваешь "Чем можно пожертвовать ради скорости?". Если говорят "нужна надёжность", ты спрашиваешь "Что считается сбоем?". Если говорят "нужна простота", ты спрашиваешь "Простота для кого? Для разработчиков? Для пользователей? Для бизнеса?".
Без этих вопросов ты будешь строить систему, которая решает не ту проблему. Или решает правильную проблему, но с неправильными компромиссами. Или с правильными компромиссами, но в неправильном контексте.
Вопросы должны вытаскивать не просто пожелания, а фундаментальные ограничения системы. Архитектура это компромиссы между противоречивыми требованиями. Твоя задача найти эти противоречия до того, как они сломают систему в проде.
Начни с формальных требований, которые можно измерить и потрогать. Сколько пользователей, какие запросы, как долго система может отвечать. Это основа для всех дальнейших решений. Без цифр и строгих таргетов у тебя не проект, а путь самопознания. Если не повезёт, путь закончится не продом, а дуркой.
Но формальные требования это только верхушка айсберга. Под ними лежат неформальные требования, те, что бизнес даже не осознаёт. Что происходит, когда система падает? Пользователи ждут или уходят? Можно ли показать устаревшие данные? Что важнее, скорость или точность?
Эти неформальные требования часто противоречат друг другу. Хочешь скорость, жертвуешь консистентностью. Хочешь надёжность, усложняешь систему. Хочешь простоту, ограничиваешь функциональность. Это неизбежные компромиссы. Их нельзя избежать, можно только осознанно выбирать.
Не существует серебряной пули. Каждое простое решение скрывает за собой сложность. Когда бизнес просит "ленту как у Threads", он просит именно такую пулю, магическое решение, которое решит все проблемы без компромиссов. Твоя задача показать, что такой пули не существует, и помочь выбрать правильные компромиссы.
Здесь вступает в игру кибернетика. Есть закон необходимого разнообразия. Система может справиться с разнообразием внешней среды, только если у неё есть достаточное внутреннее разнообразие реакций. Проще говоря, если мир вокруг сложный, твоя система тоже должна быть сложной.
Но сложность это не хаос. Это структурированная сложность, где каждый элемент имеет роль и ограничения. Твои вопросы должны выявить не только что система должна делать, но и как она должна реагировать на изменения, сбои, пики нагрузки.
Каждое техническое противоречие можно решить, если правильно его сформулировать. Не "нужна и скорость, и надёжность", а "скорость там, где важно не тормозить, надёжность там, где нельзя упасть". Не "нужна и простота, и функциональность", а "простота для тех, кто просто пользуется, функциональность для тех, кому это нужно".
Твои вопросы должны вытаскивать эти противоречия на поверхность. Что важнее: время разработки или время отклика? Стоимость инфраструктуры или доступность? Простота поддержки или богатство функций?
Каждый ответ на твой вопрос должен приводить к архитектурному решению. Если бизнес говорит "нужна скорость", ты спрашиваешь "Чем можно пожертвовать ради скорости?". Если говорят "нужна надёжность", ты спрашиваешь "Что считается сбоем?". Если говорят "нужна простота", ты спрашиваешь "Простота для кого? Для разработчиков? Для пользователей? Для бизнеса?".
Без этих вопросов ты будешь строить систему, которая решает не ту проблему. Или решает правильную проблему, но с неправильными компромиссами. Или с правильными компромиссами, но в неправильном контексте.
👍2🔥1
Как задавать вопросы
Сбор требований это не одноразовая встреча. Это итеративный процесс, где каждый цикл углубляет понимание и выявляет новые противоречия. Это архитектурное мышление, способность видеть систему как набор взаимосвязанных компромиссов.
Есть два типа сложности: сущностная и случайная. Сущностная это то, что нельзя убрать, не изменив саму задачу. Случайная это то, что мы добавляем сами, неправильно понимая требования. Твои вопросы должны выявить сущностную и минимизировать случайную.
Начни с широкого контекста. Собери всех заинтересованных лиц и задавай открытые вопросы. Как сейчас работает процесс? Что больше всего бесит в текущем решении? Что будет, если ничего не менять? Цель понять общую картину, не вдаваясь в детали.
Записывай всё. Даже то, что кажется неважным. Потом разберёшься. Часто самые важные ограничения скрываются в мелочах, которые кажутся очевидными.
Теперь иди к каждому стейкхолдеру отдельно. У них разные интересы и приоритеты. Продукт думает про пользовательский опыт, бизнес про деньги, эксплуатация про стабильность. Каждый видит систему под своим углом, и твоя задача собрать эти углы в единую картину.
Не принимай ответы типа "ну, как обычно" или "сделайте нормально". Докапывайся до цифр. Если говорят "быстро", спрашивай "Что для вас значит быстро? Сколько миллисекунд, секунд?". Если говорят "надёжно", спрашивай "Что считать сбоем? Сколько девяток доступности? Что видит пользователь при сбое?".
К этому моменту у тебя будет список требований длиной в километр. Большинство из них будут противоречить друг другу. Это нормально. Система живёт в мире ограниченных ресурсов, где нельзя получить всё сразу.
Время для взрослых разговоров. Что важнее: скорость или надёжность? Простота или функциональность? Бюджет или качество? Не пытайся угодить всем. Лучше честно сказать "это требование увеличит сроки в два раза" и дать бизнесу принять решение.
Оформи всё в документ. Не в виде списка пожеланий, а в виде конкретных, измеримых требований. "Система должна поддерживать тысячу одновременных пользователей" вместо "должна быть надёжной". "Время ответа API не должно превышать 200 миллисекунд в 95 процентах случаев" вместо "должна работать быстро".
Этот документ станет основой для всех дальнейших решений. Каждое архитектурное решение должно быть обосновано конкретным требованием из этого документа. Если не можешь объяснить, зачем нужен тот или иной компонент, значит, этот компонент лишний.
Посмотрим как это работает на примере.
Сбор требований это не одноразовая встреча. Это итеративный процесс, где каждый цикл углубляет понимание и выявляет новые противоречия. Это архитектурное мышление, способность видеть систему как набор взаимосвязанных компромиссов.
Есть два типа сложности: сущностная и случайная. Сущностная это то, что нельзя убрать, не изменив саму задачу. Случайная это то, что мы добавляем сами, неправильно понимая требования. Твои вопросы должны выявить сущностную и минимизировать случайную.
Начни с широкого контекста. Собери всех заинтересованных лиц и задавай открытые вопросы. Как сейчас работает процесс? Что больше всего бесит в текущем решении? Что будет, если ничего не менять? Цель понять общую картину, не вдаваясь в детали.
Записывай всё. Даже то, что кажется неважным. Потом разберёшься. Часто самые важные ограничения скрываются в мелочах, которые кажутся очевидными.
Теперь иди к каждому стейкхолдеру отдельно. У них разные интересы и приоритеты. Продукт думает про пользовательский опыт, бизнес про деньги, эксплуатация про стабильность. Каждый видит систему под своим углом, и твоя задача собрать эти углы в единую картину.
Не принимай ответы типа "ну, как обычно" или "сделайте нормально". Докапывайся до цифр. Если говорят "быстро", спрашивай "Что для вас значит быстро? Сколько миллисекунд, секунд?". Если говорят "надёжно", спрашивай "Что считать сбоем? Сколько девяток доступности? Что видит пользователь при сбое?".
К этому моменту у тебя будет список требований длиной в километр. Большинство из них будут противоречить друг другу. Это нормально. Система живёт в мире ограниченных ресурсов, где нельзя получить всё сразу.
Время для взрослых разговоров. Что важнее: скорость или надёжность? Простота или функциональность? Бюджет или качество? Не пытайся угодить всем. Лучше честно сказать "это требование увеличит сроки в два раза" и дать бизнесу принять решение.
Оформи всё в документ. Не в виде списка пожеланий, а в виде конкретных, измеримых требований. "Система должна поддерживать тысячу одновременных пользователей" вместо "должна быть надёжной". "Время ответа API не должно превышать 200 миллисекунд в 95 процентах случаев" вместо "должна работать быстро".
Этот документ станет основой для всех дальнейших решений. Каждое архитектурное решение должно быть обосновано конкретным требованием из этого документа. Если не можешь объяснить, зачем нужен тот или иной компонент, значит, этот компонент лишний.
Посмотрим как это работает на примере.
👍2🔥1
Cценарий 1: Сам решил
PM приходит с задачей. "Бизнес хочет ленту новостей. Сделайте как у Threads".
Ты спрашиваешь: "А что именно имеется в виду?"
PM отвечает: "Ну, лента. Где посты идут в хронологическом порядке, с лайками и комментариями".
Вроде понятно. Ты киваешь, но в голове уже крутится: "А что, если пользователей будет много? А если нагрузка вырастет? А если понадобится поиск?".
Хоронишь эти вопросы в себе и накидываешь схему. Таблицы posts, likes, comments. API для получения ленты. И вроде красиво. Микросервисы. Кафка, прости господи, чтобы общались. Valkey за кэш. Поиск на эластике. Ну и докер, кубер, графана, прометеус. Всё как у взрослых.
Через месяц выкатываешь на прод. Сотня пользователей заходит одновременно, и система падает.
Бизнес задаёт неудобные вопросы: "Почему ничего не работает? Почему так медленно? У Threads же быстро работает!"
Ты начинаешь оптимизировать. Добавляешь кеши, тюнишь Kafka, добавляешь реплики. Каждая правка ломает что-то ещё. Через полгода у тебя зоопарк микросервисов, devops-шапито и система, которую никто не понимает, и все боятся трогать. Поздравляю, ты создал legacy.
Cценарий 2: Наорал
Ты не киваешь. Ты хватаешь PM, привязываешь его к стулу и начинается, хм, диалог.
Ты задаёшь вопросы. Сначала PM. Кто пользователи? Сколько постов в день? Кто пишет? Что пишут? Как часто заходят? А сколько пользователей через пол года, год? Что важнее, скорость или актуальность? Что считается сбоем? Можно ли показывать устаревшие данные? А нужна аналитика? А архивировать будем? Поиск нужен? Реагируем на лайки сразу? Комментарии сразу видно? А подписки? А уведомления? И так далее.
Потом идешь вместе с PM пытать бизнес и эксплуатацию.
Через несколько дней у тебя полная картина. 5к активных пользователей. Сто постов в день. Пик нагрузки в обед и вечером. Главное — скорость. Можно жертвовать актуальностью лайков и комментариев. Простои до получаса допустимы. Архивируем через пол года. Поиск простой. Рост медленный. И тд. и тп.
Ты делаешь схему под это. Одна таблица posts с денормализованными данными. Один Valkey для кеша с TTL пять минут. Никакой плеяды микросервисов. Никаких очередей. Одна база, один сервис.
Лента открывается быстро. Система держит нагрузку. Код простой, понятный, надёжный. Бизнес доволен. Тебе не задают неудобные вопросы.
Всё то модное, что ты впихнул в первом сценарии, оказалось ненужным.
Во втором случае ты потратил часы на вопросы, но сэкономил месяцы на разработке и поддержке. Вопросы помогли увидеть реальную задачу и отбросить лишнее.
PM приходит с задачей. "Бизнес хочет ленту новостей. Сделайте как у Threads".
Ты спрашиваешь: "А что именно имеется в виду?"
PM отвечает: "Ну, лента. Где посты идут в хронологическом порядке, с лайками и комментариями".
Вроде понятно. Ты киваешь, но в голове уже крутится: "А что, если пользователей будет много? А если нагрузка вырастет? А если понадобится поиск?".
Хоронишь эти вопросы в себе и накидываешь схему. Таблицы posts, likes, comments. API для получения ленты. И вроде красиво. Микросервисы. Кафка, прости господи, чтобы общались. Valkey за кэш. Поиск на эластике. Ну и докер, кубер, графана, прометеус. Всё как у взрослых.
Через месяц выкатываешь на прод. Сотня пользователей заходит одновременно, и система падает.
Бизнес задаёт неудобные вопросы: "Почему ничего не работает? Почему так медленно? У Threads же быстро работает!"
Ты начинаешь оптимизировать. Добавляешь кеши, тюнишь Kafka, добавляешь реплики. Каждая правка ломает что-то ещё. Через полгода у тебя зоопарк микросервисов, devops-шапито и система, которую никто не понимает, и все боятся трогать. Поздравляю, ты создал legacy.
Cценарий 2: Наорал
Ты не киваешь. Ты хватаешь PM, привязываешь его к стулу и начинается, хм, диалог.
Ты задаёшь вопросы. Сначала PM. Кто пользователи? Сколько постов в день? Кто пишет? Что пишут? Как часто заходят? А сколько пользователей через пол года, год? Что важнее, скорость или актуальность? Что считается сбоем? Можно ли показывать устаревшие данные? А нужна аналитика? А архивировать будем? Поиск нужен? Реагируем на лайки сразу? Комментарии сразу видно? А подписки? А уведомления? И так далее.
Потом идешь вместе с PM пытать бизнес и эксплуатацию.
Через несколько дней у тебя полная картина. 5к активных пользователей. Сто постов в день. Пик нагрузки в обед и вечером. Главное — скорость. Можно жертвовать актуальностью лайков и комментариев. Простои до получаса допустимы. Архивируем через пол года. Поиск простой. Рост медленный. И тд. и тп.
Ты делаешь схему под это. Одна таблица posts с денормализованными данными. Один Valkey для кеша с TTL пять минут. Никакой плеяды микросервисов. Никаких очередей. Одна база, один сервис.
Лента открывается быстро. Система держит нагрузку. Код простой, понятный, надёжный. Бизнес доволен. Тебе не задают неудобные вопросы.
Всё то модное, что ты впихнул в первом сценарии, оказалось ненужным.
Во втором случае ты потратил часы на вопросы, но сэкономил месяцы на разработке и поддержке. Вопросы помогли увидеть реальную задачу и отбросить лишнее.
👍1🔥1
Что в итоге
System design это не про схемы. Это умение задавать правильные вопросы и вытаскивать из бизнеса реальные требования, а не слепо следовать его влажным фантазиям.
Без вопросов ты строишь систему вслепую. С вопросами решаешь конкретную задачу конкретными средствами.
Но вопросы это только начало. Дальше нужно понять, как переводить требования в архитектурные решения, выбирать правильные компромиссы и не поддаваться на соблазн "сделать как у больших дядек".
В следующих частях расскажу, почему system design держится на трех принципах, как из требований получается архитектура, как выбирать правильные компромиссы и почему простое решение часто лучше сложного.
А пока перестань кивать на хотелки бизнеса и начни задавать неудобные вопросы. Скажешь себе потом спасибо.
Что еще почитать?
- 0. System design – это тебе не квадратики рисовать
- Ordering Interrogative Questions for Effective Requirements Engineering: The W6H Pattern - немного академки
- System Design: Чек-лист по сбору и фиксации требований на все случае жизни - больше конкретики
- Бунин О.В. Highload 1. Сбор требований. Фронтенд-бэкенд - оч советую посмотреть этот курс целиком
System design это не про схемы. Это умение задавать правильные вопросы и вытаскивать из бизнеса реальные требования, а не слепо следовать его влажным фантазиям.
Без вопросов ты строишь систему вслепую. С вопросами решаешь конкретную задачу конкретными средствами.
Но вопросы это только начало. Дальше нужно понять, как переводить требования в архитектурные решения, выбирать правильные компромиссы и не поддаваться на соблазн "сделать как у больших дядек".
В следующих частях расскажу, почему system design держится на трех принципах, как из требований получается архитектура, как выбирать правильные компромиссы и почему простое решение часто лучше сложного.
А пока перестань кивать на хотелки бизнеса и начни задавать неудобные вопросы. Скажешь себе потом спасибо.
Что еще почитать?
- 0. System design – это тебе не квадратики рисовать
- Ordering Interrogative Questions for Effective Requirements Engineering: The W6H Pattern - немного академки
- System Design: Чек-лист по сбору и фиксации требований на все случае жизни - больше конкретики
- Бунин О.В. Highload 1. Сбор требований. Фронтенд-бэкенд - оч советую посмотреть этот курс целиком
🔥4
2. System design держится на трёх столпах
Допустим, ты успешно собрал требования, был посланы куда-нибудь пару раз, выбил из бизнеса конкретику и понял, что система должна делать и как себя вести. Теперь у тебя есть "документ" с цифрами, SLA, ограничениями, функциональными и нефункциональными требованиями.
Отлично. И вот ты смотришь на этот список, список длинный, много буков и цифров. Что дальше? Как из этого "документа" получить архитектуру?
Для начала нужно налить себе чего-нибудь, например, кофе. Закрыться у себя в кабинете, чтобы тебе никто не мог помешать. А дальше придётся вспомнить много теории и начать думать. Знаю, звучит сложно, особенно про думать, но придётся с этим как-то жить. По-другому никак.
А вспоминать лучше всего начать с трёх столпов любой архитектуры: надёжности, масштабируемости и сопровождаемости. И все три столпа это компромиссы. Каждый из них конфликтует с остальными. Улучшаешь один и ты убиваешь другие.
Не понимая этой базы, любая проектируемая система будет строиться заведомо с меньшими шансами на выживание в реальном мире. С каждым шагом ты будешь желать всё сжечь и уволиться всё больше и больше.
Поэтому предлагаю тебе устроиться поудобнее и почитать про базу system design.
Что там за столпы такие?
Начну сразу с ремарки: надёжность, масштабируемость и сопровождаемость это не красивые слова из ролика на ютубе или презентации. Это конкретные инженерные свойства системы, их можно измерить, потрогать, пощупать.
Надёжность — это способность системы работать в условиях, когда вот-вот всё упадёт и когда уже часть упала. Не "не падать никогда", а "падать предсказуемо и восстанавливаться быстро". Предсказуемая деградация функциональности. Какой таргет по доступности? Что происходит при отказе компонента? Как быстро система возвращается к нормальной работе? Можно ли и какие данные терять при сбое?
Масштабируемость — это способность системы справляться с ростом нагрузки без деградации качества. Не "держать любую нагрузку", а "держать запланированную нагрузку в рамках SLA". Как система ведёт себя при 2x, 10x, 100x росте трафика? Где появляются узкие места? Можно ли масштабировать по частям или только целиком?
Сопровождаемость — это способность системы изменяться без катастрофических последствий. Не "код должен быть красивым", а "изменения должны быть предсказуемыми и безопасными". Сколько времени уходит на добавление фичи? Как часто изменения ломают что-то ещё? Можно ли откатить изменения? Сколько людей нужно для поддержки системы?
Вот эти три свойства и определяют архитектуру. Все три свойства противоречат друг другу. Постоянная борьба за баланс и компромисс.
Допустим, ты успешно собрал требования, был посланы куда-нибудь пару раз, выбил из бизнеса конкретику и понял, что система должна делать и как себя вести. Теперь у тебя есть "документ" с цифрами, SLA, ограничениями, функциональными и нефункциональными требованиями.
Отлично. И вот ты смотришь на этот список, список длинный, много буков и цифров. Что дальше? Как из этого "документа" получить архитектуру?
Для начала нужно налить себе чего-нибудь, например, кофе. Закрыться у себя в кабинете, чтобы тебе никто не мог помешать. А дальше придётся вспомнить много теории и начать думать. Знаю, звучит сложно, особенно про думать, но придётся с этим как-то жить. По-другому никак.
А вспоминать лучше всего начать с трёх столпов любой архитектуры: надёжности, масштабируемости и сопровождаемости. И все три столпа это компромиссы. Каждый из них конфликтует с остальными. Улучшаешь один и ты убиваешь другие.
Не понимая этой базы, любая проектируемая система будет строиться заведомо с меньшими шансами на выживание в реальном мире. С каждым шагом ты будешь желать всё сжечь и уволиться всё больше и больше.
Поэтому предлагаю тебе устроиться поудобнее и почитать про базу system design.
Что там за столпы такие?
Начну сразу с ремарки: надёжность, масштабируемость и сопровождаемость это не красивые слова из ролика на ютубе или презентации. Это конкретные инженерные свойства системы, их можно измерить, потрогать, пощупать.
Надёжность — это способность системы работать в условиях, когда вот-вот всё упадёт и когда уже часть упала. Не "не падать никогда", а "падать предсказуемо и восстанавливаться быстро". Предсказуемая деградация функциональности. Какой таргет по доступности? Что происходит при отказе компонента? Как быстро система возвращается к нормальной работе? Можно ли и какие данные терять при сбое?
Масштабируемость — это способность системы справляться с ростом нагрузки без деградации качества. Не "держать любую нагрузку", а "держать запланированную нагрузку в рамках SLA". Как система ведёт себя при 2x, 10x, 100x росте трафика? Где появляются узкие места? Можно ли масштабировать по частям или только целиком?
Сопровождаемость — это способность системы изменяться без катастрофических последствий. Не "код должен быть красивым", а "изменения должны быть предсказуемыми и безопасными". Сколько времени уходит на добавление фичи? Как часто изменения ломают что-то ещё? Можно ли откатить изменения? Сколько людей нужно для поддержки системы?
Вот эти три свойства и определяют архитектуру. Все три свойства противоречат друг другу. Постоянная борьба за баланс и компромисс.
👍3
Почему же они конфликтуют?
Потому что мир конечен. У тебя ограниченный бюджет, ограниченное время, ограниченные ресурсы. И каждое решение, которое улучшает одно свойство, ухудшает другое.
Хочешь надёжность? Добавляешь реплики, валидацию, проверки, мониторинг. Пишешь код для контролируемой деградации, отлаживаешь управляемую остановку сервисов. Добавляешь код для работы с граничными случаями. Система становится сложнее, медленнее, дороже. Появляется больше абстракций. Сопровождаемость падает. Больше кода, больше конфигов, больше точек отказа.
Хочешь масштабируемость? Разносишь по серверам сервисы, добавляй кеши, настраиваешь шардинг, появляется желание завозить очереди, прости господи. Пишешь больше кода для обеспечения согласованности. Думаешь в сторону микросервисов и распределенки. Система становится сложнее, появляются новые типы сбоев, консистентность данных страдает. Надёжность падает. Больше компонентов, больше сетевых вызовов, больше вероятность упасть.
Хочешь сопровождаемость? Упрощаешь архитектуру, минимизируешь зависимости, пишешь простой код. Но система становится менее гибкой, хуже масштабируется, сложнее оптимизируется. Думаешь в сторону монолита. Масштабируемость падает. Масштабирование по частям становится практически невозможным, нельзя использовать некоторые инструменты.
К сожалению, таковы законы природы. Нельзя получить всё, везде и сразу. Постоянный торг, постоянный поиск компромиссов.
Потому что мир конечен. У тебя ограниченный бюджет, ограниченное время, ограниченные ресурсы. И каждое решение, которое улучшает одно свойство, ухудшает другое.
Хочешь надёжность? Добавляешь реплики, валидацию, проверки, мониторинг. Пишешь код для контролируемой деградации, отлаживаешь управляемую остановку сервисов. Добавляешь код для работы с граничными случаями. Система становится сложнее, медленнее, дороже. Появляется больше абстракций. Сопровождаемость падает. Больше кода, больше конфигов, больше точек отказа.
Хочешь масштабируемость? Разносишь по серверам сервисы, добавляй кеши, настраиваешь шардинг, появляется желание завозить очереди, прости господи. Пишешь больше кода для обеспечения согласованности. Думаешь в сторону микросервисов и распределенки. Система становится сложнее, появляются новые типы сбоев, консистентность данных страдает. Надёжность падает. Больше компонентов, больше сетевых вызовов, больше вероятность упасть.
Хочешь сопровождаемость? Упрощаешь архитектуру, минимизируешь зависимости, пишешь простой код. Но система становится менее гибкой, хуже масштабируется, сложнее оптимизируется. Думаешь в сторону монолита. Масштабируемость падает. Масштабирование по частям становится практически невозможным, нельзя использовать некоторые инструменты.
К сожалению, таковы законы природы. Нельзя получить всё, везде и сразу. Постоянный торг, постоянный поиск компромиссов.
👍4