E-02 · Лента новостей — вход#
Этап прогона: прогон завершён · правки исполнены 2026-09-17 · вход зафиксирован 2026-09-16 Кейс: CASE-02 лента новостей Шаги: W0 · Рамки и требования и W1 · Оценки на салфетке
Зачем второй прогон. Четырнадцать правок по итогам E-01 сделаны по одному кейсу. Пока они не проверены на непохожем, набор растёт на непроверенном основании.
Кейс выбран противоположным по всем осям, которые документы используют — и это касается не только предметной области.
Система вымышлена, но внутренне непротиворечива. Числа не взяты у реальной компании; они выбраны так, чтобы арифметика сходилась и решения принимались на них по-настоящему. Пометки:
[замер]— измерено на работающей системе,[расчёт]— выведено арифметически,[продукт]— задано продуктом,[орг]— из состава команд и инфраструктуры.
Чем E-02 противоположен E-01#
| E-01 · Бронирование | E-02 · Лента | |
|---|---|---|
| Стадия проекта | Гринфилд — фактов нет, предсказываем | Работающая система два года — факты измеряются |
| Дефицитный ресурс | Место: продать дважды нельзя | Нет вовсе |
| Согласованность | Атомарность обязательна, местами недостижима | Итоговая по построению, отставание 10 с допустимо |
| Объём | 130 ГБ за пять лет — ничтожен | Сотни гигабайт в памяти, терабайты на диске |
| Нагрузка | Пик ×1000 полчаса в неделю | Ровная, пик ×10, круглосуточно |
| Команда | Четыре разработчика, платформенной нет | Три команды, платформенная есть |
| Наблюдаемость | Нет — система не запущена | Есть: метрики, трассировка, автовыкат |
| Что решает исход | Конкуренция за строку | Соотношение чтений к записям и неравномерность подписок |
Стадия — главное отличие. E-01 породил D-05 · Границы на старте — решение там принималось по D-02 v2.1, а отдельный документ выделился уже по итогам прогона, так что применением D-05 E-01 не был. E-02 проверит D-02 · Границы в работающей системе — вторую половину разделения, сделанного на этапе 6. Заодно проверится сама позиция: действительно ли это разные решения, а не один документ с ветками.
Почти все дисквалификаторы, сработавшие в E-01, здесь не сработают. Это и нужно: документ, который на одном кейсе всё отсекает, а на другом ничего, — проверен вдвое лучше, чем документ, испытанный дважды в одинаковых условиях.
План прогона#
| Этап | Что делаем | Документ | Статус |
|---|---|---|---|
| 1 | Вход: рамки, числа, инварианты | W0, W1 | ✅ этот файл |
| 2 | Выбор хранилища под ленты | D-01 v1.1 → ADR-0001 | ✅ |
| 3 | Границы работающей системы | D-02 v3.0 → ADR-0002 | ✅ |
| 4 | Связь в процессе раздачи поста | D-04 v2.0 → ADR-0003 | ✅ |
| 5 | Кэш и стратегия чтения ленты | D-03 v2.0 → ADR-0004 | ✅ |
| 6 | Ревизия: подтвердились ли правки | 98-revision-plan.md | ✅ 20 находок в правки, пять документов в MAJOR |
Нумерация ADR начинается заново: это другой проект, а не продолжение первого.
1. Что имеем#
Социальная сеть работает два года. Лента обратно хронологическая, собирается при чтении: открыл — сходили за постами всех подписок, слили, отдали. Схема работала до полумиллиона активных пользователей и с тех пор деградирует.
Симптом, из-за которого всё началось: p99 открытия ленты вырос со 180 мс до 900 мс за полгода [замер]. Цель по продукту — 200 мс.
Вне рамок: ранжирование ленты (только хронология), рекомендации, медиафайлы (лежат в объектном хранилище, ссылками), личные сообщения, реклама.
2. Инварианты#
Их меньше, чем в E-01, и все мягче — атомарности не требуется нигде.
| № | Инвариант | Чем грозит нарушение |
|---|---|---|
| И1 | Пользователь видит собственный пост немедленно, не дожидаясь раздачи | Выглядит как потеря поста; пользователь публикует второй раз |
| И2 | Удалённый пост не показывается ни в одной ленте | Юридический риск и жалобы |
| И3 | Пост не появляется в ленте дважды | Видно сразу, выглядит как поломка |
Отставание до 10 секунд допустимо [продукт] — это не недоработка, а сознательная уступка, и именно она открывает целый класс архитектур. В E-01 сопоставимой уступки не было: там место либо ваше, либо нет.
3. Профиль нагрузки#
| Показатель | Значение | Откуда |
|---|---|---|
| Активных пользователей в сутки | 10 млн | [замер] |
| Открытий ленты на пользователя в сутки | 10 | [замер] |
| Чтений ленты | ≈ 1 200/с средн., 12 000/с в пике | [расчёт]: 100 млн ÷ 86 400, пик ×10 |
| Публикаций | ≈ 12/с | [замер] |
| Соотношение чтений к записям | 100 : 1 | [расчёт] |
| Подписок на пользователя | медиана 120, среднее 300 | [замер] — распределение с тяжёлым хвостом |
| Аккаунтов с аудиторией свыше миллиона | ~500 | [замер] |
| Максимальная аудитория | 10 млн подписчиков | [замер] |
Почему нынешняя схема не тянет. Сборка при чтении означает обращение к постам всех подписок на каждое открытие: 1 200 × 300 = 347 тысяч обращений в секунду в среднем и 3,5 миллиона в пике [расчёт]. Кэш по авторам гасит часть, но растёт вместе с аудиторией, и запас кончился.
Неравномерность — вторая, независимая проблема. Одна публикация аккаунта с 10 млн подписчиков при раздаче на запись даёт 10 млн вставок. При устойчивом темпе 18 тысяч вставок в секунду это девять минут работы [расчёт], в течение которых обычные посты стоят в очереди. Любая симметричная схема ломается об этот хвост.
4. Объём данных#
| Что | Размер | Откуда |
|---|---|---|
| Пост (текст и метаданные, без медиа) | ~1 КБ | [замер] |
| Посты | 1 ГБ/сут, 374 ГБ/год, 0,7 ТБ накоплено | [расчёт] |
| Медиа | В объектном хранилище, вне рамок | [продукт] |
| Лента в памяти при раздаче на запись | 10 млн × 1 000 идентификаторов × 16 Б = 160 ГБ, с накладными ≈ 320 ГБ | [расчёт] |
| Граф подписок | ~3 млрд рёбер, 48 ГБ | [расчёт]: 10 млн × 300 |
Здесь объём решает. В E-01 он был ничтожен и ни на что не влиял; здесь 320 ГБ в оперативной памяти — это статья расходов, сопоставимая со всем остальным, и ось «стоимость владения на единицу нагрузки» впервые заполняется не пометкой [нет данных].
5. Бюджет задержки#
Цель p99 открытия ленты — 200 мс [продукт]. Сейчас 900 мс [замер].
| Участок | Сейчас [замер] |
Целевой |
|---|---|---|
| Сеть до площадки и обратно | 40 мс | 40 мс |
| Периметр | 10 мс | 10 мс |
| Сборка ленты | 780 мс | 90 мс |
| Досбор постов и сериализация | 70 мс | 60 мс |
| Итого | 900 мс | 200 мс |
Весь перерасход — в одном участке, и он измерен трассировкой, а не угадан. Публикация поста: p99 500 мс [продукт], сейчас 220 мс — запас есть.
6. Доступность#
| Что | Цель | Обоснование |
|---|---|---|
| Чтение ленты | 99,95 % | Основной сценарий; недоступность видна всем |
| Публикация | 99,9 % | Пользователь повторит через минуту |
| Свежесть ленты | Отставание до 10 с | Уступка, открывающая асинхронный путь |
Доступность равномерна во времени — прямая противоположность E-01, где полчаса в неделю стоили дороже остальных 167 часов. Здесь нет момента, который нельзя пропустить, и это меняет то, как читаются все последующие решения.
7. Ограничения, не относящиеся к технике#
Почти все — противоположны E-01, и это главная ценность второго кейса.
- Команды: три — лента, посты, платформа. По 5–7 человек. Платформенная команда есть, дежурство выделенное
[орг]. - Наблюдаемость: метрики за год, сквозная трассировка за три месяца, автоматический выкат
[орг]. - В эксплуатации уже есть: PostgreSQL, Redis, Kafka, Kubernetes. Опыт эксплуатации у платформенной команды
[орг]. - Размещение: один провайдер, три зоны доступности, во втором регионе реплика только на чтение
[орг]. - История доступна: репозиторий два года, трекер — 1 400 задач за последние полгода, схема данных, метрики
[орг].
8. Что осталось неизвестным#
| Неизвестно | Чем закроется |
|---|---|
| Распределение подписок по перцентилям, не только медиана и среднее | Запрос к графу подписок — час работы |
| Доля открытий ленты, где пользователь уходит дальше первой страницы | Есть в метриках, не выгружали |
| Стоимость гигабайта памяти против гигабайта диска у нашего провайдера | Тариф; нужен для оси стоимости владения |
| Фактическое время раздачи поста по подписчикам | Трассировка есть, замер не делали |
Дальше: этап 2 — D-01 v1.1 · Выбор хранилища, результат в ADR-0001.