← назад к разделу

«Надёжная», «масштабируемая», «поддерживаемая» — эти слова вешают на любую систему, обычно не вкладывая в них ничего конкретного. А ведь у каждого из трёх качеств есть точный смысл, и почти весь системный дизайн — это осознанный размен между ними: усилили одно — обычно чем-то заплатили в другом. Прежде чем проектировать систему по шагам, стоит разобраться, что эти три слова означают на самом деле. Эта статья — про сами понятия, простым языком.

Обязательно

Надёжность: сбой — это ещё не отказспросят на собеседовании

Первое разграничение, которое сразу наводит порядок в голове: сбой и отказ — это разные вещи.

  • Сбой (fault) — когда один компонент отклонился от нормы: умер диск, зависла реплика, человек залил неверный конфиг.
  • Отказ (failure) — когда система целиком перестала делать то, что нужно пользователю.

Построить систему, в которой не бывает сбоев, невозможно — детали ломаются всегда. Поэтому задача дизайна не в этом. Задача — не дать отдельному сбою превратиться в отказ всей системы. Диск умер, а реплика продолжает отдавать данные; нода зависла, а балансировщик убрал её из ротации, и пользователь ничего не заметил. Устойчивость строится не из идеальных деталей, а из обычных, поломки которых заранее предусмотрены.

сбой изолирован умер диск балансировщик убрал запрос обслужен сбой стал отказом неверный конфиг разъехался на узлы ответа нет

Сверху сбой остался сбоем, снизу дошёл до пользователя; разница видна на втором шаге каждого ряда.

Сбои приходят из трёх мест, и лечится каждый по-своему:

  • Железо. Диски, память, питание, сеть. Такие сбои случайны и почти не связаны друг с другом: в большом кластере из 10 000 дисков в среднем один умирает каждый день — это норма, а не ЧП. Лечится избыточностью: RAID, реплики, разнесение по разным зонам (в методе это называют доменами отказов).
  • Софт. Ошибка в коде: обработчик падает на определённом входе, процесс потихоньку съедает общий ресурс, один мелкий сбой каскадом валит соседние сервисы. В отличие от железа, такие сбои связанные — одна и та же ошибка срабатывает сразу на всех одинаковых узлах, поэтому резервный сервер не спасает. Лечится изоляцией процессов, тестами и самопроверками (когда система на ходу проверяет, что её данные не разъехались).
  • Человек. Исследования крупных сервисов показывают неудобную вещь: чаще всего систему роняют не диски, а люди — операторы, которые что-то не так поменяли в конфигурации. На железо приходится лишь 10–25 % простоев. Лечится это тоже дизайном: делать интерфейсы, где правильное действие — самое простое; давать песочницу на реальных данных; уметь быстро откатываться; выкатывать изменения сначала на маленькую долю пользователей.

Есть контринтуитивный приём: раз сбои всё равно неизбежны — устраивать их нарочно. Например, случайно убивать процессы прямо в бою и смотреть, выживет ли система (так работает Chaos Monkey в Netflix). Это проверяет не столько систему, сколько вашу уверенность: механизмы устойчивости действительно работают, а не просто числятся на схеме.

Только «убивать процессы в бою» без подготовки — это не проверка устойчивости, а самодельная авария. У приёма есть порядок, и без него он приносит вред:

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

Потом гипотеза, а не любопытство. Эксперимент начинается с утверждения, которое вы проверяете: «если погасить одну копию сервиса заказов, доля ошибок не превысит 0,1 %, а время ответа вырастет не больше чем вдвое». Такое утверждение либо подтверждается, либо нет — и в обоих случаях вы что-то узнали. «Погасим и посмотрим» ничего не проверяет: что бы ни случилось, вывод сделать не из чего.

Радиус поражения — минимальный и заранее известный. Начинают с одной копии, одной зоны, одного процента трафика, и знают заранее, как остановить эксперимент. Правило: если вы не можете отменить эксперимент за минуту, его рано проводить.

Рабочее время и предупреждённая команда. Эксперимент в три ночи, о котором никто не знал, превращается в инцидент с разбором. Проводят днём, когда все на месте, объявив заранее.

И порядок по возрастанию цены. Сначала на тестовом контуре, потом в проде на одной копии, потом на зоне, и только потом что-то посерьёзнее. Первый эксперимент, который стоит провести, почти всегда один и тот же и самый дешёвый: погасить одну копию сервиса и посмотреть, заметил ли это кто-нибудь, кроме графика.

Если из эксперимента вышла авария — это тоже результат, но правильный вывод из него не «больше так не делаем», а «мы нашли настоящую слабость до того, как она нашла нас сама». Разбирают такое так же, как обычный инцидент.

Арифметика доступности: девятки, минуты и цепочкиспросят на собеседовании

«Доступность 99,9 %» звучит как качественная характеристика, а это арифметика, и без неё требования обсуждать нельзя. Первое — перевести проценты в минуты, потому что в минутах спорят, а в процентах соглашаются:

ДоступностьПростой в месяцПростой в год
99 % («две девятки»)около 7 часовоколо 3,7 суток
99,9 % («три девятки»)около 43 минутоколо 8,8 часов
99,95 %около 22 минутоколо 4,4 часов
99,99 % («четыре девятки»)около 4,3 минутоколо 53 минут
99,999 % («пять девяток»)около 26 секундоколо 5 минут

Что из этой таблицы важно: три девятки — это один час простоя в год с запасом, и почти любая обычная система их даёт. Четыре девятки означают, что у вас нет времени на человека: 53 минуты в год — это меньше, чем нужно дежурному, чтобы проснуться, разобраться и починить. Значит, четыре девятки — это не про старание, а про автоматическое переключение без участия людей, и стоит это совсем других денег.

Второе — доступность перемножается по цепочке. Если запрос пользователя проходит через пять звеньев и каждое доступно на 99,9 %, то вся цепочка даёт 0,999⁵ ≈ 99,5 % — то есть около 3,6 часов простоя в месяц вместо 43 минут. Это самый недооценённый факт в проектировании: добавляя зависимость, вы уменьшаете свою доступность, даже если новая зависимость «надёжная».

Отсюда три практических следствия, которыми пользуются постоянно:

  • Ваша доступность не может быть выше, чем у зависимостей на критическом пути. Обещать 99,99 % на операции с оплатой, когда у платёжного провайдера в договоре 99,9 %, физически нельзя. Поэтому первый вопрос про требование — «а из чего складывается путь запроса и что там самое слабое».
  • Убрать зависимость с критического пути — самый дешёвый способ поднять доступность. Уведомление, которое можно отправить позже, уносят в очередь: сервис уведомлений теперь может лежать, а оформление заказа работает. Это не про надёжность самого уведомления, а про то, что оно больше не перемножается.
  • Резервирование, наоборот, перемножает вероятности отказа. Две копии, каждая с недоступностью 0,1 %, дают одновременный отказ с вероятностью 0,0001 % — при условии, что они независимы. Ровно поэтому копии разносят по разным зонам: две копии в одной зоне падают вместе, и никакой арифметики не получается.

И честная оговорка про сами цифры: доступность считается не «работает или нет», а по вашему определению успеха и на вашем окне — и от этого определения зависит результат. Что считать успешным запросом и как это записать, разбирает статья про цели уровня обслуживания (Java, Go, Node, Python).

Масштабируемость: сначала опиши нагрузку

Фраза «система X масштабируемая» сама по себе пустая. Осмысленный вопрос звучит иначе: «если нагрузка вырастет вот так — что мы будем делать?». Ответить на него «в общем» нельзя: рост в десять раз по чтениям и рост в десять раз по записям ломают систему в разных местах. Поэтому нагрузку сначала описывают числами — параметрами нагрузки: сколько запросов в секунду на чтение и на запись, каково соотношение чтений к записям, сколько данных в «горячем» наборе, сколько одновременных пользователей. Какой из параметров главный — зависит от конкретной системы.

Классический пример — лента Twitter, каким его разбирает Мартин Клеппман: новых публикаций — 4 600 в секунду, а вот запросов «покажи мою ленту» — 300 000 в секунду. Цифры относятся примерно к 2012 году и приводятся как учебный пример — свежих сервис, сменивший с тех пор и владельца, и имя, не публикует. Для разбора это не важно: интересна не точная величина, а соотношение. Устроить ленту можно двумя способами:

  1. Собирать при чтении. Твит — это просто одна вставка в общую таблицу. А лента строится запросом «найди всех, на кого я подписан, и собери их свежие твиты». Запись дешёвая, зато чтение дорогое.
  2. Собирать при записи. У каждого пользователя есть свой готовый «почтовый ящик» ленты. Когда кто-то публикует твит, его сразу раскладывают по ящикам всех подписчиков. Теперь чтение дешёвое (лента уже собрана), зато запись дорогая — один твит человека с миллионом подписчиков превращается в миллион вставок.

Twitter начинал с первого способа, не выдержал нагрузки на чтение и перешёл на второй: раз чтений в 65 раз больше, чем записей, выгоднее платить на записи. Но и это не финал: для знаменитостей с миллионами подписчиков раскладка по ящикам неподъёмна, поэтому их твиты подмешивают уже при чтении — получается гибрид двух подходов. Мораль: главный параметр нагрузки здесь — вовсе не «твитов в секунду», а то, как распределены подписчики по пользователям. Не описав нагрузку в числах, эту архитектуру нельзя ни выбрать, ни объяснить.

Общий принцип: универсального рецепта масштабирования не бывает. Система под 100 000 мелких запросов в секунду и система под 3 больших запроса в минуту — это разные архитектуры, даже если в байтах они прокачивают одинаково. Хорошая архитектура всегда стоит на предположениях о том, какие операции будут частыми, — то есть на параметрах нагрузки.

Опорные величины: что такое «много»

Весь дальнейший метод требует прикидок на салфетке, а прикидка невозможна без опорных чисел. Вот минимальный набор, который стоит помнить — это порядки величин, а не точные значения, и нужны они, чтобы отличить «это нормально» от «здесь проблема».

Что сколько стоит по времени. Разница между этими строками объясняет большинство решений в дизайне:

ОперацияПорядок времениВо сколько раз дороже памяти
Обращение в память процесса100 наносекунд1
Чтение с быстрого диска100 микросекундв 1 000 раз
Обращение к соседнему сервису в том же центре данных0,5–1 миллисекундав 5 000–10 000 раз
Простой запрос в базу по индексу1–5 миллисекундв 10 000–50 000 раз
Обращение в другой регион (через континент)50–150 миллисекундв миллион раз

Из этой таблицы следуют два вывода, которые пригодятся каждый день. Первый: сетевой вызов в цикле — почти всегда ошибка, потому что он в десять тысяч раз дороже обращения в память; сто вызовов по миллисекунде — это сто миллисекунд, то есть весь ваш бюджет ответа. Второй: кеш в памяти выигрывает у базы не в разы, а в тысячи раз, и именно поэтому он так помогает, — но по той же причине промах кеша стоит дорого, и доля попаданий важнее скорости самого кеша.

Что сколько держит по нагрузке. Здесь разброс больше, но порядки такие:

ЧтоПорядок величины
Обычный сервис на одной машине, простая логикаединицы тысяч запросов в секунду
Тот же сервис, если каждый запрос идёт в базусотни — единицы тысяч запросов в секунду
PostgreSQL на хорошей машине, простые запросы по индексудесятки тысяч запросов в секунду
PostgreSQL, запись с фиксациейединицы — десятки тысяч транзакций в секунду
Кеш в памяти по сетисотни тысяч операций в секунду
Kafka, один разделдесятки тысяч сообщений в секунду

Как этим пользоваться: услышали «нам нужно 500 запросов в секунду» — это одна-две обычные машины, и никакой особенной архитектуры не требуется. Услышали «50 тысяч записей в секунду в базу» — вот здесь начинается настоящий системный дизайн, потому что одна база этого не даст. Большинство задач, которые обсуждают как «высоконагруженные», при переводе в эти числа оказываются в первой строке таблицы, и это самый полезный результат прикидки.

Ёмкость и объёмы. Полезно помнить, что в сутках примерно 86 400 секунд (для прикидок — сто тысяч), а в месяце 2,6 миллиона. Отсюда: тысяча событий в секунду — это 86 миллионов в сутки и 2,6 миллиарда в месяц; если каждое весит килобайт, это 86 гигабайт в сутки и 2,6 терабайта в месяц. Такая цепочка «в секунду → в сутки → в месяц → в байтах» и есть основной приём прикидки, и он же обычно показывает, что хранить всё вечно нельзя.

Производительность: почему среднее время ответа врётспросят на собеседовании

Как измерить, «быстро» ли работает система? Для пакетной обработки — пропускной способностью (сколько записей в секунду обработали). Для онлайн-систем — временем ответа. И вот тут первая ловушка: время ответа — это не одно число, а разброс. Один и тот же запрос сегодня выполнится за 20 мс, а завтра за 800 — из-за переключения задач на сервере, потерянного пакета в сети или паузы сборщика мусора.

Считать среднее арифметическое — плохая идея: оно не говорит, скольким пользователям реально не повезло с медленным ответом. Смотреть надо на перцентили — они отвечают на вопрос «а сколько запросов уложилось в такое-то время»:

  • Медиана (p50) — половина запросов быстрее этого времени. Это «типичный» запрос.
  • p95, p99, p99.9 — это «хвост»: время, которое видят самые невезучие 5 %, 1 % и 0,1 % запросов. Последний иногда записывают как «p999», но читать это надо как «99,9-й перцентиль» — 999-го перцентиля не бывает.
p50 40 мс среднее 55 мс p95 180 мс p99 600 мс p99.9 1200 мс

Один сервис по перцентилям: среднее стоит рядом с медианой, а хвост p99.9 длиннее её в тридцать раз.

Хвост важнее, чем кажется, по двум причинам. Во-первых, самые медленные ответы часто достаются самым «тяжёлым» — а значит, самым ценным — пользователям: у них больше всего данных. Amazon формулирует требования к своим сервисам через p99.9, и оттуда же пошла известная оценка середины 2000-х: лишние 100 мс задержки стоят около 1 % продаж. Опубликованного измерения за ней нет — это цифра, названная вслух инженером компании, — но её с тех пор повторяли достаточно, чтобы считать порядок величины правдоподобным. Во-вторых, работает усиление хвоста: если одна страница собирается из обращений к 7 разным сервисам, и у каждого свой p99, то вероятность зацепить хотя бы один медленный ответ — уже не 1 %, а 1 − 0,99⁷ ≈ 6,8 %. Чем больше параллельных вызовов, тем чаще один медленный сервис тормозит целую страницу.

Ещё один источник хвоста — блокировка головы очереди. Серверу хватает пары медленных запросов, чтобы за ними в очереди застряли быстрые: сами они выполнились бы за миллисекунды, но ждали своей очереди секунду.

Из этого следует, где мерить: время ответа честнее считать на стороне клиента, а не сервера — иначе не видно, сколько запрос простоял в очереди.

На перцентилях строят и договорённости об уровне сервиса, и их три штуки, одна поверх другой. Сначала выбирают SLI — то, что реально измеряется: доля ответов без ошибок, p99 времени ответа. На SLI вешают SLO — внутреннюю цель вида «p99 меньше 1 секунды, доступность 99,9 %». И уже поверх SLO пишут SLA — соглашение с клиентом, за нарушение которого бывают санкции; в SLA цифры обычно мягче, чем во внутренней цели, чтобы был запас. Подробно эта тройка разобрана в статье про SLO и алерты (Java, Go, Node, Python). Как перцентили считаются технически (гистограммы, агрегация) — в статье про метрики (Java, Go, Node, Python).

Когда нагрузка описана числами и метрика выбрана, варианты ответа на рост известны: вертикальное масштабирование (взять машину помощнее) против горизонтального (взять много машин поменьше). На практике это прагматичная смесь: сервисы без состояния (stateless) размножаются легко, а базу данных держат на одном узле до последнего — пока стоимость более мощного железа или требования к доступности не заставят её распределять. Подробнее — в строительных блоках.

«Размножаются легко» — правда ровно до третьей копии, и стоит сразу сказать, во что упирается такая система следом. Порядок почти всегда один и тот же:

Пул соединений к базе. Каждая копия держит свой пул, скажем, по 20 соединений. Двадцать копий — это 400 соединений к базе, а у PostgreSQL по умолчанию предел около 100, и каждое соединение стоит памяти. Добавление копий здесь ухудшает ситуацию: база начинает отказывать в соединениях, и вы получаете лавину ошибок вместо роста производительности. Лечится внешним пулером соединений и уменьшением пула на копию, а не увеличением предела.

Единственный узел записи. Реплики решают проблему чтения и никак не помогают записи: все копии сервиса пишут в один и тот же узел. Значит, потолок по записи у вас один независимо от числа копий, и упереться в него можно на десяти копиях так же, как на сотне. Дальше — разделение данных по узлам, и это уже другая архитектура.

Общий ресурс, который вы не заметили. Внешний сервис с ограничением по числу запросов в минуту; лицензия на число соединений; блокировка в базе, за которой копии стоят в очереди; общий кеш, который стал горячей точкой; одна и та же строка, которую все обновляют. Признак этого класса проблем: копий больше, а суммарная пропускная способность не растёт или падает.

Состояние, которое вы считали отсутствующим. Кеш в памяти процесса (у каждой копии свой, и доля попаданий падает с ростом числа копий), задачи по расписанию (запустились на всех копиях разом), счётчики в памяти, сессии пользователя. Каждая такая мелочь превращает «без состояния» в «почти без состояния», и ломается это именно при росте.

Отсюда практическое правило: сначала находят, что станет узким местом после добавления копий, и только потом добавляют копии. Проверяется это дешёвым опытом из четырёх шагов выше — прогон на одной копии и на двух: если вторая копия не дала почти удвоения, дальше добавлять бессмысленно, и разговор идёт про общий ресурс.

Сопровождаемость: код читают дольше, чем пишут

Главные деньги софт съедает не на этапе написания, а потом — на сопровождении: правки, эксплуатация, подгонка под новые требования. Три вещи делают систему пригодной для долгой жизни:

  • Удобство эксплуатации. Хорошая система делает рутину дежурных простой: понятный мониторинг, ясная связь «сделал X → произойдёт Y», автоматизация, независимость от конкретной машины, разумные настройки по умолчанию с возможностью вмешаться руками.
  • Простота. Главный враг — лишняя сложность, то есть та, что порождена не самой задачей, а способом её решения. Признаки: слишком много состояний, модули цепляются друг за друга, повсюду костыли и особые случаи. Главное оружие против неё — абстракция: SQL прячет от вас, как данные лежат на диске и как разруливается одновременный доступ; язык высокого уровня прячет машинный код. Хорошая абстракция убирает сложность за понятный фасад и переиспользуется много раз.
  • Возможность развития. Требования обязательно поменяются — вопрос лишь в том, насколько дорого системе даётся каждое изменение. Простые и хорошо абстрагированные системы менять легко, запутанные — мучительно.

Где это применяется

Эти три слова — скелет любого разговора об архитектуре. Обсуждение дизайна, в котором не прозвучали параметры нагрузки, перцентили и поведение при сбоях, — это разговор про квадратики на схеме, а не про систему. Метод системного дизайна начинается ровно с этого: сначала числа и обязательства, потом схема.

Где спотыкаются начинающие:

  • Называют систему «масштабируемой» без параметров нагрузки. Масштабируемость — не свойство-галочка, а ответ на вопрос «что мы сделаем, когда вырастет вот этот показатель?».
  • Смотрят на среднее время ответа. Среднее прячет хвост, а хвост — это ваши самые активные пользователи. Начинайте с медианы и p99.
  • Путают сбой и отказ — и пытаются построить систему, где «ничего не ломается», вместо системы, которая спокойно переживает поломки.
  • Борются со всей сложностью сразу. Сложность самой задачи никуда не денется; убирать надо только лишнюю — ту, что добавил способ реализации.
Дополнительно: при первом чтении можно пропустить

Глубже: сколько машин: ёмкость и запасрасширенноеспросят на собеседовании

Числа выше отвечают на вопрос «сколько держит одна», а решение принимается в штуках. Расчёт делают в четыре шага, и он занимает пять минут.

1. Взять пик, а не среднее. Суточный трафик неровный: вечерний пик обычно в 2–3 раза выше среднего за сутки, а у сервисов с рассылками и распродажами — в десятки раз. Считают по пику; средним считают только деньги.

2. Разделить на то, что держит одна копия — из замера на нагрузочном прогоне, а не из веры. Получили, скажем, 6000 запросов в секунду в пике при 450 на копию — нужно 14 копий.

3. Заложить запас на отказ. Здесь два вопроса, и разные ответы дают разную цену. Если надо пережить отказ одной копии — добавляют одну (правило N+1). Если надо пережить отказ зоны (а это обычный случай для трёх зон) — нагрузка распределена по трём, и при потере одной оставшиеся две должны удержать всё: значит, в каждой зоне должно быть не 1/3 нужной мощности, а половина. Практически: 14 копий превращаются в 21 (по 7 в трёх зонах), и тогда потеря зоны оставляет 14 работающих.

4. Проверить, что осталась голова. Копия, загруженная в норме на 80 %, при любом всплеске оказывается на полке, где время ответа взлетает. Целевая загрузка в норме — 40–60 %, и это не расточительство, а то, что превращает всплеск в неприятность вместо аварии.

И самое важное ограничение всего расчёта: он верен только пока узкое место внутри копии. Если предел упирался в общий ресурс — базу, брокер, внешний сервис, — двадцать первая копия ничего не добавит, а только быстрее исчерпает этот общий ресурс. Проверяется прогоном на двух копиях: получили вдвое больше — масштабирование линейное; получили чуть больше — вы нашли общий ресурс, и разговор теперь про него.

Глубже: как отличить лишнюю сложность от неизбежнойрасширенное

Совет «убирать лишнюю сложность» бесполезен без признаков: своя сложность всегда кажется необходимой. Проверки, которые работают на настоящем коде.

Главный признак: сложность не выводится из требований. Возьмите конкретный механизм — три слоя абстракции, фабрика фабрик, событийная шина внутри одного процесса — и попробуйте назвать требование, из которого он следует. Не «на будущее», не «так правильнее», а требование. Не получилось назвать — сложность лишняя. Это тот же вопрос, который в методе задают про каждый квадратик на схеме.

Признаки в обычной работе, по убыванию надёжности:

  • Простое изменение требует правки в пяти местах. Добавить поле в форму — и оно проходит через DTO, маппер, сущность, событие, ещё один DTO. Число мест, которые надо тронуть ради одного изменения, — самая честная мера лишней сложности, и её можно просто посчитать.
  • Новый человек не может объяснить путь запроса через час чтения кода. Не «не знает деталей», а не может сказать, где вход и где выход.
  • Особых случаев больше, чем общих. Условия вида «а для этого типа клиента иначе» размазаны по коду в десяти местах, и никто не знает полного списка.
  • Абстракция с одной реализацией, которая живёт больше года. Интерфейс, заведённый «чтобы подменить потом», и подменять его никто не собирается: он не убрал сложность, а добавил ещё один уровень чтения.
  • Нельзя удалить кусок и увидеть, что сломалось. Если нельзя проверить необходимость части системы экспериментом, значит, границы размыты, и это тоже свойство лишней сложности.
  • В объяснении звучит «исторически». Это слово почти всегда помечает сложность, которая когда-то была нужна и осталась.

А вот признаки неизбежной сложности, за которую бороться не надо: она названа в требованиях (валютные правила, разные налоговые режимы, согласование с внешней системой), она сосредоточена, а не размазана (сложные правила лежат в одном месте и у них есть имя), и она проверяется тестами по требованиям, а не по реализации. Такую сложность не убирают — её изолируют и документируют.

И проверка на лишнюю сложность, которая делается до её появления: какое требование перестанет выполняться, если этого не делать? Если ответ «никакое, но потом может понадобиться», решение откладывают. Почти всегда «потом» либо не приходит, либо приходит в другой форме.

Коротко

  • Сбой — отклонение одного компонента, отказ — неработающая система; задача дизайна не в идеальных деталях, а в том, чтобы сбой не стал отказом, и чаще всего роняют систему люди, а не диски.
  • Учения с намеренными сбоями проводят по правилам: сначала наблюдаемость, потом гипотеза с числами, минимальный радиус, рабочее время и возможность отменить за минуту.
  • Девятки переводят в минуты: три девятки — час в год, четыре — 53 минуты, то есть без участия человека; доступность перемножается по цепочке, поэтому убрать зависимость с критического пути дешевле, чем усиливать её.
  • Опорные числа: обращение в память — 100 наносекунд, к соседнему сервису — миллисекунда, запрос в базу — единицы миллисекунд, другой регион — сотня; обычная машина держит тысячи запросов в секунду, PostgreSQL — десятки тысяч по индексу.
  • Ёмкость считают по пику, делят на замеренную пропускную способность копии, добавляют запас на отказ зоны (в каждой зоне половина нужного) и держат загрузку 40–60 %.
  • Масштабируемость — не свойство, а ответ на вопрос «что делаем, когда вырастет вот этот параметр»; параметры нагрузки описывают числами, и главный не всегда очевиден.
  • Копии без состояния упираются в пул соединений, единственный узел записи, общий внешний ресурс и незамеченное состояние; сначала находят будущее узкое место, потом добавляют копии.
  • Время ответа — разброс, а не число: смотрят медиану и хвост, считают на стороне клиента, помнят про усиление хвоста при параллельных вызовах и блокировку головы очереди.
  • Лишняя сложность не выводится из требований: её признаки — правка в пяти местах на одно изменение, особых случаев больше общих, абстракция с одной реализацией, «исторически»; неизбежную сложность изолируют, а не убирают.

Что почитать дальше