Забитая фаза 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
Пример
Давай покажу примеры, как эти компромиссы работают на практике.
Кеш vs консистентность
Начнём с классики. Хочешь скорость? Добавляй кеш. Данные читаются из памяти, ответы прилетают быстрее. Масштабируемость растёт, нагрузка на базу падает.
Но что происходит с консистентностью? Данные в кеше могут быть устаревшими. Пользователь видит старую информацию. Если кеш упал, время ответа кратно возрастает. Если кеш переполнился, часть данных вытесняется.
Можешь добавить инвалидацию кеша, но тогда кеш становится сложнее. Можешь использовать write-through, но тогда записи становятся медленнее. Можешь использовать eventual consistency, но тогда пользователи видят разные данные в момент времени.
Выбор зависит от требований. Для ленты новостей eventual consistency это нормально. Для банковского баланса совершенно неприемлемо.
Микросервисы vs простота
Хочешь независимые релизы и масштабирование по частям? Завози микросервисы. Каждый сервис можно разрабатывать, тестировать и деплоить отдельно. Команды не мешают друг другу.
Но что происходит со сложностью? Появляются сетевые вызовы, нужно думать про таймауты и ретраи. Руки тянутся к очередям, сложность x10. Данные размазываются по сервисам, консистентность становится проблемой. Отладка усложняется, практически неминуемая большая связность, один запрос проходит через десяток сервисов.
Можешь добавить service mesh, но тогда появляется ещё один слой абстракции. Можешь использовать event sourcing, но тогда система становится ещё сложнее. Можешь минимизировать количество сервисов, но тогда теряешь преимущества микросервисов.
Примерно тут ты вспоминаешь про саги. Способ склеить распределённые транзакции без 2PC. Каждая операция локальна, но цепочка координируется сообщениями. Если шаг падает, запускается компенсирующее действие. В теории красиво, на практике превращается в ад отложенных событий и "ручного" восстановления состояния. Любая ошибка в оркестрации, и данные начинают расходиться.
Выбор зависит от размера команды и сложности предметной области. Для стартапа из трёх человек микросервисы это оверинжиниринг. Для команды из ста человек монолит будет якорем.
Синхронность vs асинхронность
Хочешь простоту и предсказуемость? Делай всё синхронно. Запрос пришёл, обработался, ответ ушёл. Легко отлаживать, легко тестировать, легко понимать.
А что с производительностью? Медленная операция блокирует весь запрос. Если один компонент тормозит, тормозит вся система. Масштабировать можно только вертикально.
Можешь добавить очереди, прости господи, но тогда появляется eventual consistency. Можешь использовать неблокирующие вызовы, но тогда код становится сложнее. Можешь разбить операцию на этапы, но тогда нужно думать про компенсацию, привет саги.
Выбор зависит от требований к задержкам и пропускной способности. Для систем реального времени синхронность может быть критична. Для batch-обработки асинхронность это необходимость.
Давай покажу примеры, как эти компромиссы работают на практике.
Кеш vs консистентность
Начнём с классики. Хочешь скорость? Добавляй кеш. Данные читаются из памяти, ответы прилетают быстрее. Масштабируемость растёт, нагрузка на базу падает.
Но что происходит с консистентностью? Данные в кеше могут быть устаревшими. Пользователь видит старую информацию. Если кеш упал, время ответа кратно возрастает. Если кеш переполнился, часть данных вытесняется.
Можешь добавить инвалидацию кеша, но тогда кеш становится сложнее. Можешь использовать write-through, но тогда записи становятся медленнее. Можешь использовать eventual consistency, но тогда пользователи видят разные данные в момент времени.
Выбор зависит от требований. Для ленты новостей eventual consistency это нормально. Для банковского баланса совершенно неприемлемо.
Микросервисы vs простота
Хочешь независимые релизы и масштабирование по частям? Завози микросервисы. Каждый сервис можно разрабатывать, тестировать и деплоить отдельно. Команды не мешают друг другу.
Но что происходит со сложностью? Появляются сетевые вызовы, нужно думать про таймауты и ретраи. Руки тянутся к очередям, сложность x10. Данные размазываются по сервисам, консистентность становится проблемой. Отладка усложняется, практически неминуемая большая связность, один запрос проходит через десяток сервисов.
Можешь добавить service mesh, но тогда появляется ещё один слой абстракции. Можешь использовать event sourcing, но тогда система становится ещё сложнее. Можешь минимизировать количество сервисов, но тогда теряешь преимущества микросервисов.
Примерно тут ты вспоминаешь про саги. Способ склеить распределённые транзакции без 2PC. Каждая операция локальна, но цепочка координируется сообщениями. Если шаг падает, запускается компенсирующее действие. В теории красиво, на практике превращается в ад отложенных событий и "ручного" восстановления состояния. Любая ошибка в оркестрации, и данные начинают расходиться.
Выбор зависит от размера команды и сложности предметной области. Для стартапа из трёх человек микросервисы это оверинжиниринг. Для команды из ста человек монолит будет якорем.
Синхронность vs асинхронность
Хочешь простоту и предсказуемость? Делай всё синхронно. Запрос пришёл, обработался, ответ ушёл. Легко отлаживать, легко тестировать, легко понимать.
А что с производительностью? Медленная операция блокирует весь запрос. Если один компонент тормозит, тормозит вся система. Масштабировать можно только вертикально.
Можешь добавить очереди, прости господи, но тогда появляется eventual consistency. Можешь использовать неблокирующие вызовы, но тогда код становится сложнее. Можешь разбить операцию на этапы, но тогда нужно думать про компенсацию, привет саги.
Выбор зависит от требований к задержкам и пропускной способности. Для систем реального времени синхронность может быть критична. Для batch-обработки асинхронность это необходимость.
👍4🤣1
И как выбирать эти ваши компромиссы?
Ага, примерно тут, ты достаёшь из широких штанин, толщиною с консервную банку, хм, документ. Тот, что ты собирал с боем в прошлый раз. Требования. Каждое архитектурное решение должно быть обосновано конкретной строкой или абзацем в этом документе.
Если бизнес кричал «нужна скорость», теперь ты смотришь какой ценой.
Где они согласились терпеть боль. Медленную запись? Потерю консистентности? Сложность кода?
Если требовали «надёжность», ты открываешь SLA и смотришь, что на самом деле под этим имелось в виду.
Сколько минут даунтайма они готовы пережить, сколько данных потерять, какие ошибки простить.
Если просили «простоту» ищешь, для кого именно.
Для разработчиков, чтобы быстрее пилить фичи? Для пользователей, чтобы не путались в интерфейсе?
Или для эксплуатации, чтобы не вылавливать зомби-процессы ночью?
Тут появляется концепция "бюджета боли". У тебя есть ограниченный бюджет на сложность, на производительность, на надёжность. Ты не можешь получить всё сразу. Ты можешь только осознанно распределить этот бюджет.
Что за бюджет такой?
Представь, что у тебя есть сто деняк. Ты можешь потратить их на надёжность, на масштабируемость или на сопровождаемость. Но на всё сразу у тебя не хватает.
Если потратишь всё на надёжность, получишь систему, которая не падает, но тормозит и которую никто не понимает. Если потратишь всё на масштабируемость, получишь систему, которая держит нагрузку, но падает от любого чиха. Если потратишь всё на сопровождаемость, получишь систему, которую легко поддерживать, но которая не масштабируется.
Правильный подход потратить бюджет осознанно. Понять, что важнее для твоего конкретного случая, и вложиться в это. А в остальном принять компромиссы.
Ага, примерно тут, ты достаёшь из широких штанин, толщиною с консервную банку, хм, документ. Тот, что ты собирал с боем в прошлый раз. Требования. Каждое архитектурное решение должно быть обосновано конкретной строкой или абзацем в этом документе.
Если бизнес кричал «нужна скорость», теперь ты смотришь какой ценой.
Где они согласились терпеть боль. Медленную запись? Потерю консистентности? Сложность кода?
Если требовали «надёжность», ты открываешь SLA и смотришь, что на самом деле под этим имелось в виду.
Сколько минут даунтайма они готовы пережить, сколько данных потерять, какие ошибки простить.
Если просили «простоту» ищешь, для кого именно.
Для разработчиков, чтобы быстрее пилить фичи? Для пользователей, чтобы не путались в интерфейсе?
Или для эксплуатации, чтобы не вылавливать зомби-процессы ночью?
Тут появляется концепция "бюджета боли". У тебя есть ограниченный бюджет на сложность, на производительность, на надёжность. Ты не можешь получить всё сразу. Ты можешь только осознанно распределить этот бюджет.
Что за бюджет такой?
Представь, что у тебя есть сто деняк. Ты можешь потратить их на надёжность, на масштабируемость или на сопровождаемость. Но на всё сразу у тебя не хватает.
Если потратишь всё на надёжность, получишь систему, которая не падает, но тормозит и которую никто не понимает. Если потратишь всё на масштабируемость, получишь систему, которая держит нагрузку, но падает от любого чиха. Если потратишь всё на сопровождаемость, получишь систему, которую легко поддерживать, но которая не масштабируется.
Правильный подход потратить бюджет осознанно. Понять, что важнее для твоего конкретного случая, и вложиться в это. А в остальном принять компромиссы.
👍2🔥2🤔1
Пример: система уведомлений
Допустим, тебе нужно построить систему уведомлений. Бизнес говорит: "Нужно отправлять уведомления пользователям. Быстро, надёжно, много."
Ты собираешь требования и понимаешь:
- 100k пользователей, 10k уведомлений в день
- Время доставки не критично, можно в течение минуты
- Потеря 1% уведомлений допустима
- Команда из пяти человек, бюджет ограничен
Теперь ты можешь осознанно выбирать компромиссы:
Надёжность vs простота: Можно сделать простую систему, один процесс, одна база, прямой вызов. Но если процесс упадёт, часть задач потеряется. Можно добавить репликацию и мониторинг, но система станет сложнее.
Масштабируемость vs консистентность: Можно гарантировать, что каждое событие будет обработано ровно один раз, но тогда придётся решать вопросы дубликатов и идемпотентности. Можно выбрать eventual consistency, но тогда часть данных может устаревать или теряться.
Сопровождаемость vs производительность: Можно написать простой и прозрачный код, который легко читать и менять. Но он не выдержит пиков. Можно добавить кэширование, пайплайны и оптимизации, но код станет запутаннее и более хрупким.
Исходя из требований, ты выбираешь: простота важнее надёжности (1% потерь допустимы), eventual consistency важнее строгой консистентности (задержка не критична), простота важнее производительности (нагрузка невысокая).
Получается простая система: прямые вызовы, базовая обработка ошибок, минимальный контроль состояния. Не идеально, но соответствует заявленным требованиям.
Допустим, тебе нужно построить систему уведомлений. Бизнес говорит: "Нужно отправлять уведомления пользователям. Быстро, надёжно, много."
Ты собираешь требования и понимаешь:
- 100k пользователей, 10k уведомлений в день
- Время доставки не критично, можно в течение минуты
- Потеря 1% уведомлений допустима
- Команда из пяти человек, бюджет ограничен
Теперь ты можешь осознанно выбирать компромиссы:
Надёжность vs простота: Можно сделать простую систему, один процесс, одна база, прямой вызов. Но если процесс упадёт, часть задач потеряется. Можно добавить репликацию и мониторинг, но система станет сложнее.
Масштабируемость vs консистентность: Можно гарантировать, что каждое событие будет обработано ровно один раз, но тогда придётся решать вопросы дубликатов и идемпотентности. Можно выбрать eventual consistency, но тогда часть данных может устаревать или теряться.
Сопровождаемость vs производительность: Можно написать простой и прозрачный код, который легко читать и менять. Но он не выдержит пиков. Можно добавить кэширование, пайплайны и оптимизации, но код станет запутаннее и более хрупким.
Исходя из требований, ты выбираешь: простота важнее надёжности (1% потерь допустимы), eventual consistency важнее строгой консистентности (задержка не критична), простота важнее производительности (нагрузка невысокая).
Получается простая система: прямые вызовы, базовая обработка ошибок, минимальный контроль состояния. Не идеально, но соответствует заявленным требованиям.
👍2🔥2
Что в итоге
System design это не про то, чтобы найти "правильную" архитектуру, тем более не про "чистую". Правильной архитектуры не существует. Есть только архитектура, которая подходит под твои требования и ограничения, и даёт шанс на выживание проекта.
Три столпа: надёжность, масштабируемость и сопровождаемость. Три, казалось бы, простых таргета, которые есть в каждой системе, причиняют огромное количество боли. Особенно, если наступить не в тот компромисс или войти не в ту дверь.
Искусство настоящего архитектора - умело жонглировать этой болью, принимать решения, жертвовать, ради достижения поставленной цели.
Рождение архитектуры, в котором помогает system design, интересный, тяжёлый и завораживающий итеративный процесс, поучаствовать в котором я всем желаю.
В следующий раз попробую показать на примере, как последовательно от требований, от документа, толщиною с консервную банку, перейти к архитектурному решению с шансами на жизнь.
А пока читайте книги, всем добра.
Что еще почитать?
- 1. System design начинается с вопросов
- High Scalability — Real World Architectures - много интересных примеров
- Martin Fowler — Microservices Trade-Offs - про компромисы в микросервисах
System design это не про то, чтобы найти "правильную" архитектуру, тем более не про "чистую". Правильной архитектуры не существует. Есть только архитектура, которая подходит под твои требования и ограничения, и даёт шанс на выживание проекта.
Три столпа: надёжность, масштабируемость и сопровождаемость. Три, казалось бы, простых таргета, которые есть в каждой системе, причиняют огромное количество боли. Особенно, если наступить не в тот компромисс или войти не в ту дверь.
Искусство настоящего архитектора - умело жонглировать этой болью, принимать решения, жертвовать, ради достижения поставленной цели.
Рождение архитектуры, в котором помогает system design, интересный, тяжёлый и завораживающий итеративный процесс, поучаствовать в котором я всем желаю.
В следующий раз попробую показать на примере, как последовательно от требований, от документа, толщиною с консервную банку, перейти к архитектурному решению с шансами на жизнь.
А пока читайте книги, всем добра.
Что еще почитать?
- 1. System design начинается с вопросов
- High Scalability — Real World Architectures - много интересных примеров
- Martin Fowler — Microservices Trade-Offs - про компромисы в микросервисах
🔥4👍2😨1
3. System design. Потоки …, гхм, данных
Штош. Тут должен быть мем с котом, но его не будет. Немного выбило меня из ритма повествования. Можно меня поздравить, я теперь совсем безработный, буду чилить и возможно даже больше писать "умных мюслей". А может, и не буду, а может, завтра уже буду работать в новом месте, а может, и нет. Посмотримс
Ну ладно, минутка офтопа закончилась. Вернёмся к system design.
В прошлый раз я закончил статью анонсом большого практического материала. Хотел на практике показать, как получается архитектура на основании собранных требований. Материал написал, получилось много, сложно и как-то скомкано. Не получилось впихнуть невпихуемое в рамки одной статьи. Поэтому переобуваюсь в прыжке и предлагаю тебе продолжить знакомится с важными элементами system design.
На этот раз расскажу тебе про "Потоки данных", зачем стоит заранее подумать, куда и как текут данные, почему это знание значительно увеличит шансы на выживание проекта, и как не получить потоки говны вместо данных.
Штош. Тут должен быть мем с котом, но его не будет. Немного выбило меня из ритма повествования. Можно меня поздравить, я теперь совсем безработный, буду чилить и возможно даже больше писать "умных мюслей". А может, и не буду, а может, завтра уже буду работать в новом месте, а может, и нет. Посмотримс
Ну ладно, минутка офтопа закончилась. Вернёмся к system design.
В прошлый раз я закончил статью анонсом большого практического материала. Хотел на практике показать, как получается архитектура на основании собранных требований. Материал написал, получилось много, сложно и как-то скомкано. Не получилось впихнуть невпихуемое в рамки одной статьи. Поэтому переобуваюсь в прыжке и предлагаю тебе продолжить знакомится с важными элементами system design.
На этот раз расскажу тебе про "Потоки данных", зачем стоит заранее подумать, куда и как текут данные, почему это знание значительно увеличит шансы на выживание проекта, и как не получить потоки говны вместо данных.
❤2🔥2