Тюнинг производительности

Оптимизация Meridian под высокую нагрузку — параллелизм, размеры очередей и memory-пулы.

Обзор

Meridian предоставляет три ключевых параметра, влияющих на пропускную способность, потребление памяти и поведение при обработке запросов:

ПараметрОбластьПо умолчаниюОписание
ConcurrencyНа провайдера1000Количество worker-горутин, обслуживающих запросы одновременно
Buffer SizeНа провайдера5000Максимум запросов в очереди до блокировки/отказа
Initial Pool SizeГлобально5000Предварительно выделенные объекты в sync-пулах для снижения нагрузки на GC

Значения по умолчанию подходят для большинства production-развёртываний с нагрузкой до ~5000 RPS. Для бо́льших нагрузок или ограниченных окружений тонкая настройка этих параметров заметно улучшает производительность.


Как работают параметры

Concurrency (на провайдера)

Что делает: управляет двумя аспектами производительности провайдера:

  1. Worker-горутины — число горутин, обслуживающих запросы для каждого провайдера. Каждый воркер забирает запросы из очереди провайдера и выполняет их против его API.
  2. Прогрев пула провайдера — заранее выделяет provider-specific объекты ответов (например, AnthropicMessageResponse, OpenAIResponse) в sync-пулах, чтобы снизить аллокации в рантайме.

Эффект:

  • Бо́льшая concurrency — больше параллельных запросов к провайдеру, выше throughput, больше предвыделенных объектов;
  • Меньшая concurrency — меньше параллельных запросов, ниже потребление ресурсов, меньше риск упереться в rate limit провайдера.

По умолчанию: 1000 воркеров на провайдера.

{
    "providers": {
        "openai": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 100,
                "buffer_size": 500
            }
        }
    }
}

Buffer Size (на провайдера)

Что делает: задаёт ёмкость буферизированного канала (очереди) у каждого провайдера. Входящие запросы складываются туда, прежде чем попасть к воркерам.

Эффект:

  • Больший буфер — больше запросов укладывается во время всплесков нагрузки; лучше переживает burst-трафик;
  • Меньший буфер — меньше памяти, более быстрая backpressure-сигнализация клиентам.

По умолчанию: 5000 запросов на очередь провайдера.

Поведение при переполнении управляется флагом drop_excess_requests:

  • false (по умолчанию) — новые запросы блокируются до освобождения места;
  • true — новые запросы немедленно отклоняются с ошибкой при переполнении.

Ограничение. Buffer size должен быть не меньше concurrency. Если concurrency > buffer_size, инициализация провайдера завершится ошибкой.

Initial Pool Size (глобально)

Что делает: задаёт количество объектов, предварительно выделяемых во внутренних sync-пулах Meridian при старте. Пулы переиспользуют объекты и снижают нагрузку на сборщик мусора.

Что находится в пулах:

  • сообщения каналов (request wrappers);
  • response-каналы;
  • error-каналы;
  • stream-каналы;
  • plugin pipelines;
  • request-объекты.

Эффект:

  • Большее значение — меньше GC-давления на пиках, более стабильная latency, больше памяти на старте;
  • Меньшее значение — меньше начальных аллокаций; под нагрузкой возможны дополнительные runtime-аллокации.

По умолчанию: 5000 объектов на пул.

{
    "config": {
        "initial_pool_size": 10000,
        "drop_excess_requests": false
    }
}

Рекомендации по подбору значений

Concurrency и Buffer Size (на провайдера)

Настраивайте на каждого провайдера в зависимости от ожидаемой нагрузки:

Нагрузка на провайдераConcurrencyBuffer Size
100 RPS100150
500 RPS500750
1000 RPS10001500
2500 RPS25003750
5000 RPS50007500
10000 RPS1000015000

Пример. Если ожидается 2000 RPS к OpenAI и 500 RPS к Anthropic, настройте OpenAI с concurrency: 2000, buffer_size: 3000, а Anthropic — с concurrency: 500, buffer_size: 750.

Формула:

concurrency = ожидаемое_RPS
buffer_size = 1.5 × ожидаемое_RPS

Это соотношение даёт:

  • запас очереди для всплесков трафика;
  • работу воркеров без простоев;
  • срабатывание backpressure до исчерпания памяти.

Initial Pool Size (глобально)

Подбирается на основе суммарной нагрузки по всем провайдерам:

Суммарный RPSInitial Pool SizeОценка памяти
100150~50 МБ
500750~100 МБ
10001500~200 МБ
25003750~400 МБ
50007500~800 МБ
1000015000~1,5 ГБ

Оценки памяти приблизительны и зависят от размеров запросов и ответов, числа провайдеров и плагинов. Контролируйте фактическое потребление в своём окружении.

Формула:

initial_pool_size = 1.5 × суммарное_RPS

Дополнительно убедитесь, что:

initial_pool_size >= max(buffer_size по всем провайдерам)

Так пулы будут прогреты до пиковой глубины очередей и не потребуют runtime-аллокаций.


Multi-node-развёртывания

Если несколько инстансов Meridian работают за балансировщиком, поделите параметры на число узлов, исходя из суммарного RPS.

Формула

Per-Node Concurrency      = Total Concurrency      / Number of Nodes
Per-Node Buffer Size      = Total Buffer Size      / Number of Nodes
Per-Node Initial Pool     = Total Initial Pool     / Number of Nodes

Пример: 10 000 RPS на 4 узлах

Суммарная ёмкость (по всем 4 узлам):

  • Total RPS: 10 000;
  • Per-node RPS: ~2500 на узел.

Параметры одного узла, если его одного хватит на 10 000 RPS:

  • Concurrency: 10000;
  • Buffer Size: 15000;
  • Initial Pool Size: 15000.

Параметры на узел при 4 узлах и общем 10 000 RPS:

ПараметрСуммарноНа узел (4 узла)
Concurrency100002500
Buffer Size150003750
Initial Pool Size150003750
{
    "config": {
        "initial_pool_size": 3750,
        "drop_excess_requests": false
    },
    "providers": {
        "openai": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 2500,
                "buffer_size": 3750
            }
        },
        "anthropic": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 2500,
                "buffer_size": 3750
            }
        }
    }
}

Kubernetes HPA. При использовании horizontal pod autoscaler настройки следует задавать под минимальное число реплик. По мере scale up каждый узел берёт меньшую долю трафика. Удобно использовать переменные окружения или ConfigMap, чтобы динамически подстраивать параметры под текущее число реплик.


Подбор под конкретного провайдера

У провайдеров разные rate limit'ы и характеристики латентности. Настраивайте параметры независимо.

Лимиты провайдеров

ПровайдерТипичные лимитыРекомендованная concurrencyЗамечания
OpenAI500–10 000 RPM (зависит от tier)100–500Высокие тарифы поддерживают бо́льшую concurrency
Anthropic1000–4000 RPM (зависит от tier)50–200Более консервативные лимиты
BedrockНа каждую модель100–300См. квоты AWS под ваш аккаунт
Azure OpenAIНа deployment100–500Настраивается на уровне deployment
Vertex AIНа каждую модель100–300См. квоты GCP
GroqОчень высокая пропускная способность500–1000Спроектирован под высокую concurrency
OllamaЛокальные ресурсы10–50Ограничен локальным GPU/CPU

Пример: смешанная конфигурация

{
    "providers": {
        "openai": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 200,
                "buffer_size": 1000
            }
        },
        "anthropic": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 100,
                "buffer_size": 500
            }
        },
        "groq": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 500,
                "buffer_size": 2500
            }
        },
        "ollama": {
            "keys": [...],
            "concurrency_and_buffer_size": {
                "concurrency": 20,
                "buffer_size": 100
            }
        }
    }
}

Поведение при переполнении очереди

Когда очередь провайдера достигает ёмкости, поведение Meridian определяется флагом drop_excess_requests.

Режим блокировки (по умолчанию)

{
    "config": {
        "drop_excess_requests": false
    }
}
  • Новые запросы ждут освобождения места.
  • Запросы не теряются.
  • Может вырасти latency на пиках.
  • Подходит для критичных нагрузок, где важен каждый запрос.

Режим отказа

{
    "config": {
        "drop_excess_requests": true
    }
}
  • Новые запросы немедленно отклоняются при переполнении.
  • Возвращается ошибка: "request dropped: queue is full".
  • Сохраняется стабильная latency для принятых запросов.
  • Подходит для real-time сценариев, где устаревшие запросы бесполезны.

Best practice. Для production-нагрузок используйте drop_excess_requests: true с буфером 1.5× concurrency. Это предотвращает исчерпание памяти и при этом нормально переживает разумные всплески трафика.


Мониторинг и диагностика

Ключевые метрики

МетрикаЗдоровый диапазонДействие при превышении
Глубина очереди< 50 % от buffer_sizeУвеличить буфер или concurrency
Latency p99< 2× от среднегоПроверить rate limit'ы провайдера
Dropped requests0Увеличить buffer_size
Потребление памятиСтабильноУменьшить размер пулов и буфера
Число горутинСтабильноПроверить goroutine-leak'и

Health-эндпоинт

Шлюз отдаёт health- и metrics-эндпоинты:

# Health-проверка
curl http://localhost:8080/health

# Prometheus-метрики
curl http://localhost:8080/metrics

Краткая сводка лучших практик

  • Начинайте консервативно. Стартуйте с меньших значений и масштабируйте по мере необходимости. Перерасход ресурсов — пустая трата.
  • Постоянно мониторьте. Глубина очередей, latency, ошибки — настраивайте параметры по реальным паттернам трафика.
  • Соотносите с лимитами провайдеров. Concurrency не должна превышать rate limit провайдера: больше — значит просто упираться в rate limit.
  • Закладывайте на всплески. Buffer size = 1.5× concurrency покрывает всплески без потерь запросов.

Шпаргалка

// Формула
concurrency       = ожидаемое RPS
buffer_size       = 1.5 × ожидаемое RPS
initial_pool_size = 1.5 × суммарное RPS (по всем провайдерам)

// Пример: 500 RPS на провайдера, 2 провайдера (1000 RPS суммарно)
concurrency: 500, buffer_size: 750, initial_pool_size: 1500

// Пример: 2000 RPS на провайдера, 3 провайдера (6000 RPS суммарно)
concurrency: 2000, buffer_size: 3000, initial_pool_size: 9000

// Multi-node формула
per_node_value = total_value / number_of_nodes

Связанные документы

Содержание