Архитектура и системный дизайн

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

Вячеслав Федоров

Лекция 14

Разработка и применение ПО

в физических исследованиях

SLO

Содержание

От требований к работающей системе

01

Требования

02

Масштабирование

03

Отказоустойчивость

04

Данные

05

Сопровождение

06

Проектирование

Цель лекции

Записывать требования числами и выбирать устройство системы, которое их выдерживает

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

Четыре свойства

системы

01

Масштабируемость

задержка и доля ошибок сохраняются при росте нагрузки за счёт новых ресурсов

02

Отказоустойчивость

отказ машины, процесса или зависимости не останавливает обслуживание

03

Наблюдаемость

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

04

Сопровождаемость

изменение, выкатка и откат выполняются быстро и предсказуемо

Для физика

Сквозной пример — архив измерений установки: оцифровщики пишут данные, скрипты сотрудников читают запуски и графики по HTTP.

Пояснение

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

Требования

Функциональные и нефункциональные требования, нагрузка, перцентили, SLI, SLO и SLA, бюджет ошибок

01

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Требования

до проектирования

ТребованиеПример для архива измерений
функциональноевыдача запуска по номеру, график канала за интервал
нагрузказапись 4 МБ/с непрерывно, чтение — 50 запросов/с в пике
задержкаp99 выдачи запуска меньше 300 мс
доступность99,9 % успешных ответов за 30 дней
сохранностьни одного потерянного измерения
стоимостьтри машины и диски на год записи

Пояснение

Функциональные требования говорят, что система делает; нефункциональные — насколько хорошо: числами нагрузки, задержки, доступности, объёма и цены.

Важно

Требование без числа непроверяемо: «быстро» становится перцентилем задержки, «надёжно» — долей успешных ответов.

Нагрузка

и задержка

lat = sorted(latencies_ms)
q = statistics.quantiles(lat, n=100,
                         method='inclusive')
p50, p95, p99 = q[49], q[94], q[98]
mean = statistics.fmean(lat)  # смещено хвостом

Для физика

Страница архива из 20 запросов задевает медленный 1 % с вероятностью 1 − 0,99^20 ≈ 18 %.

01

rps

запросов в секунду: среднее за сутки и пик

02

p50, p95, p99

задержка, которую не превышают 50, 95 и 99 % запросов

03

Среднее

сдвигается редкими медленными ответами и скрывает хвост

04

Закон Литтла

L = λW: 100 запросов/с по 30 мс — в работе в среднем 3 запроса

Демонстрация:

перцентили задержки

01

Сервис svc.py: 4 запроса одновременно, каждый 25-й — 200 мс

02

uvicorn на 127.0.0.1:8000

03

aload.py на httpx: 50–200 запросов/с по расписанию, по 4 с

04

Среднее, p50, p95, p99 и максимум

Что наблюдается

До 140 запросов/с: p50 22–23 мс, p99 202–208 мс. При 160 — p50 265 мс, при 200 — 792 мс: очередь растёт весь тест.

root@lab — запись со стенда

SLI, SLO

и SLA

ТерминЧто этоПример
SLIизмеряемый показательдоля ответов не 5xx; доля быстрее 300 мс
SLOцель для SLI за окно99,9 % успешных за 30 дней
SLAдоговор с последствияминиже 99,5 % — возврат части оплаты
бюджет ошибок1 − SLO0,1 % запросов за 30 дней

Пояснение

Доступность считают по запросам — доля успешных ответов за скользящее окно, — а не по времени работы машины.

Для физика

Архив: 99,9 % чтений успешны, 99 % выдач запуска быстрее 300 мс; измерение попадает в архив не позже 10 с после снятия.

Девятки

и бюджет ошибок

budget = 1 - slo / 100          # доля неуспешных
downtime = budget * 30 * 86400  # с за 30 дней
 
chain = math.prod(a)            # последовательно
copies = 1 - math.prod(1 - x for x in a)

Важно

Две копии по 99 % дают 99,99 % только при независимых отказах: общий диск, сеть или выкатка делают их одной.

01

Бюджет

1 − SLO: при 99,9 % — 0,1 % запросов или 43 мин за 30 дней

02

Расход

выкатки, эксперименты и аварии тратят один бюджет

03

Последовательно

A = A1·A2·A3: цепочка хуже каждого звена

04

Параллельно

1 − (1 − A1)(1 − A2): отказ, только если отказали все

Демонстрация:

девятки

01

Простой для пяти уровней SLO: год, 30 дней, неделя, сутки

02

Цепочка из трёх сервисов по 99,9 %

03

Две независимые копии по 99 %

04

Бюджет ошибок при 100 000 запросов/с

Что наблюдается

99,99 % — 52 мин 34 с в год и 4 мин 19 с за 30 дней; цепочка — 99,7 %; две копии по 99 % — 99,99 %.

root@lab — запись со стенда

Масштабирование

Вертикальное и горизонтальное, сервисы без состояния, балансировка нагрузки, консистентное хеширование

02

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Вертикальное

и горизонтальное

ВертикальноеГоризонтальное
ресурсCPU и память машинычисло экземпляров
доработкане нужнасервис без состояния
пределсамая большая машинаобщие зависимости, бюджет
отказ машиныостановка всего сервисапотеря доли мощности
изменениеобычно с перезапускомпо одному, без простоя

Для физика

Процесс обычной сборки CPython выполняет байт-код в одном потоке за раз: машину с 32 ядрами загружают 32 процесса.

docker update --cpus 2 svc
uvicorn app:app --workers 4

Сервисы

без состояния

# состояние в процессе: другой экземпляр не видит
results = {}
results[job_id] = fit(data)
 
# состояние снаружи: видят все экземпляры
db.execute('INSERT INTO results VALUES (?, ?)',
           (job_id, fit(data)))

Для физика

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

01

Без состояния

любой экземпляр обслуживает любой запрос: сессии, файлы, задания — снаружи

02

С состоянием

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

03

Балансировщик

хранит не данные приложения, а соединения, счётчики и результаты проверок

Демонстрация:

процессы и ядра

01

Сервис cpu.py: около 10 мс вычислений на запрос

02

uvicorn с одним процессом: 300 запросов по 8 одновременно

03

uvicorn --workers 2 на двух ядрах стенда

04

Новое соединение на каждый запрос (--fresh)

Что наблюдается

Один процесс — 62 запроса/с, p50 118 мс; два — 126 запросов/с, p50 57–61 мс, запросы поделены 142 и 158.

root@lab — запись со стенда

Балансировка

нагрузки

АлгоритмКак выбираетКогда подходит
round robinпо очереди, с весамиодинаковые короткие запросы
least_connменьше активных соединенийразная длительность
hashпо ключу: пользователь, URLпривязка, локальный кеш
random twoлучший из двух случайныхмного балансировщиков

Пояснение

L4 распределяет соединения TCP, не читая HTTP; L7 разбирает запрос и выбирает экземпляр по пути, заголовку или cookie.

upstream app {
    zone app 64k;  # общее состояние
    least_conn;    # или hash $arg_user;
    server 127.0.0.1:8001 weight=2;
    server 127.0.0.1:8002;
    server 127.0.0.1:8003;
}
location / { proxy_pass http://app; }

Демонстрация:

балансировка в nginx

01

Три экземпляра на 8001–8003, задержка app2 — 200 мс

02

nginx:alpine в контейнере с сетью хоста, rr.conf

03

300 запросов по 12 одновременно: round robin

04

least_conn и hash $arg_user для восьми пользователей

Что наблюдается

Round robin: по 100 за 2,0 с; least_conn: 144, 12 и 144 за 0,6 с; hash: каждый пользователь у одного экземпляра.

root@lab — запись со стенда

Консистентное

хеширование

s0

s1

s2

s3

s4

ключ принадлежит первому узлу по ходу часовой стрелки

новый s4 забирает ключи только у s1

def h(s):  # одинаков в любом процессе
    d = hashlib.md5(s.encode()).digest()
    return int.from_bytes(d[:8], 'big')
 
nodes[h(key) % len(nodes)]     # остаток
max(nodes, key=lambda n: h(f'{n}:{key}'))

Пояснение

Остаток от деления при переходе с 4 на 5 узлов переносит 4/5 ключей, кольцо и рандеву — около 1/5.

Важно

Встроенный hash() строк меняется от запуска к запуску интерпретатора: для распределения ключей непригоден.

Демонстрация:

остаток и кольцо

01

100 000 ключей вида run-000123 на четырёх шардах

02

Остаток от деления, кольцо со 100 точками на узел, рандеву

03

Доля ключей, сменивших шард при добавлении пятого

04

Встроенный hash() одной строки в двух процессах

Что наблюдается

Остаток переносит 80,0 % ключей, кольцо — 20,0 %, рандеву — 20,1 %; кольцо неравномернее: от 23 749 до 26 014 ключей на шард.

root@lab — запись со стенда

Отказоустойчивость

Резервирование и кворум, проверки работоспособности, тайм-ауты и дедлайн, повторы, идемпотентность, автомат защиты

03

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Резервирование

и кворум

УзловКворумПереживает отказов
110
220
321
431
532

Для физика

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

01

N + 1

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

02

Домены отказа

копии на разных машинах, стойках, линиях питания, площадках

03

Кворум

решение принимает большинство: целая часть N/2 плюс 1

04

Нечётное N

четвёртый узел не добавляет устойчивости к отказам

Проверка

работоспособности

upstream app {
    server 127.0.0.1:8001 max_fails=1 fail_timeout=10s;
    server 127.0.0.1:8002 max_fails=1 fail_timeout=10s;
}
location / {
    proxy_pass http://app;
    proxy_read_timeout 1s;
    proxy_next_upstream error timeout;
}

Важно

/health только о процессе пропустит экземпляр без базы; /health обо всех зависимостях снимет все экземпляры из-за одной.

01

Активная

опрос /health по расписанию: HAProxy, Kubernetes; в открытом nginx нет

02

Пассивная

по настоящим запросам: max_fails неудач за fail_timeout

03

Повтор на соседе

GET — да; уже отправленный POST — нет

04

Точка входа

несколько адресов в DNS, плавающий адрес VRRP, anycast

Демонстрация:

отказ экземпляра

01

hc.conf: max_fails=1, fail_timeout=10s, ожидание ответа 1 с

02

Три экземпляра за nginx, остановка app2 сигналом STOP

03

Три POST, шесть GET и через 10 с ещё шесть GET

04

Журнал nginx: адреса и коды попыток

Что наблюдается

POST на app2 — 504 через 1,0 с; затем GET идут мимо app2; через 10 с GET пробует app2 и повторяется на app1: st=504, 200.

root@lab — запись со стенда

Тайм-ауты

и дедлайн

nginx

gateway

articles

база

10 с

30 с

60 с

клиент получает 504 через 10 с, а gateway и articles ждут до 30 и 60 с — ответ уже никому не нужен

left = deadline - time.monotonic()   # остаток
if left <= 0:
    raise TimeoutError('бюджет исчерпан')
r = client.get(url, timeout=left,
    headers={'X-Deadline-Ms': str(int(1000 * left))})

01

Убывание

тайм-аут вызываемого меньше тайм-аута вызывающего

02

Значение

по распределению задержки: p99,9 — 0,1 % ложных отмен

03

Дедлайн

остаток бюджета передаётся дальше; исчерпан — работа прекращается

Демонстрация:

тайм-ауты в цепочке

01

deadline.py: client → gateway → articles в одном процессе

02

Ожидание клиента — 1 с, gateway — 6 с, работа articles — 3 с

03

Без дедлайна и с остатком бюджета в X-Deadline-Ms

Что наблюдается

Клиент в обоих случаях получает отказ через 1,0 с; без дедлайна articles и gateway работают до 3,1 с, с дедлайном останавливаются через 1,0 с.

root@lab — запись со стенда

Повторы

с бюджетом

for attempt in range(4):
    try:
        return call(timeout=0.3)
    except TimeoutError:
        cap = min(1.0, 0.05 * 2 ** attempt)
        time.sleep(random.uniform(0, cap))
raise RuntimeError('попытки исчерпаны')

Важно

2 попытки на каждом из трёх уровней — до 8 запросов к последнему сервису; 3 попытки на пяти уровнях — 243.

01

Одна точка

повторяет один уровень цепочки, остальные — нет

02

Бюджет

повторов не больше 10 % запросов, попыток на запрос не больше трёх

03

Пауза

растёт вдвое и выбирается случайно: клиенты расходятся во времени

04

Что повторять

тайм-аут, обрыв, 429, 502–504 — и только идемпотентное

Идемпотентность

и ключ операции

ОперацияБезопасность повтора
GET, HEAD — чтениеда
PUT — замена целикомда
DELETE — удалениеда
POST — создание, списаниенет, без ключа
«выставить ток 10 А»да
«увеличить ток на 1 А»нет

key = str(uuid.uuid4())   # один на операцию
for attempt in range(4):
    try:
        return client.post(url, json=payment,
            headers={'Idempotency-Key': key})
    except httpx.TransportError:
        time.sleep(random.uniform(0, 0.1 * 2**attempt))
 
# сервер: известный ключ — прежний ответ
# без повторного списания

Для физика

Программа управления повторяет уставку «10 А» с проверкой прочитанного значения, а не приращение «+1 А».

Демонстрация:

повтор и ключ

01

backoff.py: удвоение предела паузы от 0,1 до 2 с

02

pay.py: задержка каждого третьего ответа на 1 с

03

Тайм-аут 0,3 с, до 4 попыток: без ключа и с ключом

Что наблюдается

Паузы трёх клиентов различны; без ключа 10 платежей дали 14 записей и списание 1400, с ключом — 10 записей и 1000.

root@lab — запись со стенда

Автомат защиты

и деградация

Закрыт

запросы проходят,
неудачи считаются

Открыт

сразу заглушка,
сервис не вызывается

Полуоткрыт

один пробный
настоящий запрос

3 неудачи подряд

пауза 1 с

неудача

успех

Пояснение

Пока автомат открыт, вызывающий отвечает заглушкой: последняя известная версия, скрытый блок страницы, 503 с Retry-After.

Для физика

Страница запуска с таблицей и последним сохранённым графиком лучше страницы с ошибкой 504.

Демонстрация:

повторы и автомат

01

chain.py: client → nginx → gateway → articles, ответ articles — 503

02

Попытки по уровням: 2·2·2, 3·3·3 и повтор только в gateway

03

breaker.py: 10 запросов/с, отказ articles на 2,5 с

04

Автомат: после 3 неудач подряд — пауза 1 с и пробный запрос

Что наблюдается

До articles дошло 8, 27 и 2 запроса; автомат пропустил к нему 13 запросов из 40 и закрылся по пробному запросу в 3,2 с.

root@lab — запись со стенда

Данные

Репликация, шардирование, CAP и PACELC, кеширование и инвалидация, очереди и обратное давление

04

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Репликация

и отставание

Главная

запись
и чтение

Реплика 1

только чтение

Реплика 2

только чтение

журнал WAL

Для физика

Метаданные запусков: запись — в главную, поиск и графики — с реплик.

01

Асинхронная

запись подтверждается сразу; реплика отстаёт, обычно меньше секунды

02

Синхронная

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

03

Свои записи

после записи читать с главной или дождаться реплики

04

Переключение

реплику повышают до главной; асинхронная теряет хвост

Шардирование

по ключу

КлючПлюсМинус
времяинтервал — с одного шардався запись — в последний
хеш номера запусканагрузка равномернаинтервал — со всех шардов
канал, затем времяканал за интервал — вместепопулярный канал перегружен

Важно

Смена ключа — перенос всех данных: ключ выбирают по главным запросам заранее.

01

Секционирование

сначала внутри одной базы: PARTITION BY RANGE

02

Шарды

части на разных машинах, когда одной не хватает

03

Перебалансировка

консистентный хеш переносит около 1/(N+1) ключей

04

Между шардами

соединения и транзакции дороже или невозможны

CAP

и

PACELC

01

CAP

при разрыве сети (P) — отказ ради согласованности (C) или ответ, возможно устаревший (A)

02

PACELC

иначе (E) — выбор между задержкой (L) и согласованностью (C)

03

Кворум

N копий, запись на W, чтение с R: при R + W > N чтение видит последнюю запись

N, W, R = 3, 2, 2
assert R + W > N   # множества пересекаются
# запись: ждать 2 подтверждения из 3
# чтение: опросить 2 копии, взять новейшую

Для физика

График в архиве допускает отставание в секунду; номер нового запуска выдаёт одна главная база.

Кеширование

и инвалидация

def get_run(run_id):
    key = f'run:{run_id}'
    if (v := cache.get(key)) is not None:
        return v                   # попадание
    v = db.fetch_run(run_id)       # промах
    cache.set(key, v, ex=60)       # срок 60 с
    return v
 
def update_run(run_id, data):
    db.update_run(run_id, data)
    cache.delete(f'run:{run_id}')  # сброс

01

Где

браузер, CDN, nginx, память процесса, Redis

02

Срок жизни

ограничивает устаревание, если сброс не дошёл

03

Лавина промахов

ключ истёк — все идут в базу: нужен один загрузчик

04

Холодный старт

база выдерживает пустой кеш, или кеш прогревают

Демонстрация:

кеш перед базой

01

Метод get: попадание, промах, один загрузчик под замком

02

lru_cache перед «базой» с чтением за 200 мс

03

Запись 12 А в базу: кеш без сброса и после сброса

04

50 одновременных запросов после истечения срока жизни

Что наблюдается

Чтение — 200,1 мс, повтор — 0,0 мс; без сброса кеш отдаёт 10 А вместо 12; без защиты 50 обращений к базе, с одним загрузчиком — одно.

root@lab — запись со стенда

Очереди

и обратное давление

Источник

100 измерений/с

Очередь

не больше 10

Обработчики

2 по 30 мс: около 66/с

данные

давление

полна: ждать или отклонить

01

Развязка

источник и обработчики работают в своём темпе, пик сглаживается

02

Предел

очередь без предела накапливает перегрузку до исчерпания памяти

03

Давление или сброс

полная очередь замедляет источник или отклоняет: 429, 503

04

Хотя бы один раз

брокер доставляет повторно: обработчик идемпотентен

Для физика

Измерения сбрасывать нельзя: буфер на диске и давление на оцифровщик. Запросы просмотра при перегрузке можно отклонить.

Демонстрация:

очередь с пределом

01

Источник: 100 измерений в секунду в течение 3 с

02

Два обработчика по 30 мс: ёмкость около 66 в секунду

03

Очередь без предела, maxsize=10 с ожиданием и со сбросом

Что наблюдается

Без предела очередь растёт до 108, ожидание 1,74 с; с давлением выдача растянулась до 4,6 с; со сбросом потеряно 98 измерений.

root@lab — запись со стенда

Сопровождение

Журналы, метрики и трассировки, монолит и микросервисы, сетка сервисов, защита от перегрузки

05

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Журналы, метрики

и трассировки

СигналВопросИнструменты
журналычто случилось с этим запросом?logging, Loki, Elasticsearch
метрикисколько, как быстро, сколько ошибок?Prometheus, Grafana
трассировкигде запрос провёл время?OpenTelemetry, Jaeger

proxy_set_header X-Request-ID $request_id;
log_format rid '$request_id $status $request_time';
access_log /var/log/nginx/access.log rid;

01

Четыре сигнала

задержка, трафик, ошибки, насыщение

02

Оповещение

о симптоме — расходе бюджета ошибок, а не о загрузке CPU

03

Расход бюджета

2 % за час — вызов дежурного; 10 % за трое суток — заявка

Монолит

и микросервисы

МонолитМикросервисы
выкаткацеликомпо сервису
масштабированиецеликомпо сервису
отказ частиобщий процессчастичный, через сеть
данныеодна транзакциясогласование между базами
командыоднанесколько независимых
цена стартанизкаясеть, наблюдаемость, выкатка

Пояснение

Модульный монолит — чёткие границы модулей внутри одной программы; сервис выделяют, когда граница мешает выкатке или масштабированию.

Для физика

Группа из пяти человек с одной установкой: монолит с модулями сбора, хранения и веб-интерфейса.

Service mesh:

сетка сервисов

Control plane

адреса, правила, сертификаты

сервис A

посредник

посредник

сервис B

data plane: посредник рядом с каждым экземпляром

01

Data plane

балансировка, повторы, тайм-ауты, mTLS — вне кода сервиса

02

Control plane

раздаёт посредникам конфигурацию и сертификаты

03

Цена

два посредника на каждый вызов, CPU и память на каждом узле

Пояснение

Оправдана при десятках сервисов и нескольких командах; трём сервисам хватает библиотеки и nginx.

Защита

от перегрузки

limit_req_zone $binary_remote_addr
               zone=perip:10m rate=5r/s;
location / {
    limit_req zone=perip burst=10 nodelay;
    limit_req_status 429;
    proxy_pass http://app;
}

Пояснение

Ведро с темпом r и запасом b: запрос сверх темпа и запаса отклоняется кодом 429.

01

Ограничение частоты

по адресу или ключу API, до дорогой обработки

02

429 и Retry-After

клиент понимает, что ждать, а не что сервер отказал

03

Сброс нагрузки

отклонить часть сразу лучше, чем ответить всем медленно

04

Объёмные атаки

отсекают до сервиса: провайдер, CDN, anycast

Демонстрация:

ограничение частоты

01

rl.conf: 5 запросов/с с адреса, запас 10, код 429

02

Три экземпляра за nginx в контейнере

03

Залп из 30 одновременных запросов

04

Поток 20 запросов/с в течение 4 с

Что наблюдается

Из залпа прошло 11, 19 получили 429; в потоке — 15 в первую секунду и по 5 в каждую следующую, как задано темпом.

root@lab — запись со стенда

Сопровождаемость

и простота

01

Простота

компонент добавляют, когда без него измеримо хуже

02

Знакомые технологии

новую сначала проверяют под нагрузкой на тестовом контуре

03

Выкатка и откат

постепенно, с оповещением, откат одной командой

04

Изоляция контуров

тест не пишет в рабочую базу; группы — на своих экземплярах

Для физика

Новая версия программы сбора — сначала на стенде с моделью установки, затем в смену с готовым откатом.

Важно

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

Проектирование

Система сбора данных установки: требования, оценки, схема, отказы и реакции

06

Требования

Масштабирование

Отказоустойчивость

Данные

Сопровождение

Проектирование

Задача:

система сбора данных

01

Функции

приём с оцифровщиков, архив, выборка канала за интервал, графики

02

Поток

200 каналов × 10 кГц × 2 байта — непрерывно

03

Сохранность

ни одного потерянного измерения при отказе одной машины

04

Чтение

20 сотрудников и их скрипты: до 50 запросов/с, p99 выборки меньше 1 с

rate = 200 * 10_000 * 2     # байт/с
day = rate * 86_400 / 1e12  # ТБ в сутки
year = day * 365            # ТБ в год
print(rate / 1e6, round(day, 2), round(year))
# 4.0 0.35 126

Пояснение

Две копии архива — 252 ТБ в год; сжатие и прореживание старых данных — тоже требования.

Схема

системы

Оцифровщики

200 каналов

Приёмники ×2

буфер на диске

Очередь

брокер, 3 узла

Писатели ×2

файлы по часам

Метаданные

PostgreSQL
и реплика

Архив

HDF5, 2 копии

Сотрудники

скрипты, браузер

nginx

лимит и
балансировка

API ×2

FastAPI

Кеш графиков

Redis

4 МБ/с

50 запросов/с

Отказы

и реакции

ОтказРеакция системыТребование
приёмникприём вторым приёмником, буфер в оцифровщикесохранность
узел очередиработа кворума из двух оставшихся узловсохранность
писательожидание данных в очереди, второй писательсохранность
заполнение дискаоповещение за 7 суток по прогнозусохранность
главная базаповышение реплики, пауза регистрации файловдоступность
экземпляр APIисключение в nginx, повтор GET на соседедоступность
наплыв запросовответ 429 от limit_req, графики из кешазадержка

Для физика

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

Итоги

лекции

01

Требования записываются числами: запросы в секунду, перцентиль задержки, SLO и бюджет ошибок.

02

Горизонтальное масштабирование требует сервисов без состояния и выбора алгоритма балансировки.

03

Отказ переживают резервирование, проверки работоспособности, тайм-ауты с дедлайном и повторы с бюджетом.

04

Повторяют только идемпотентное; автомат защиты и заглушка дают зависимости восстановиться.

05

Реплики, шарды, кеш и очередь решают задачи данных, и у каждого — своя цена согласованности.

Упражнения

и задание

01

Бюджет ошибок

SLO 99,5 % своего сервиса: простой и неуспешные запросы за 30 дней

02

Нагрузочный тест

aload.py против своего сервиса: ёмкость и рост задержки за ней

03

Отказ экземпляра

три экземпляра за nginx: weight, least_conn, kill -STOP одного

04

Проект архива

оценки, схема и таблица отказов для данных своей установки

Пояснение

Итоговый проект: путь решения в документе первого этапа удобно описать требованиями с числами, оценками и схемой.

Для физика

Компонент добавляется, когда расчёт или замер показывает, что без него хуже.

Источники

1

Книга курса: главы «Системный дизайн», «От скрипта к приложению», «Компьютерные сети», «Веб-технологии», «Базы данных», «Работа с базами данных», «Docker», «Жизненный цикл ПО»

phys-dev.github.io/soft-dev-book

2

Лекция «Архитектура и системный дизайн», КИТ 2024, Young&&Yandex; В. Агапов

youtube.com/watch?v=3-6oGNIHRTI

3

Beyer B. et al. Site Reliability Engineering. – O'Reilly, 2016; The Site Reliability Workbook. – 2018

sre.google/books

4

Kleppmann M., Riccomini C. Designing Data-Intensive Applications. – 2nd ed. – O'Reilly, 2026 (рус. пер. 1-го изд.: Высоконагруженные приложения. – Питер, 2018)

dataintensive.net

5

nginx: модули ngx_http_upstream_module и ngx_http_limit_req_module

nginx.org/en/docs/http/ngx_http_upstream_module.html

6

Brooker M. Timeouts, retries, and backoff with jitter. – Amazon Builders' Library, 2019

builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/timeouts-retries-and-backoff-with-jitter

7

Fowler M. CircuitBreaker. – 2014

martinfowler.com/bliki/CircuitBreaker.html

Спасибо за внимание

Вопросы

phys-dev.github.io/soft-dev-book

99,9