Управление и учет

Политики резервирования бюджета

Как Meridian резервирует расходы до отправки запроса в провайдера — flat, worst-case и отключённое резервирование, когда какой режим выбирать и как настроить.

Обзор

При проверке бюджета Meridian резервирует часть лимита до того, как запрос ушёл к LLM-провайдеру, и списывает реальную стоимость после ответа. Это двухфазный протокол:

RESERVE   — резервирует maxCost (верхняя оценка) на бюджете

Запрос к LLM-провайдеру

FINALIZE  — списывает фактическую стоимость, освобождает резерв

Сколько именно резервируется на шаге RESERVE, определяется политикой резервирования. От её выбора напрямую зависит:

  • насколько строго блокируются превышения бюджета в момент пика нагрузки;
  • какая часть лимита временно недоступна под параллельные запросы;
  • как часто отказы происходят на этапе RESERVE против отказов после расчёта реальной стоимости.

Политика задаётся глобально для шлюза в секции governance.reservation файла config.json и применяется ко всем бюджетам — клиентов, команд, виртуальных ключей и per-provider-config — единообразно.


Жизненный цикл резервирования

У записи о резервировании ровно два состояния: активна (создаётся RESERVE в начале запроса) и удалена (после FINALIZE — списание состоялось). Списание происходит ровно один раз, независимо от того, чем закончился запрос.

RESERVE
(maxCost зарезервирован, ExpiresAt = now + 1ч)

FINALIZE(actualCost)
штатный путь, есть тело ответа

FINALIZE(MaxCost)
HTTP-timeout провайдера, тела ответа нет

Трекер резервов → FINALIZE(MaxCost)
ExpiresAt истёк, осиротевший резерв

Активна

Все три перехода применяют одно и то же действие — FINALIZE. Различается только то, какая сумма списывается и кто инициирует:

Что произошло с запросомКто завершает резервЧто списаноКогда
Получен ответ от провайдераPostLLMHookactualCost (из ответа)сразу после получения ответа
HTTP-timeout провайдера (нет тела ответа)PostLLMHookMaxCost (зарезервированная сумма)сразу после срабатывания таймаута
Падение ноды до FINALIZEReservationTrackerMaxCostчерез reservation_ttl (по умолчанию 1 час)
Запрос реально идёт дольше reservation_ttlReservationTrackerMaxCostчерез reservation_ttl

Принцип прост: каждое резервирование завершается списанием. Если фактическая стоимость известна — списывается она; если нет — MaxCost как консервативный заменитель. Потерь учёта быть не может.

ReservationTracker — фоновый процесс на лидере каждого шарда. Сканирует резервирования каждые 5 секунд; работает только с теми, у которых истёк ExpiresAt. На штатных запросах не вмешивается — PostLLMHook всегда успевает первым.

Рекомендуемые настройки

reservation_ttl определяет, как долго резервирование считается «живым», прежде чем его подберёт ReservationTracker. Значение по умолчанию — 1 час — рассчитано на любую реальную длительность одиночного запроса.

Связанная настройка — таймаут HTTP-запросов провайдера (network_config.default_request_timeout в конфигурации провайдера). Он задаёт, сколько Meridian держит TCP-соединение с upstream'ом, ожидая ответа.

ПараметрЗначение по умолчаниюРекомендуемая граница
cluster_config.raft.reservation_ttl1hоставить по умолчанию
providers[*].network_config.default_request_timeoutзадаётся явноне более 30 минут

Не настраивайте таймаут HTTP-запросов провайдера больше 30 минут — это практический верхний предел. Даже самые тяжёлые сценарии (Claude Opus с контекстом 200K, генерация длинных потоков) укладываются в 10–15 минут. Более высокие таймауты не дают пользы, удерживают TCP-соединения в висящем состоянии, маскируют реальные деградации провайдера и удлиняют окно недоступности при сбоях. Если провайдер не ответил за 30 минут — почти наверняка не ответит и за час.


Почему это важно

Политика резервирования напрямую влияет на финансовую предсказуемость и пропускную способность при высоких нагрузках. Поведение, которое выглядит идентично на малом RPS, расходится тем сильнее, чем выше параллелизм и чем дороже отдельный запрос.

LLM-автоматизации (агентные пайплайны, batch-обработка)

Агенты и пайплайны часто выпускают серии запросов в коротком окне — десятки последовательных шагов или пачки в десятки тысяч запросов. Каждый отдельный запрос может стоить от долей цента до нескольких долларов в зависимости от модели и контекста. При параллельной отправке пачки система должна решать на этапе RESERVE, хватит ли бюджета — до того, как известны фактические стоимости.

Слишком пессимистичный резерв (worst_case) гарантирует, что бюджет физически невозможно превысить, но при дорогих моделях резервирует так много на каждый запрос, что параллельные запросы конкурируют за один и тот же «слот» резерва и часть из них отклоняется. Оптимистичный резерв (flat $0.05 или меньше) пропускает пачку целиком, но фактический расход после FINALIZE может на короткое время превысить лимит.

AI SaaS-сервисы с тарифными планами клиентов

В SaaS-моделях клиент покупает план с лимитом расходов на период (например, $50/мес). Каждый его запрос проверяется по бюджету клиента/команды/VK. Здесь критично:

  • Жёсткий контроль перерасхода — пользователь, заплативший за $50, не должен «потратить» $54 из-за гонки RESERVE/FINALIZE;
  • Предсказуемое поведение для оператора — отказы должны быть объяснимы (превышен бюджет), а не плавать в зависимости от размера модели;
  • Возможность отдельно настроить дорогие модели — Claude Opus и GPT xhigh классов требуют worst-case резерва, а быстрые/дешёвые — нет.

Подходящий выбор политики — flat-резерв на дешёвые модели + worst-case на премиум-модели через model_overrides. См. Когда какой режим выбирать.

Крупные потребители LLM/AI с высоким объёмом и низкой средней стоимостью

Внутренние сервисы и крупные продукты часто работают с бюджетами на порядки больше стоимости отдельного запроса ($100K/мес, бессрочные лимиты на отделы). В этом режиме резервирование per-request почти не несёт защитной функции — даже worst-case резерв в $4 на каждый запрос значительно меньше доступного лимита. Зато strict RESERVE искусственно ограничивает пропускную способность под пиковой нагрузкой.

Здесь часто выгодно отключить резервирование совсем — фактический расход всё равно списывается через FINALIZE и отказы будут происходить только после реального превышения лимита, без блокировок на этапе RESERVE.


Три политики

Meridian поддерживает три режима резервирования. Все они применяются единообразно ко всей иерархии governance — customers, teams, virtual keys и provider configs.

ПолитикаСумма резерва на RESERVEЗащита от перерасходаПараллелизм при малых бюджетах
Flat $0.05 (по умолчанию)Фиксированно $0.05 на запросСредняя: фактическая стоимость может немного превысить лимит до FINALIZEВысокий — на бюджет $20 проходят ~400 параллельных запросов
Worst-caseCatalog-driven верхняя оценка (от $0.01 до нескольких долларов)Строгая: RESERVE физически не пропустит превышениеОграничен текущим доступным бюджетом и размером резерва
Disabled (sentinel)$1e-9 (символический)Без RESERVE-защиты, только пост-фактум через FINALIZEМаксимальный — RESERVE не ограничивает

Flat $0.05 (политика по умолчанию)

RESERVE всегда резервирует одинаковую сумму — $0.05, независимо от модели и размера запроса. Это компромисс «достаточно, чтобы поймать очевидное превышение, мало, чтобы не блокировать параллелизм».

Как это выглядит на практике. Команда с бюджетом $20 и моделью openai/gpt-5.4 xhigh (фактическая стоимость ~$4.05/запрос):

  • RESERVE #1: pending=$0.05, settled=$0 → $0.05 < $20 ✓ пропускает
  • RESERVE #2: pending=$0.10, settled=$0 → $0.10 < $20 ✓ пропускает (FINALIZE первого ещё не пришёл)
  • … несколько запросов проходит подряд …
  • После FINALIZE: settled=$8.10, бюджет $20 пока не исчерпан
  • Когда settled превышает $20, следующий запрос получает 402 на этапе RESERVE — но запросы, уже находящиеся в полёте, успешно завершаются.

Свойства:

  • Высокая пропускная способность для дешёвых моделей и больших бюджетов;
  • Возможен кратковременный перерасход на величину actualCost − $0.05 × (число запросов в полёте на момент пересечения лимита);
  • Хорошо работает, когда стоимость одного запроса много меньше доступного бюджета.

Worst-case (catalog-driven)

RESERVE вычисляет верхнюю оценку стоимости запроса по каталогу цен моделей: учитываются input_cost_per_token, output_cost_per_token, максимальное число output-токенов и тип запроса (chat/embedding/image и т. п.). Например, для Claude Opus при контексте в 1 миллион токенов верхняя оценка превышает $8 за 1 запрос — и именно эта сумма резервируется до получения ответа.

Свойства:

  • Строгая гарантия неперерасхода: если RESERVE пропустил запрос, фактическая стоимость гарантированно меньше резерва и FINALIZE только освобождает разницу;
  • Меньшая пропускная способность параллельных запросов на одну и ту же модель: при бюджете $8 и worst-case резерве $4.146 одновременно «в полёте» помещается только один запрос Opus;
  • Возможны конфликты по RESERVE на малых бюджетах под нагрузкой: два параллельных Opus-запроса при $8-лимите — первый пройдёт, второй получит 402.

Это поведение Meridian до введения настраиваемого резервирования. Установка default_mandatory: "worst_case" восстанавливает строгий контроль расходов «как раньше».

Disabled — отключённое резервирование

Если enabled: false, RESERVE использует символическую сумму $1e-9 — настолько маленькую, что pre-check практически никогда не отклоняет запрос. Двухфазный протокол RESERVE/FINALIZE всё равно выполняется (это нужно, чтобы корректно учесть расход), но защитная функция RESERVE отключена.

Свойства:

  • Запросы блокируются только после того, как FINALIZE зафиксировал превышение лимита;
  • Между моментом пересечения лимита и моментом, когда об этом узнают все ноды кластера, может пройти несколько секунд (зависит от dumper-цикла, см. архитектуру бюджетов);
  • В этот период все приходящие запросы успевают пройти RESERVE и попасть к провайдеру — фактический перерасход потенциально неограничен;
  • Зато пропускная способность при больших бюджетах не ограничивается резервированием.

Когда какой режим выбирать

СценарийРекомендуемый режим
Стандартный шлюз для команд с бюджетами от десятков долларовFlat $0.05 (по умолчанию)
AI SaaS с подпиской и жёстким лимитом «не больше $X в месяц»Flat для базовых моделей + model_overrides: "claude-opus-*": "worst_case" для премиум
Запуск batch-пайплайна с дорогими моделями (Opus, GPT-4o) на ограниченном бюджетеWorst-case глобально или для премиум-моделей через model_overrides
Очень большой корпоративный бюджет (>$10K/мес) с акцентом на пропускную способностьDisabled — учёт через FINALIZE, без блокировки RESERVE
Тестовая среда с маленькими бюджетами под несколько десятков долларовWorst-case: иначе несколько Opus-запросов мгновенно израсходуют месячный тест-бюджет
Mixed workload: львиная доля — дешёвые модели, изредка дорогиеFlat + точечные worst_case override для дорогих моделей
Высоконагруженная инференс-платформа с тысячами параллельных запросов в секундуDisabled или Flat — worst-case съест параллелизм

Главный практический вопрос — что ценнее: строгая защита от перерасхода или высокий параллелизм под пиковой нагрузкой. Worst-case гарантирует первое ценой второго; flat-резерв и disabled — наоборот. Использование model_overrides позволяет получить оба свойства одновременно: строгая защита для дорогих моделей, высокий throughput для дешёвых.


Переопределения по моделям

Глобальная политика может быть точечно переопределена через model_overrides — карту glob-шаблон → значение резерва. Это нужно, когда основной политике подчиняются десятки моделей, а отдельные требуют другого режима.

{
  "governance": {
    "reservation": {
      "enabled":           true,
      "default_mandatory": 0.05,
      "model_overrides": {
        "claude-opus-*":   "worst_case",
        "gpt-4o-*":        "worst_case",
        "claude-haiku-*":  0.01
      }
    }
  }
}

Семантика и приоритеты:

  • Шаблоны — синтаксис filepath.Match (*, ?, [abc]);
  • При совпадении нескольких шаблонов выбирается самый специфичный: длиннее литеральный префикс → больше литеральных символов → раньше вставленный;
  • Литеральный ключ "claude-opus-4.7" сильнее, чем "claude-opus-*";
  • Если ни один шаблон не подошёл, применяется default_mandatory;
  • Значение 0 запрещено и для default-а, и для override-ов — для нулевого резерва используйте enabled: false.

Имя модели в model_overrides сопоставляется с именем модели как его видит резолвер — без префикса провайдера. То есть для запроса model: "fake/small" шаблоны проверяются против строки "small", не "fake/small". Это особенность Meridian; в логах и в каталоге pricing-моделей используется тот же формат.


Настройка через config.json

Сегодня политика резервирования настраивается только через config.json — отдельной страницы в веб-интерфейсе и API-эндпоинта для динамического переключения политики нет.

Будущие версии могут добавить настройку политики через UI и API. Текущая реализация — статическая: значения берутся из config.json при старте процесса.

Базовая конфигурация (значения по умолчанию)

Если секция governance.reservation не задана, шлюз применяет значения по умолчанию: flat $0.05 с включённым резервированием и без переопределений по моделям. Явно проставлять секцию нужно только если вы хотите отличающееся поведение.

{
  "governance": {
    "reservation": {
      "enabled": true,
      "default_mandatory": 0.05
    }
  }
}

Worst-case как политика по умолчанию

Восстанавливает поведение Meridian до введения настраиваемого резервирования: каждый запрос резервирует катало­говую верхнюю оценку.

{
  "governance": {
    "reservation": {
      "enabled": true,
      "default_mandatory": "worst_case"
    }
  }
}

Flat-резерв с точечным worst-case для премиум-моделей

Рекомендуемая конфигурация для AI SaaS-сервисов: высокий параллелизм по дешёвым моделям, строгая защита по дорогим.

{
  "governance": {
    "reservation": {
      "enabled": true,
      "default_mandatory": 0.05,
      "model_overrides": {
        "claude-opus-*": "worst_case",
        "claude-sonnet-*": "worst_case",
        "gpt-4o": "worst_case",
        "gpt-4o-mini": 0.02
      }
    }
  }
}

Отключённое резервирование (только пост-фактум учёт)

Для крупных потребителей с большими бюджетами и приоритетом на пропускную способность.

{
  "governance": {
    "reservation": {
      "enabled": false
    }
  }
}

При enabled: false поля default_mandatory и model_overrides игнорируются: RESERVE всегда использует символический $1e-9, а реальный учёт расхода выполняется через FINALIZE после ответа провайдера.

Описание полей

ПолеТипЗначение по умолчаниюОписание
enabledbooleantrueПри false RESERVE использует $1e-9, фактический учёт идёт только через FINALIZE
default_mandatorynumber | "worst_case"0.05Сумма резерва на запрос. Положительное число = flat $X. Строка "worst_case" = верхняя оценка по каталогу
model_overridesobject{}Карта glob-шаблон → значение резерва. Тот же тип значений, что у default_mandatory

Правила валидации:

  • enabled — boolean;
  • default_mandatory и значения в model_overrides — строго положительное число или строка "worst_case";
  • Значение 0 запрещено: для отключённого резерва используйте enabled: false;
  • Шаблоны в model_overrides — синтаксис filepath.Match; некорректный шаблон валится на старте.

Дальнейшее чтение

  • Бюджеты и лимиты — иерархия бюджетов, поток проверки, calendar-aligned сброс, rate limits;
  • Виртуальные ключи — основная точка применения governance, к которой привязаны бюджеты;
  • Кластеризация и Raft — как резервирования синхронизируются между нодами кластера через Raft (reservation_ttl, write-path через лидера группы).

Содержание