# Runtime-модель Telemt ## Область описания Документ фиксирует ключевые runtime-понятия пайплайна Middle-End (ME) и оркестрации вокруг него. Фокус: - `ME Pool / Reader / Writer / Refill / Registry` - `Adaptive Floor` - `Trio-State` - `Generation Lifecycle` ## Базовые сущности ### ME Pool `ME Pool` — центральный оркестратор всех Middle-End writer-ов. Зона ответственности: - хранит инвентарь writer-ов по DC/family/endpoint; - управляет выбором writer-а и маршрутизацией; - ведёт состояние поколений (`active`, `warm`, `draining` контекст); - применяет runtime-политики (floor, refill, reconnect, reinit, fallback); - отдаёт сигналы готовности для admission-логики (conditional accept/cast). Что не делает: - не декодирует клиентский протокол; - не реализует бизнес-политику пользователя (квоты/лимиты). ### ME Writer `ME Writer` — долгоживущий ME RPC-канал к конкретному endpoint (`ip:port`), у которого есть: - канал команд на отправку; - связанный reader loop для входящего потока; - флаги состояния/деградации; - метаданные contour/state и generation. Writer — это фактический data-plane носитель клиентских сессий после бинда. ### ME Reader `ME Reader` — входной parser/dispatcher одного writer-а: - читает и расшифровывает ME RPC-фреймы; - проверяет sequence/checksum; - маршрутизирует payload в client-каналы через `Registry`; - обрабатывает close/ack/data и обновляет телеметрию. Инженерный принцип: - Reader должен оставаться неблокирующим. - Backpressure одной клиентской сессии не должен останавливать весь поток writer-а. ### Refill `Refill` — механизм восстановления покрытия writer-ов при просадке: - восстановление на том же endpoint в первую очередь; - восстановление по DC до требуемого floor; - опциональные outage/shadow-режимы для хрупких single-endpoint DC. Refill работает асинхронно и не должен блокировать hotpath. ### Registry `Registry` — маршрутизационный индекс между ME и клиентскими сессиями: - `conn_id -> канал ответа клиенту`; - map биндов `conn_id <-> writer_id`; - снимки активности writer-ов и idle-трекинг. Ключевые инварианты: - один `conn_id` маршрутизируется максимум в один активный канал ответа; - потеря writer-а приводит к безопасному unbind/cleanup и отправке close; - именно `Registry` является источником истины по активным ME-биндам. ## Adaptive Floor ### Что это `Adaptive Floor` — runtime-политика, которая динамически меняет целевое число writer-ов на DC в зависимости от активности, а не держит всегда фиксированный статический floor. ### Зачем Цели: - уменьшить churn на idle-трафике; - сохранить достаточную прогретую ёмкость для быстрых всплесков; - снизить лишние reconnect-штормы на нестабильных endpoint. ### Модель поведения - при активности floor стремится к статическому требованию; - при длительном idle floor может снижаться до безопасного минимума; - grace/recovery окна не дают системе "флапать" слишком резко. ### Ограничения безопасности - нельзя нарушать минимальный floor выживаемости DC-группы; - refill обязан быстро нарастить покрытие по запросу; - адаптация не должна принудительно ронять уже привязанные healthy-сессии. ## Trio-State `Trio-State` — контурная роль writer-а: - `Warm` - `Active` - `Draining` ### Семантика состояний - `Warm`: writer подключён и валиден, но не основной для новых биндов. - `Active`: приоритетный для новых биндов и обычного трафика. - `Draining`: новые обычные бинды не назначаются; текущие сессии живут до правил graceful-вывода. ### Логика переходов - `Warm -> Active`: когда достигнуты условия покрытия/готовности. - `Active -> Draining`: при swap поколения, замене endpoint или контролируемом выводе. - `Draining -> removed`: после drain TTL/force-close политики (или естественного опустошения). Такое разделение снижает SPOF-риски и делает cutover предсказуемым. ## Generation Lifecycle Generation изолирует эпохи пула при reinit/reconfiguration. ### Фазы жизненного цикла 1. `Bootstrap`: поднимается начальный набор writer-ов. 2. `Warmup`: создаётся и валидируется новое поколение. 3. `Activation`: новое поколение становится active после прохождения coverage-gate. 4. `Drain`: предыдущее поколение переводится в draining, текущим сессиям дают завершиться. 5. `Retire`: старое поколение удаляется по graceful-правилам. ### Операционные гарантии - нельзя активировать поколение частично без минимального покрытия; - healthy-клиенты не должны теряться только из-за появления нового поколения; - draining-поколение служит буфером для in-flight трафика во время swap. ### Готовность и приём клиентов Готовность пула не равна "все endpoint полностью насыщены". Типичная стратегия: - открыть admission при минимально достаточном alive-покрытии по DC; - параллельно продолжать saturation для multi-endpoint DC. Это уменьшает startup latency и сохраняет выход на полную ёмкость. ## Как понятия связаны между собой - `Generation` задаёт эпохи пула. - `Trio-State` задаёт роль каждого writer-а внутри/между эпохами. - `Adaptive Floor` задаёт, сколько ёмкости нужно сейчас. - `Refill` — исполнитель, который закрывает разницу между desired и current capacity. - `Registry` гарантирует корректную маршрутизацию сессий, пока всё выше меняется. ## Архитектурный подход ### Слоистая модель Runtime специально разделён на две плоскости: - `Control Plane`: принимает решения о целевой топологии и политиках (`floor`, `generation swap`, `refill`, `fallback`). - `Data Plane`: исполняет транспорт сессий и пакетов (`reader`, `writer`, маршрутизация, ack, close). Ключевое правило: - Control Plane может менять состав writer-ов и policy. - Data Plane должен оставаться стабильным и низколатентным в момент этих изменений. ### Модель владения состоянием Владение разделено по доменам: - `MePool` владеет жизненным циклом writer-ов и policy-state. - `Registry` владеет routing-биндами клиентских сессий. - `Writer task` владеет исходящей прогрессией ME-сокета. - `Reader task` владеет входящим парсингом и dispatch-событиями. Это ограничивает побочные мутации и локализует инварианты. ### Обязанности Control Plane Control Plane работает событийно и policy-ориентированно: - стартовая инициализация и readiness-gate; - runtime reinit (периодический и/или по изменению конфигурации); - проверки покрытия по DC/family/endpoint group; - применение floor-политики (static/adaptive); - планирование refill и orchestration retry; - переходы поколений (`warm -> active`, прежний `active -> draining`). Для него важнее детерминизм, чем агрессивная краткосрочная реакция. ### Обязанности Data Plane Data Plane ориентирован на пропускную способность и предсказуемую задержку: - bind клиентской сессии к writer-у; - per-frame parsing/validation/dispatch; - распространение ack/close; - корректная реакция на missing conn/closed channel; - минимальный лог-шум в hotpath. Data Plane не должен ждать операций, не критичных для корректности текущего фрейма. ## Конкурентность и синхронизация ### Принципы конкурентности - Изоляция по writer-у: у каждого writer-а независимые send/read loop. - Изоляция по сессии: состояние канала локально для `conn_id`. - Асинхронное восстановление: refill/reconnect выполняются вне пакетного hotpath. ### Стратегия синхронизации - Для shared map используются короткие и узкие lock-секции. - Read-heavy пути избегают длительных write-lock окон. - Решения по backpressure локализованы на границе route/channel. Цель: - медленный consumer должен деградировать локально, не останавливая глобальный прогресс writer-а. ### Cancellation и shutdown Reader/Writer loop должны быть cancellation-aware: - явные cancel token / close command; - безопасный unbind/cleanup через registry; - детерминированный порядок: stop admission -> drain/close -> release resources. ## Модель согласованности ### Согласованность сессии Для одного `conn_id`: - одновременно ровно один активный route-target; - close/unbind операции идемпотентны; - потеря writer-а не оставляет dangling-бинды. ### Согласованность поколения Гарантии generation: - новое поколение не активируется до прохождения минимального coverage-gate; - предыдущее поколение остаётся в `draining` на время handover; - принудительный вывод writer-ов ограничен policy (`drain ttl`, optional force-close), а не мгновенный. ### Согласованность политик Изменение policy (`adaptive/static floor`, fallback mode, retries) не должно ломать инварианты маршрутизации уже активных сессий. ## Backpressure и управление потоком ### Route-level backpressure Route-каналы намеренно bounded. При росте нагрузки: - кратковременный burst поглощается; - длительная перегрузка переходит в контролируемую drop-семантику; - все drop-сценарии должны быть прозрачно видны в метриках. ### Приоритет неблокирующего Reader Входящий ME-reader path не должен сериализоваться из-за одной перегруженной клиентской сессии. Практически это означает: - использовать неблокирующую попытку route в parser loop; - выносить тяжёлое восстановление в асинхронные side-path. ## Стратегия доменов отказа ### Отказ отдельного endpoint Сначала применяется endpoint-local recovery: - reconnect в тот же endpoint; - затем замена endpoint внутри той же DC-группы (если доступно). ### Деградация уровня DC Если DC-группа не набирает floor: - сервис сохраняется на остаточном покрытии (если policy разрешает); - saturation refill продолжается асинхронно в фоне. ### Потеря готовности всего пула Если достаточного ME-покрытия нет: - admission gate может временно закрыть приём новых подключений (conditional policy); - уже активные сессии продолжают работать, пока их маршрут остаётся healthy. ## Архитектурные заметки по производительности ### Дисциплина hotpath Допустимо в hotpath: - фиксированный и дешёвый parsing/validation; - bounded channel operations; - precomputed/low-allocation доступ к данным. Нежелательно в hotpath: - повторные дорогие decode; - широкие lock-секции с `await` внутри; - высокочастотный подробный logging. ### Стабильность важнее пиков Архитектура приоритетно выбирает стабильную пропускную способность и предсказуемую latency, а не краткосрочные пики ценой churn и long-tail reconnect. ## Правила эволюции модели Чтобы расширять модель безопасно: - новые policy knobs сначала внедрять в Control Plane; - контракты Data Plane (`conn_id`, route/close семантика) держать стабильными; - перед дефолтным включением проверять generation/registry инварианты; - новые recovery/retry стратегии вводить через явный config-флаг. ## Нюансы отказов и восстановления - падение single-endpoint DC — штатный деградированный сценарий; приоритет: быстрый reconnect и, при необходимости, shadow/probing; - idle-close со стороны peer должен считаться нормальным событием при upstream idle-timeout; - backoff reconnect-логики должен ограничивать синхронный churn, но сохранять быстрые первые попытки; - fallback (`ME -> direct DC`) — это переключаемая policy-ветка, а не автоматический признак бага транспорта. ## Краткий словарь - `Coverage`: достаточное число живых writer-ов для политики приёма по DC. - `Floor`: целевая минимальная ёмкость writer-ов. - `Churn`: частые циклы reconnect/remove writer-ов. - `Hotpath`: пер-пакетный/пер-коннектный путь, где любые лишние ожидания и аллокации особенно дороги.