Перейти к содержанию

ADR-0002 · Границы: перенос линии вокруг лент#

Статус:    принят · 2026-09-16
Метод:     D-02 v3.0 · шаблон ADR v1.2
Участники: команды «лента», «посты», «платформа»

Учебная запись: часть прогона E-02, система вымышлена. Форма и рассуждение настоящие.

Контекст#

Вход — 00-context.md. Хранилище лент выбрано в ADR-0001.

Что есть сейчас — два сервиса, разделённые исторически, а не по решению:

Сервис Что внутри Владелец
web API, сборка ленты, профили, граф подписок Команда «лента»
posts Создание и хранение постов, медиа Команда «посты»

Сборка ленты живёт в web, посты — в posts, поэтому каждое открытие ленты требует обращения через границу. Отсюда 347 тысяч межсервисных обращений в секунду [расчёт W1] и деградация p99.

Семь вопросов D-02 v3.0 — впервые все с ответами#

Вопрос Ответ Пометка
Сколько изменений за полгода затронуло больше одного сервиса? 32 % из 1 400 задач. В одной границе укладывается 68 % [история 6 мес]
Какие операции обязаны выполняться атомарно? Ни одной [требования]
У каких сущностей больше одного пишущего? Одна: users.last_seen пишут и web при открытии ленты, и профили при заходе [схема]
Где одно слово означает разное? «Лента»: экран у продукта, список идентификаторов у бэкенда, поток событий у аналитики. «Пост»: запись в posts, идентификатор в ленте [орг]
Сколько команд и кто чем владеет? Три: лента, посты, платформа. Команда «лента» владеет web, где лежат ещё профили и подписки — не её язык [орг]
У каких частей профиль нагрузки различается на порядок? Чтение ленты 12 000/с против публикации 12/с — три порядка [расчёт W1]
Что по плану переписывается в год? Ранжирование ленты — сядет на ленту, не на посты [орг]

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

Заодно выяснилось, что нынешняя граница частично идёт по техническим слоям: web — это «всё, что отвечает на запросы», posts — «то, где лежат данные постов». D-02 называет такой разрез анти-формой, и 32 % сквозных задач — ровно его симптом.

Дисквалификаторы#

Сработал один из шести — против трёх в E-01, где они отсекли саму идею разреза.

Условие Здесь
Разрыв атомарной операции в основном сценарии Не срабатывает: атомарности нет нигде
У сущности больше одного пишущего Срабатывает для варианта «оставить как есть»: users.last_seen остаётся с двумя писателями
Разрез по техническим слоям Не срабатывает для кандидатов; срабатывает для текущего состояния, см. выше
У сервиса нет владельца Не срабатывает: три команды, дежурство выделенное
Нет наблюдаемости, трассировки, автовыката Не срабатывает: всё построено
Сервисов больше, чем команд Не срабатывает: три и три

Рассмотренные варианты#

Ось Что записывать Оставить как есть A — выделить ленту третьим B — перенести линию по языку
Связность изменений Доля задач за период в одной границе 68 % [история 6 мес] 74 % — часть сквозных задач остаётся [история 6 мес] 89 % [история 6 мес]
Транзакционные разрывы Список операций, пересекающих границу нет нет нет
Владение данными Сколько сущностей с несколькими писателями 1 — users.last_seen [схема] 1 — та же [схема] 0 — подписки уезжают к ленте вместе с записью [схема]
Языковая чистота Сколько терминов с двойным значением внутри границы 2 — «лента» и «пост» [орг] 1 — «пост» [орг] 0 [орг]
Организационное соответствие Команды и границы один к одному Нет: «лента» владеет профилями и подписками [орг] Частично [орг] Полное [орг]
Глубина синхронной цепочки Сетевых вызовов в основном сценарии 1, но 347 тыс./с [расчёт W1] 1, редкий — досбор 1 % [расчёт] 1, редкий [расчёт]
Стоимость перехода Шагов миграции и что ломается 0 2 шага, контракт web↔лента 4 шага: перенос подписок, strangler fig на API [схема]
Обратимость Что переделывать при ложной границе — Недели Недели плюс миграция графа подписок

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

Решение#

Вариант B: перенести линию по языку.

Сервис Что внутри Владелец
лента Материализованные ленты, раздача, граф подписок, чтение ленты Команда «лента»
посты Создание, хранение, удаление постов, медиа Команда «посты»
Шлюз Маршрутизация, аутентификация, профили Платформа

Граф подписок переезжает к ленте: он нужен раздаче на каждую публикацию и не нужен больше никому. Профили уходят к платформе вместе со шлюзом. web как сервис растворяется.

Переход — четырьмя шагами через strangler fig (ARCH-13), с переносом подписок последним.

Обоснование#

Решила связность изменений — единственная ось, которую нельзя было оценить на глаз. 89 % против 74 % и 68 %: разница не в пределах погрешности, и она посчитана по 1 400 задачам, а не выведена из соображений.

Вариант «оставить как есть» дисквалифицирован по двум писателям у одной сущности. Строго говоря, users.last_seen — мелочь, которую можно починить и не трогая границ; но вместе с 68 % связности и разрезом по слоям он складывается в одну картину: линия проведена не там, и её надо двигать, а не латать.

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

Самая дорогая будущая граница#

Между лентой и постами. Она остаётся и после переноса: каждое открытие ленты требует досбора постов по идентификаторам. Сейчас это дёшево, потому что материализация свела обращения к одному проценту открытий (ADR-0001). Но если ранжирование ленты, стоящее в роадмапе, потребует содержимого постов, а не только идентификаторов, — эта граница станет узким местом снова, и денормализация постов в ленту сделает удаление поста дорогим. Записано здесь, потому что через год связь между ранжированием и стоимостью удаления будет неочевидной.

Последствия#

  • Миграция графа подписок — самый рискованный шаг. 3 млрд рёбер, 48 ГБ; переносить придётся с двойной записью и сверкой, а не переключением.
  • До завершения перехода живут два пути чтения ленты — старый и новый. Срок сосуществования надо ограничить датой, иначе он станет постоянным.
  • Команда «платформа» получает профили, то есть продуктовый код. Это отклонение от её роли, и его стоит пересмотреть, когда появится четвёртая команда.
  • Три сервиса на три команды — граница, за которой дробление придётся останавливать: следующий сервис окажется без владельца.
  • users.last_seen чинится по дороге: писать будет только шлюз.

Условия пересмотра#

  • Доля задач внутри одной границы снова упала ниже 80 % — теперь это измеримо и должно попасть в регулярный отчёт.
  • Появилась четвёртая команда.
  • Ранжирование потребовало содержимого постов в ленте — см. «самая дорогая будущая граница».
  • Профиль нагрузки частей разошёлся так, что мешает масштабировать их порознь.
  • Атомарность потребовалась там, где её сейчас нет.

Связанные ADR#

Зависит от: ADR-0001. Зависимые: ADR-0003 (раздача поста идёт через границу «посты → лента»).