Узлы, рёбра, направления
Запрос как обход графа
Каждый компонент, с которым взаимодействует запрос, является узлом: клиент, DNS-резолвер, крайний узел CDN, reverse-proxy, реплика бэкенда, база данных, кэш.
Каждое соединение между двумя узлами — это ориентированное ребро: запросы идут вперёд, ответы — назад. Вперёд направленное ребро представляет собой открытый TCP-соединение плюс протокол поверх него.
Одиночный запрос — это путь через этот граф. Общий объём работы, который система выполняет для ответа на запрос, равен сумме работы на каждом узле плюс задержка на каждом ребре.
Зачем это нужно? Как только вы нарисуете граф, становятся видны свойства, которые невозможно разглядеть в коде:
- Количество хопов (скачков): количество рёбер на пути. Каждый хоп добавляет задержку (сетевой обмен + обработка узлом). Меньше хопов = более низкий порог задержки.
- Входящая степень: количество рёбер, направленных В узел. Высокая входящая степень означает, что узел получает запросы от многих источников и должен масштабироваться или защищаться.
- Исходящая степень: количество рёбер, направленных ИЗ узла. Высокая исходящая степень означает, что узел зависит от многих нижестоящих компонентов и имеет множество вариантов отказа.
- Разделяющая вершина (Cut vertex): единственный узел, удаление которого разрывает граф. Обратный прокси без резервного экземпляра — это разделяющая вершина; его удаление лишает всех доступа к его источникам.
Где концентрируется трафик
Схождение = Концентрация
Входящая степень узла = количество рёбер, направленных в него. В графе запросов входящая степень = количество верхневых источников, отправляющих запросы.
Схема слияния (fan-in): много клиентов -> один CDN; много краевых узлов CDN -> несколько прокси-серверов источника; много прокси -> меньшее число реплик бэкенда; много бэкендов -> единая база данных.
Концентрация важна, потому что узел с наибольшей степенью входа (in-degree) видит наибольшую суммарную нагрузку. БД в конце цепочки может получать запросы от каждого активного запроса во всей системе, даже если ни один пользователь не генерирует значительного объема.
Разветвление (Fan-Out) = Зависимость
Степень выхода (out-degree) узла = количество рёбер, исходящих из него. Высокая степень выхода означает множество зависимостей ниже по потоку.
Бэкенд, который обращается к базе данных, двум кэшам, трём внешним API и очереди, имеет степень выхода 7. Его вероятность успеха примерно равна произведению вероятностей успеха каждого нижележащего компонента (если все они необходимы для успешного ответа).
0.999 ^ 7 ≈ 0.993: бэкенд с 7 нижележащими компонентами, каждый из которых имеет надежность 99,9%, может обеспечить лишь ~99,3% собственной надежности, даже не имея собственных ошибок.
Снизить степень выхода можно: кэшированием результатов нижележащих компонентов, сделав некритичные зависимости необязательными (грациозное деградирование), параллелизацией того, что можно параллелить.
Асимметрия
Fan-in концентрирует нагрузку; fan-out умножает риск. Граф с правильной структурой минимизирует оба фактора на узлах с наибольшим влиянием.
База данных (наибольший fan-in): агрессивно используйте кэширование для снижения нагрузки. Используйте реплики для чтения, чтобы распределить fan-in между несколькими узлами.
Оркестратор (наибольший fan-out): используйте circuit breakers для каждой зависимости, плавное деградирование (graceful degradation) и bulkheads.
Вставка узла обеспечивает гибкость
Непрямые ссылки = добавление промежуточного узла
Без прокси граф выглядит так: клиент -> бэкенд. Клиент должен знать адрес бэкенда. Перемещение бэкенда требует обновления клиента (через DNS или конфигурацию). Это жёсткая связь.
С прокси граф становится таким: клиент -> прокси -> бэкенд. Клиент знает только о прокси. Перемещение бэкенда требует обновления конфигурации upstream прокси, а не клиента.
Операция над графом: вставка узла вдоль существующего ребра. Новое ребро клиент -> прокси является стабильным; новое ребро прокси -> бэкенд теперь находится в ведении команды.
Геометрическая интерпретация: непрямые ссылки добавляют слой, который развязывает изменения upstream от изменений downstream. Ребра каждого слоя могут переподключаться независимо.
Цена косвенности
Каждый слой добавляет:
- Один дополнительный переход задержки (ребро от клиента к прокси)
- Ещё одну точку разрыва на пути (само прокси)
- Ещё одно место, где может произойти ошибка конфигурации
Преимущества (переподключение, масштабирование, защита, завершение TLS, распределение нагрузки) обычно перевешивают затраты для любой нетривиальной системы. Но есть предел: каждый слой косвенности добавляет ещё один переход и ещё одного кандидата в единственные точки отказа (SPOF).
Народное правило: любую проблему можно решить, добавив слой косвенности (кроме проблемы слишком большого количества слоёв косвенности).
Чтение архитектуры как графа
Синтез
Теперь вы можете читать архитектуру системы как граф: подсчитывать число хопов, идентифицировать точки разрыва (cut vertices), измерять концентрацию fan-in, вычислять верхние пределы доступности на основе fan-out и оценивать компромиссы при использовании посредников (indirection).
Примените все четыре навыка.
У новой службы такая архитектура: clients -> CDN -> reverse proxy (2 реплики) -> backend tier (8 реплик) -> { DB primary, cache cluster (3 узла), external API }.
Сопроводительные заметки
Сопроводительные заметки
Этот урок по геометрии переосмысливает основной урок Прокси и источники как анализ направленного графа.
Следующий сопутствующий урок в этом курсе, geometry_of_stateless_horizontal_scaling, берёт математическую модель репликации из основного урока по масштабированию и геометрически выводит кривую очередей, закон Литтла и «колено» при 80% загрузке.
Отличная работа.