Масштабируемость
Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems
Главная книга для тех, кто хочет научиться создавать крупные, быстрые и надежные ИТ-системы вроде Telegram, Netflix или крупных интернет-магазинов.
Что такое масштабируемость
Масштабируемость — способность системы справляться с возросшей нагрузкой. Это не галочка «есть или нет», а разговор о том, что мы будем делать, когда система перестанет справляться.
Что произойдёт с системой, если такой-то параметр нагрузки вырастет в десять раз? Сначала называем параметр, потом обсуждаем последствия.
Сколько ресурсов придётся добавить, чтобы производительность осталась прежней? Ответ «докупим в десять раз больше серверов» тоже считается ответом — просто дорогим.
Система продолжит работать нормально — или начнёт тормозить и падать? И во сколько нам обойдётся ответ «нормально»? Система может отлично держать миллион мелких запросов и падать на десятке тяжёлых, каждый из которых перемалывает гигабайты. Поэтому «масштабируемая» без указания нагрузки — пустое слово.
Краткая идея автора
Клеппман не даёт рецепта «сделай так — и будет масштабируемо». Он даёт порядок рассуждения, и весь раздел построен по нему.
-
01
Система перестаёт справляться
Пользователей стало больше, данных стало больше, запросов стало больше. Система, которая вчера работала нормально, начинает тормозить. Рост — это не только рост трафика: расти могут объём данных и сложность связей между ними.
-
02
Описать нагрузку
Нагрузку нужно описать числами — параметрами нагрузки: запросы в секунду, соотношение чтений и записей, число активных пользователей, доля попаданий в кэш. Пока нагрузка не описана, разговор о масштабировании беспредметен.
-
03
Описать производительность
Производительность — это насколько хорошо система справляется с этой работой. В пакетной обработке смотрят на пропускную способность, в онлайн-системах — на время отклика, причём не среднее, а по процентилям.
-
04
Выбрать стратегию и архитектуру
И только теперь выбираем: расти вверх (машина мощнее) или вширь (машин больше), что делать с состоянием, где кэшировать и что можно потерять. Архитектура вырастает из нагрузки, а не наоборот.
Параметры нагрузки и то, как их выбирают. Разбираем на примере Twitter — там разница между «правильным» и «неправильным» параметром видна лучше всего.
Пропускная способность и время отклика, почему среднее обманывает и зачем нужны процентили — вплоть до хвоста в 0,1 % запросов.
Вертикально или горизонтально, что делать с состоянием и почему универсальной масштабируемой архитектуры не существует.
Шаг первый: описать нагрузку
Число, которое описывает, сколько работы система должна выполнять. Обычно их несколько.
- просмотров в секунду
- загрузок видео
- объём переданных данных
- активных пользователей
- запросов в секунду
- соотношение чтений и записей
- размер запроса
- объём хранимых данных
- активных пользователей
- сообщений в секунду
- сколько адресатов у сообщения
![]()
Сначала описываем нагрузку — и только потом смотрим, как система при ней себя ведёт. Выбрать не тот параметр — значит промахнуться мимо реальной проблемы.
Пример из книги: Twitter
Числа Twitter из книги. Кажется, что главная нагрузка здесь — публикация твитов. На самом деле нет.
12 000 записей в секунду современная система переварит. Проблема в другом: один твит нужно доставить всем подписчикам автора — операция размножается на выходе. Это разветвление и называют fan-out.
Два способа собрать ленту
Одну и ту же ленту можно построить двумя способами, и вся разница — в том, когда выполняется работа: в момент чтения или в момент записи.
Твит просто сохраняется. Когда пользователь открывает ленту, система находит всех, на кого он подписан, берёт их свежие твиты и сливает в один список по времени.
В момент публикации система смотрит, кто подписан на автора, и кладёт твит в персональную ленту каждого подписчика. Открытие ленты — чтение готового списка.
Выберите действие — покажу, кто за него платит в каждом подходе.
Почему выгоднее платить при записи
Потому что у Twitter записей несоизмеримо меньше, чем чтений — и это свойство нагрузки выгодно использовать.
Много. Но взамен исчезает работа на чтении: 300 000 запросов ленты в секунду превращаются в 300 000 выборок готового списка. Один раз посчитать при записи дешевле, чем пересчитывать на каждом из чтений.
подход 02 из предыдущего слайдаПеренести работу с чтения на запись. Он работает ровно потому, что таково свойство этой нагрузки: чтений в десятки раз больше, чем записей. Поменяйте соотношение — и выгодным станет обратное.
приём общего назначения, а не рецепт для соцсетейВот зачем нужен был шаг «описать нагрузку»: решение принимает не архитектурный вкус, а соотношение чисел — 4 600 записей против 300 000 чтений.
Пока не появляется знаменитость
Среднее — 75 подписчиков. У аккаунтов из верхнего ряда их сотни миллионов, и один твит такого автора превращается в десятки миллионов записей, которые Twitter хотел доставлять примерно за пять секунд.
Среднее число подписчиков прячет крайние случаи. Архитектуру определяет распределение параметра нагрузки, а не его среднее — поэтому в итоге в Twitter живут оба подхода сразу.
Шаг второй: описать производительность
Нагрузка — это сколько работы система должна сделать. Производительность — насколько хорошо она с этой работой справляется. Отсюда те самые два вопроса: что будет, если нагрузка вырастет, а ресурсы останутся прежними, и сколько ресурсов нужно, чтобы ничего не изменилось.
Ночной пересчёт отчётов, Hadoop. Пользователь не ждёт у экрана, поэтому важна пропускная способность: сколько записей система обрабатывает в секунду и за сколько отработает всё задание целиком.
throughputМагазин, соцсеть, банковское приложение, публичный API. Здесь важно время отклика: сколько прошло от отправки запроса до получения ответа — то есть сколько ждал живой человек.
response timeИз чего складывается время отклика
Response time — всё это вместе, то есть ровно то, что видит клиент.
Поэтому время отклика может быть огромным, даже когда обработка мгновенная — запрос просто стоял в очереди. Мерить надо у клиента, а не внутри сервера.
Одно среднее число ничего не описывает
99 запросов по 10 мс и один на 10 секунд дают «в среднем 110 мс» — число, которого не видел ни один пользователь. Поэтому время отклика описывают распределением и процентилями.
Хвост — не мелочь: при 10 млн запросов 1 % — это 100 000 медленных ответов, и достаются они чаще самым активным клиентам, у которых больше всего данных. В Amazon считали, что лишние 100 мс времени отклика — это около −1 % продаж.
Откуда берётся хвост
Медленный запрос редко медленный сам по себе — чаще он просто стоял в очереди за чужим.
A, C и D — по 40 мс каждый. Но обрабатываются они по очереди, и всё, что встало за медленным B, ждёт вместе с ним. Один тяжёлый запрос делает медленными несколько лёгких — и все они попадают в хвост.
Обычный тестовый клиент устроен так: отправил запрос → дождался ответа → отправил следующий. Выглядит логично, но пока система тормозит, такой клиент перестаёт слать нагрузку — очередь искусственно укорачивается.
- в системе одновременно оказывается меньше запросов, чем в реальности
- очередей почти нет — значит, нет и хвоста
- система выглядит быстрее, чем есть на самом деле
Генератор нагрузки должен слать запросы независимо от того, ответили ли на предыдущие.
Отсюда правило: время отклика измеряют на стороне клиента. Внутреннее время работы сервера ничего не знает про очередь, которая выстроилась перед ним.
В распределённых системах хвост усиливается
Одна страница магазина собирается из ответов нескольких сервисов — пользователи, товары, рекомендации, реклама, оплата, — и пользователь ждёт самый медленный из них. Пусть каждый отвечает медленно лишь в 1 % случаев.
На сотне вызовов быстрыми окажутся все сразу лишь в 0,99100 ≈ 36,6 % случаев — значит, в 63,4 % пользовательский запрос упрётся хотя бы в один медленный. Это и есть усиление хвостового времени ожидания.
- скользящее окно последних 10 минут: каждую минуту считаем p50, p95, p99, p999 по запросам за окно
- строим график и смотрим не на число, а на его изменение
- p50 стабилен, а p99 пополз вверх — обычным пользователям хорошо, небольшой доле стало заметно плохо
p99 сервера A = 100 мс, p99 сервера B = 300 мс. Общий p99 — не 200 мс: так математика не работает. Складывать нужно распределения — гистограммы, а не готовые числа.
forward decay · t-digest · HdrHistogramШаг третий: чем масштабировать
Нагрузка описана, производительность описана — теперь можно выбирать: расти вверх или вширь. На практике — разумное сочетание того и другого.
Вертикальноеvertical scaling · scale up
Та же одна машина, только мощнее.
Плюсы
- архитектура остаётся простой
- нет распределённых проблем
- проще эксплуатация
Минусы
- мощные машины дорожают непропорционально
- есть физический предел
- одна машина — точка отказа
Горизонтальноеhorizontal scaling · scale out · shared-nothing
Много обычных машин, нагрузка распределяется между ними.
Плюсы
- растём по мере надобности
- отказ одной машины не убивает систему
- для stateless-сервиса — почти бесплатно
Минусы
- состояние приходится синхронизировать
- репликация и согласованность
- отказы становятся штатной ситуацией
Разница вся в состоянии. Stateless-сервис размножается почти даром — запрос можно отдать любому серверу. Но если сервер помнит корзину пользователя, соседний о ней не знает, и появляются репликация, согласованность, обработка отказов. Поэтому базы данных так долго старались держать на одной машине; инструменты стали лучше, но распределённость сама по себе не цель.
Главный вывод раздела
Универсальной масштабируемой архитектуры не существует. Она вырастает из конкретной нагрузки: чего больше — чтений или записей, насколько велики данные, как быстро нужно отвечать, какие бывают пики. Две системы с одинаковой пропускной способностью могут не иметь между собой ничего общего.
100 000 запросов в секунду, каждый по 1 КБ
- огромное количество запросов
- время отклика и параллельность
- процессор, сеть, очереди
3 запроса в минуту, каждый по 2 ГБ
- объём памяти
- последовательное чтение с диска
- дисковая пропускная способность
Пропускная способность одинаковая — около 100 МБ/с у обеих. Архитектуры будут совершенно разными, потому что нагрузка у них разная по характеру, а не по объёму.
![]()
Масштабируемость — не свойство системы, а ответ на вопрос: что мы сделаем, когда конкретный параметр нагрузки вырастет в десять раз?
вывод по разделу 1.3 книги «Высоконагруженные приложения» Мартина Клеппмана
Что далее
Антон Помазков
Артём Никифоров
Антон Помазков
Артём Никифоров