Статья
Зачем выбирать реплику по префиксу, если есть sticky sessions
Сессия помогает вернуть диалог на прежний сервер. Общий префикс связывает запросы, у которых вообще нет общей сессии. Выбор начинается с того, где повторяется работа.
Автор — Сергей Нотевский
Опубликовано
Зачем нужен кэш-роутер, если запросы одной сессии можно отправлять на один сервер? С этого вопроса начался мой пост про sticky sessionsВнешняя ссылка, откроется в новой вкладке. Привязка сессии действительно решает часть задачи. Для некоторых систем этой части достаточно.
Но сессия и повторяемый префикс не всегда совпадают. В чате повторяется история одного пользователя. В сервисе разметки документов тысячи независимых запросов начинаются с одной инструкции. Политика, удачная для первого случая, может незаметно дублировать вычисления во втором.
Сессия сохраняет адрес следующего запроса
Пользователь отправил сообщение, получил ответ и задал следующий вопрос. В новый запрос вошла прежняя история. Если он вернулся на тот же worker, сервер исполнения, у движка есть шанс переиспользовать KV — промежуточные состояния модели для совпавшего начала.
Session-based sticky выбирает worker по ключу сессии. При стабильном ключе и неизменном составе реплик следующие обращения сохраняют локальность диалога. Но привязка не подтверждает попадание в кэш: блоки могли вытеснить, worker мог перезапуститься, а приложение — изменить начало запроса.
Это три отдельные проверки: совпало ли начало, вернулся ли запрос к нужным данным и сохранились ли они до повторного обращения. Базовый механизм разобран в главе про префиксный кэш.
Общая инструкция не принадлежит одной сессии
Представим два независимых запроса на разметку. У них одинаковые правила, схема ответа и примеры, но разные документы. Полезное совпадение заканчивается перед документом. Идентификатор пользователя для поиска этого совпадения ничего не добавляет.
При sticky по сессии запросы могут случайно попасть на одну реплику и использовать общий префикс. Могут попасть на разные и создать несколько его копий. Политика не выбирает адрес по общему началу, поэтому такое переиспользование для неё побочный результат распределения.
Выбор по префиксу, prefix-aware routing, учитывает именно это начало. Так он находит общую работу между диалогами и независимыми запросами. Особенно полезно проверить такой вариант, когда в пуле несколько длинных инструкций и популярность сценариев различается.
| Профиль запросов | Разумная отправная точка | Что проверить дальше |
|---|---|---|
| Растущая история одного диалога | Sticky по сессии | Сохранность KV между обращениями и поведение после смены worker |
| Независимые документы с общей инструкцией | Привязка по сценарию или выбор по префиксу | Сколько копий инструкции пересчитывается на разных репликах |
| Несколько популярных сценариев | Сравнение ключей привязки и prefix-aware policy | Повторяемость между сессиями и распределение работы |
| Короткий, почти уникальный вход | Простая балансировка | Есть ли вообще заметный объём повторной обработки |
Есть и промежуточный вариант: привязать к реплике версию сценария. Это проще индекса префиксов, пока сценариев мало и их нагрузка предсказуема. Цена такой простоты проявляется, когда один сценарий становится намного популярнее остальных.
Привязка не заменяет совместимость и изоляцию
Перед выбором политики я бы зафиксировал, что считается одним префиксом. Сравнивать нужно фактический вход после шаблонизации, с порядком tools и выбранным chat template. Одинаковое название сценария при разных версиях инструкции не гарантирует повторного использования.
При обновлении весов, адаптера или формата входа нельзя считать старую привязку доказательством совместимости KV. Отдельно стоит проверить смену состава workers: куда уйдёт сессия после отказа и как поведёт себя ключ при добавлении реплик. Конкретный ответ зависит от реализации sticky, поэтому одного слова «хэширование» в конфиге мало.
Границы доступа задаются до поиска совпадения. Общая инструкция у двух арендаторов не даёт разрешения объединить их приватные контексты. В эксперименте нужно сохранить тот же режим изоляции, который потребуется в эксплуатации; иначе сравниваются разные условия обслуживания.
Балансировка остаётся отдельным условием
Round-robin выравнивает число назначений, но запросы разной длины создают разный объём работы. При этом он не обязан создавать перегрузку на любом потоке. Sticky и выбор по префиксу тоже не определяют победителя заранее.
Например, в standalone vllm-router, в проверенной cache_aware policy на commit 1d10e71, при одновременном превышении абсолютного и относительного порогов дисбаланса выбирается здоровый worker с меньшим load(). Это не правило «всегда отправлять к максимальному совпадению». Исходник политикиВнешняя ссылка, откроется в новой вкладке.
Но до такого сравнения нужно выяснить, какую строку получает политика. В обычном HTTP-пути /v1/chat/completions этого коммита извлекается только непустой session_params.session_id либо пустая строка. Содержимое messages в это сравнение не попадает. У /v1/completions вход извлекается из prompt. Код извлеченияВнешняя ссылка, откроется в новой вкладке. Поэтому включить cache_aware и сравнивать общие начала chat-промптов — не одно действие; сначала проверяются версия и endpoint.
Что именно видит этот счётчик и насколько достоверен индекс, разбираю в статье о состоянии роутера. А компромисс между очередью и пересчётом уже разобран отдельно: «Тёплый кэш — ещё не быстрый ответ».
Начать с двух повторов, затем сравнить политики
Первый шаг — взять пару запросов одного сценария и проверить общее начало. На странице проекта есть первый аудит с воспроизводимым примером и переход к собственным входным данным. Такой анализ находит изменения запроса; факт чтения KV требует данных движка.
Если начало стабильно, я бы сравнил простую балансировку, sticky и prefix-aware policy на одинаковом потоке. Сохранил бы времена прихода, распределения входа и выхода, модель, workers, бюджет KV и процедуру прогрева. Отдельно прогнал бы длинные диалоги, независимые запросы с общей инструкцией и смену worker. Повторения и смена порядка прогонов нужны, чтобы удачный прогрев не назначил победителя.
В результате нужны клиентское время до первого токена (TTFT), полное время ответа, ошибки и объём фактического переиспользования. Наблюдения роутера и движка следует соединить по запросу и попытке; требования к данным собраны в аудите маршрутизации.
Если sticky выполняет цели по задержке и стоимости, его достаточно. Следующая политика нужна тогда, когда сравнение показывает конкретную потерю: повторную обработку общих префиксов, неудачное распределение работы или долгое восстановление локальности после отказа.