Tropic Host

LiteLLM Proxy + Langfuse на KVM VPS: Единый шлюз к 100+ LLM, кэширование, балансировка квот и трекинг затрат

37 мин чтения
Tropic

Краткий вывод: Для стабильного продакшен-развертывания стека LiteLLM Proxy и Langfuse в Docker Compose необходим KVM-сервер минимум с 4 vCPU (%st = 0.0%), 8 ГБ RAM (с жестким лимитированием памяти ClickHouse и PostgreSQL через cgroups v2) и 80 ГБ NVMe (PCIe 4.0, от 15 000 IOPS 4K QD1) во избежание I/O-блокировок при слиянии аналитических партиций и ротации WAL. Сетевой контур требует выделенного аплинка от 1 Гбит/с с алгоритмом TCP BBR для удержания p99 latency сквозного SSE-стриминга (Server-Sent Events) в пределах 15 мс и непрерывного gRPC/OTel-экспорта трейсов.


Содержание

  1. Архитектура корпоративного LLM-шлюза: почему прямой доступ к API не работает в продакшне
  2. Аппаратный сайзинг KVM VPS: расчет RAM и NVMe под PostgreSQL, Redis и ClickHouse
  3. Развертывание полного стека LiteLLM + Langfuse в Docker Compose
  4. Настройка семантического кэширования и автоматического фейловера между провайдерами
  5. Управление бюджетами команд, квоты токенов и маскирование PII-данных
  6. Отказоустойчивость, бэкапы баз данных и регламент аварийного восстановления
  7. Часто задаваемые вопросы (FAQ)

Архитектура корпоративного LLM-шлюза: почему прямой доступ к API не работает в продакшне

Прямое обращение распределенных микросервисов к публичным эндпоинтам внешних провайдеров (OpenAI, Anthropic, Google Vertex AI, Mistral) — классический антипаттерн, приводящий к каскадным сбоям инфраструктуры при первом же непрогнозируемом всплеске трафика. В архитектуре без промежуточного балансирующего слоя каждый экземпляр бэкенда самостоятельно инициирует TLS-сессии, управляет собственным пулом соединений и изолированно обрабатывает ответы upstream API. На масштабе от нескольких сотен запросов в секунду (RPS) эта схема порождает комплексную деградацию: лавинообразные отказы по таймаутам, исчерпание пула сокетов операционной системы, неконтролируемый перерасход бюджета и полную потерю контроля над передаваемыми данными.

Анатомия деградации: каскадные отказы и исчерпание сетевого стека

Ключевой фактор нестабильности прямого доступа — разница во времени ответа (latency profile) классических микросервисов и генеративных моделей. Если стандартный CRUD-запрос к СУБД или кэшу выполняется за 2–15 мс, то генерация ответа большой языковой моделью (LLM) с потоковой передачей токенов (Server-Sent Events) удерживает активное TCP-соединение от 1,5 до 45 секунд.

При резком росте нагрузки прямое обращение вызывает следующие сетевые и архитектурные патологии:

  1. Лавина повторных попыток (Retry Storms) и HTTP 429 (Too Many Requests): Внешние провайдеры лимитируют трафик по двум жестким метрикам — RPM (Requests Per Minute) и TPM (Tokens Per Minute). Когда десяток независимых воркеров синхронно получают статус 429, их встроенные механизмы повтора (даже с экспоненциальной задержкой Exponential Backoff без глобального джиттера) синхронизируются. Возникает проблема Thundering Herd: запросы циклически перегружают API, исчерпывают скользящее окно квот провайдера и переводят микросервисы в перманентное состояние блокировки.
  2. Исчерпание локальных сокетов и деградация TCP-стека Linux: Каждый HTTP-запрос во внешнюю сеть требует выделения эфемерного порта из пула net.ipv4.ip_local_port_range. При удержании соединений десятками секунд и аварийном закрытии по таймауту сокеты переходят в состояние TIME_WAIT (по умолчанию удерживаются ядром 60 секунд). При отсутствии централизованного пулинга на хосте наступает нехватка свободных дескрипторов:
# Диагностика переполнения сокетов в состоянии TIME_WAIT и TCP backoff
ss -s
cat /proc/sys/net/ipv4/tcp_tw_reuse
netstat -s | grep -E "overflows|dropped"
  1. Отсутствие Circuit Breaker и каскадная блокировка пула потоков: В синхронных и асинхронных рантаймах (Node.js event loop, Python AsyncIO, пул потоков Gunicorn/Uvicorn) длительное ожидание байтов генерации исчерпывает воркеры. При падении upstream-провайдера (Outage) сервисы продолжают ждать ответа вплоть до истечения TCP read timeout (часто настроенного на 60–120 секунд), вызывая глобальный паралич всей клиентской очереди.

Утечки бюджетов, отсутствие аудита и риски утечки PII

Прямая раздача сырых токенов авторизации (OPENAI_API_KEY, ANTHROPIC_API_KEY) воркерам распределенной системы полностью разрушает контур безопасности:

  • Отсутствие гранулярного контроля бюджетов (Hard/Soft Limits): Провайдеры предоставляют биллинг на уровне организации или глобального API-ключа. Неконтролируемый цикл в ReAct-агенте (зацикливание автономного агента при вызове инструментов) способен за несколько часов сжечь месячный бюджет проекта, не встретив локального ограничителя.
  • Слепое пятно наблюдаемости (Zero Observability): Логирование запросов в прикладной код микросервиса либо перегружает хранилище логов сырыми мегабайтами контекста, либо не ведется вовсе. Инженеры лишены возможности оценить реальную задержку до первого токена (Time to First Token, TTFT), распределение затрат по командам или проектам, а также процент кэшируемых промптов.
  • Нарушение требований безопасности и PII (Personally Identifiable Information): При прямом взаимодействии невозможно внедрить централизованную валидацию и маскирование чувствительных данных (номера карт, паспортные данные, API-токены внутренней инфраструктуры) до их отправки во внешнее облако.

Сравнительный анализ архитектур доступа к моделям

Для обеспечения отказоустойчивости корпоративного уровня прямое обращение заменяется выделенным слоем абстракции — шлюзом на базе связки LiteLLM Proxy, Langfuse и Docker, развернутым на независимом KVM VPS.

Критерий / Параметр Прямой доступ (Direct-to-API) SaaS-шлюзы (Portkey / Cloudflare AI) LiteLLM Proxy + Langfuse в Docker на VPS
Сетевая топология Распределенная, неуправляемая Централизованная (Vendor Cloud) Полный контроль (Single-tenant Private Cloud)
Задержка проксирования (p99 overhead) 0 мс (но частые таймауты) +40–120 мс (маршрутизация через чужую CDN) < 3–8 мс (локальный высокоскоростной KVM-узел)
Единый API-интерфейс Отсутствует (разные SDK под каждого вендора) Присутствует Полная совместимость со спецификацией OpenAI API
Динамический Failover / Fallbacks Ручная логика в коде каждого микросервиса Ограничено политиками платформы Автоматическое переключение провайдеров за < 100 мс
Кэширование ответов Отсутствует В облаке сервиса Локальный Redis (Exact & Semantic Caching)
Аудит, трейсинг и токены Разрозненные логи приложений Внешний вендор (риск комплаенса) Полный аудит в self-hosted Langfuse (PostgreSQL/ClickHouse)
Ограничение бюджетов (Rate Limit / Spend) Только глобальный баланс провайдера Зависит от тарифа SaaS Гранулярно: по ключам, командам, моделям, секундам
Безопасность данных Ключи размазаны по конфигурациям Ключи хранятся у третьей стороны Ключи изолированы в памяти защищенного контейнера

Функциональная роль стека: LiteLLM Proxy + Langfuse в Docker

Архитектурный шаблон корпоративного шлюза выстраивается на разделении зон ответственности между маршрутизацией, кэшированием и глубокой аналитикой:

[Микросервисы / Агенты] 
         │ (OpenAI-compatible: /v1/chat/completions)
         ▼
┌────────────────────────────────────────────────────────┐
│ LiteLLM Proxy (Контейнер Docker на KVM VPS)            │
│ ├─ Virtual API Keys (RBAC, Rate Limits, Budget Caps)   │
│ ├─ Router Engine (Load Balancing, Cooldowns, Fallback) │
│ └─ In-Memory / Redis Cache (Exact Match, TTL)          │
└────────┬──────────────────────────────────────┬────────┘
         │ (Асинхронный экспорт телеметрии)     │ (Оптимизированный HTTPS)
         ▼                                      ▼
┌──────────────────────────────┐       ┌────────────────────────┐
│ Langfuse (Docker)            │       │ Upstream LLM Providers │
│ ├─ Traces, Spans, Generations│       │ ├─ OpenAI (Primary)    │
│ ├─ Cost Tracking & Token Eval│       │ ├─ Anthropic (Fallback)│
│ └─ PostgreSQL / ClickHouse   │       │ └─ Self-hosted vLLM    │
└──────────────────────────────┘       └────────────────────────┘
  1. LiteLLM Proxy как унифицированный фасад: Предоставляет микросервисам стандартный эндпоинт /v1/chat/completions. Сервису-клиенту не требуется внедрять SDK под каждую модель: достаточно изменить OPENAI_BASE_URL на внутренний адрес прокси. Шлюз транслирует форматы на лету: преобразует сообщения OpenAI в синтаксис Anthropic Messages API или Google Gemini, нормализуя структуры ошибок, расчет использованных токенов и механизмы стриминга.
  2. Динамический роутинг, пулинг и Circuit Breaker: В конфигурации шлюза задаются списки моделей с весами и приоритетами. При фиксации ошибки 429 или 5xx от OpenAI прокси мгновенно отправляет исходный запрос в модель аналогичного класса от Anthropic (например, с gpt-4o на claude-3-5-sonnet-20241022), переводя упавший эндпоинт в режим Cooldown на заданный интервал (например, 30 секунд). Клиентское приложение не получает ошибки и не прерывает пайплайн.
  3. Глубокий трейсинг через Langfuse: Вся входящая и исходящая полезная нагрузка асинхронно передается в коллектор Langfuse. Это фиксирует граф выполнения (execution graph) каждого запроса: стоимость в долларах США с точностью до десятых долей цента, задержку генерации, количество Prompt/Completion токенов, контекст сессий и качество генерации для последующей разметки.

Инженерные требования к инфраструктуре хостинга шлюза

Развертывание стека LiteLLM Proxy и Langfuse в Docker на виртуальном сервере накладывает жесткие требования на базовое железо и сетевой стек ядра. Проксирующий узел становится критической точкой отказа (Single Point of Failure) всей инфраструктуры искусственного интеллекта компании. Любая задержка внутри хоста напрямую увеличивает p99 latency всей цепочки вызовов.

Для продакшн-эксплуатации неприменимы VPS с разделяемыми ресурсами (overselling). Если на физической ноде гипервизора процессорное время размазывается между соседями, метрика CPU Steal Time (%st в выводе top или vmstat) начинает скакать до 5–15%. Для асинхронного Event Loop LiteLLM (построенного на FastAPI/Uvicorn) и баз данных Langfuse это приводит к резким микрофризам, блокировке обработки потоков SSE и экспоненциальному росту p99 задержки:

# Проверка наличия оверселлинга CPU и лагов ввода-вывода дисковой подсистемы
vmstat 1 5
# Анализируем колонку st (Steal Time) — в продакшне она обязана быть строго 0.0%
# Анализируем колонку wa (Wait IO) — не должна превышать 0.1-0.2%

В качестве надежного аппаратного стандарта выступают KVM-инстансы облачной платформы tropic.host. Полная аппаратная виртуализация KVM гарантирует отсутствие оверселлинга ресурсов с гарантированным показателем %st = 0.0% на высокочастотных ядрах AMD EPYC и Ryzen 9. Это исключает паразитные задержки контекстных переключений операционной системы при пиковых нагрузках на шлюз.

Вторым критическим фактором является дисковая подсистема. База данных Langfuse (PostgreSQL и ClickHouse) непрерывно фиксирует детальные трейсы, спаны и метаданные сотен параллельных стримов. Медленный виртуальный диск быстро исчерпывает лимиты операций ввода-вывода (IOPS), блокируя сброс буферов на диск (fsync). Серверные накопители NVMe корпоративного класса с интерфейсом PCIe 4.0 на платформе tropic.host обеспечивают производительность на случайных операциях 4K QD1 свыше 50 000 IOPS без троттлинга, гарантируя мгновенную запись телеметрии без деградации сетевых очередей шлюза.

Наконец, сетевая связность узла определяет скорость взаимодействия с внешними API: аплинки пропускной способностью 1–10 Гбит/с с активированным по умолчанию алгоритмом контроля перегрузок TCP BBR и прямой BGP-маршрутизацией на ключевых европейских и азиатских точках обмена трафиком (Франкфурт, Амстердам, Сингапур) минимизируют сетевой RTT (Round Trip Time) до дата-центров провайдеров LLM. Наличие выделенного статического IPv4 без репутационной спам-истории защищает шлюз от блокировок со стороны систем защиты Cloudflare, стоящих перед API большинства поставщиков нейросетей.

Системный тюнинг ядра Linux для шлюза высокой плотности

Перед запуском Docker-демона операционная система хоста настраивается на удержание десятков тысяч долгоживущих TCP-сессий без утечек памяти. Конфигурация /etc/sysctl.d/99-llm-gateway.conf:

# Расширение диапазона эфемерных портов для исходящих подключений к LLM API
net.ipv4.ip_local_port_range = 10240 65535

# Повторное использование сокетов в состоянии TIME_WAIT для исходящих TCP-соединений
net.ipv4.tcp_tw_reuse = 1

# Сокращение времени удержания сокета в FIN-WAIT-2 для быстрой очистки ресурсов
net.ipv4.tcp_fin_timeout = 15

# Максимальный размер очереди входящих соединений на сокете
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# Увеличение буферов приема и передачи TCP для высокоскоростного стриминга
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Включение алгоритма BBR для минимизации задержек в нестабильных каналах
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# Лимиты открытых файловых дескрипторов на системном уровне
fs.file-max = 2097152

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

sysctl --system

На уровне системных ограничений пользователей и демона Docker (/etc/security/limits.d/99-nofile.conf) задаются жесткие лимиты файловых дескрипторов:

* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576

Для изоляции контейнеров в подсистеме cgroups v2 лимиты ресурсов фиксируются на уровне декларации Docker Compose, гарантируя, что утечка памяти в воркерах Langfuse или аналитическом модуле не спровоцирует аварийное срабатывание Linux OOM Killer против критически важного процесса маршрутизатора LiteLLM:

# Фрагмент изоляции ресурсов в docker-compose.yml через cgroups v2
services:
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    restart: always
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 4096M
        reservations:
          cpus: '2.0'
          memory: 2048M
    ulimits:
      nofile:
        soft: 65535
        hard: 65535

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

Аппаратный сайзинг KVM VPS: расчет RAM и NVMe под PostgreSQL, Redis и ClickHouse

Развертывание production-стека на базе LiteLLM Proxy и Langfuse v3 в Docker на единой ноде KVM VPS формирует смешанный профиль нагрузки, где высокочастотный сетевой I/O прокси-шлюза напрямую упирается в пропускную способность транзакционного и аналитического хранилищ. Архитектура Langfuse v3 разделяет состояние системы на три независимых контура: реляционные метаданные (PostgreSQL), оперативный кэш и очереди rate-limiting (Redis), а также колоночное хранилище трейсов, спанов и метрик токенизации (ClickHouse). Ошибки в сайзинге оперативной памяти и дисковой подсистемы на данном уровне приводят к каскадным блокировкам, лавинообразному росту p99 latency прокси-запросов и неконтролируемому срабатыванию Linux OOM Killer.


Профиль потребления RAM: декомпозиция компонентов стека

При расчете оперативной памяти необходимо учитывать не только статические лимиты контейнеров, но и накладные расходы ядра Linux на буферизацию ввода-вывода (Page Cache), поддержание TCP-сокетов и поведение аллокаторов памяти (jemalloc vs glibc malloc).

┌─────────────────────────────────────────────────────────────────────────┐
│                      Оперативная память хоста (RAM)                     │
├─────────────────┬─────────────────┬──────────────────┬──────────────────┤
│ LiteLLM Proxy   │ Redis 7.x       │ PostgreSQL 16    │ ClickHouse Server│
│ 1.5 – 3.0 ГБ    │ 1.0 – 2.0 ГБ    │ 2.0 – 4.0 ГБ     │ 4.0 – 8.0 ГБ     │
│ (AsyncIO, SSE)  │ (TTL, Sliding)  │ (shared_buffers) │ (MergeTree, RAM) │
├─────────────────┴─────────────────┴──────────────────┴──────────────────┤
│ OS Kernel, Page Cache, Docker Engine, Prom/Node Exporter: 2.0 – 3.0 ГБ  │
└─────────────────────────────────────────────────────────────────────────┘

1. LiteLLM Proxy (Python/FastAPI/Uvicorn)

  • Потребление на воркер: Базовый оверхед одного процесса Uvicorn составляет 180–250 МБ. При включенном стриминге Server-Sent Events (SSE) каждый активный TCP-сокет удерживает в памяти буфер чанков генерации, контекст авторизации и состояние сессии.
  • Расчет: Для обработки 300–500 RPS при пуле из 4 воркеров суммарная аллокация процесса составляет: $$\text{RAM}_{\text{LiteLLM}} = (\text{Workers} \times 250\,\text{МБ}) + (\text{Concurrent Connections} \times 1.8\,\text{МБ}) \approx 2.0 - 3.5\,\text{ГБ}$$

2. Redis 7.x (Rate Limiting, Routing Fallbacks, Кэш)

  • Назначение: Реализация алгоритмов Token Bucket / Leaky Bucket для клиентов LiteLLM, временное хранение эмбеддингов и координация очередей фоновых воркеров Langfuse.
  • Накладные расходы: 100 000 активных счетчиков с TTL занимают около 85–120 МБ. Однако подсистема персистентности (RDB Snapshotting) требует резервирования памяти под Copy-on-Write (CoW). Если Redis аллоцирует 1 ГБ данных, во время вызова системного вызова fork() ядру Linux требуется запас свободных страниц, эквивалентный объему модифицируемых данных.
  • Параметр ядра: В обязательном порядке задается эвристика выделения памяти: bash sysctl -w vm.overcommit_memory=1 При значении 0 сброс RDB на диск завершится аварийной ошибкой Can't save in background: fork: Cannot allocate memory.

3. PostgreSQL 16 (Langfuse Core DB & LiteLLM Configs)

  • Назначение: Хранение пользовательских аккаунтов, проектов, API-ключей, конфигураций моделей, промптов и метаданных сессий.
  • Ключевые директивы конфигурации (postgresql.conf):
  • shared_buffers: Выделяется строго 25% от доступной PostgreSQL памяти. На ноде с 16 ГБ RAM под контейнер Postgres резервируется 4 ГБ, соответственно shared_buffers = 1GB.
  • work_mem: Задается из расчета 16–32 МБ на соединение. При пуле max_connections = 100: $$\text{Peak Work Mem} = 100 \times 32\,\text{МБ} = 3.2\,\text{ГБ}$$ Завышение этого параметра приведет к мгновенному падению контейнера по OOM при параллельном выполнении сложных выборок аналитики с сортировками (ORDER BY, HashAggregate).
  • maintenance_work_mem = 256MB: Ускоряет вакуумизацию и построение индексов B-Tree.
  • effective_cache_size: Задается на уровне 50–75% от лимита контейнера, информируя планировщик запросов о вероятности нахождения данных в Linux Page Cache.

4. ClickHouse Server (Langfuse v3 Analytical Engine)

  • Назначение: Прием высокоинтенсивного потока спанов, трейсов и скоринга. ClickHouse использует архитектуру векторизованной обработки данных, требующую массивных блоков памяти под сортировку и слияние кусков (MergeTree parts).
  • Специфика: Минимальный порог стабильной работы ClickHouse под нагрузкой составляет 4 ГБ RAM. При генерации отчетов в веб-интерфейсе Langfuse запрос к несжатым колонкам может мгновенно утилизировать весь доступный объем.
  • Защита в users.xml: xml <profiles> <default> <!-- Жесткое ограничение потребления памяти на один аналитический запрос --> <max_memory_usage>3221225472</max_memory_usage> <!-- Общий лимит памяти для всех параллельных запросов инстанса --> <max_server_memory_usage>6442450944</max_server_memory_usage> <use_uncompressed_cache>0</use_uncompressed_cache> </default> </profiles>

Дисковая подсистема: расчет IOPS, пропускной способности и Write Amplification

Стек litellm proxy vps langfuse docker создает преимущественно нагрузку типа Write-Heavy. Каждая операция взаимодействия с LLM (особенно длинные вызовы агентов с tool calls и цепочками рассуждений) генерирует каскад записей: 1. LiteLLM сбрасывает аудит транзакции и списание бюджета в Redis и Postgres. 2. Langfuse Worker принимает пакеты событий через OpenTelemetry Collector / REST API. 3. ClickHouse непрерывно пишет микробатчи во временные партиции движка ReplacingMergeTree, параллельно выполняя фоновые операции дедупликации и сжатия (OPTIMIZE / BACKGROUND MERGE).

                    Поток записи (Write Path)

 Client Request ──► LiteLLM Proxy ──► LLM Provider (Upstream)
                         │
                         ├──► Redis (AOF / fsync everysec)
                         ├──► PostgreSQL (WAL Append ──► sync)
                         └──► Langfuse Worker
                                   │
                                   ▼ [Micro-batches 500-1000 spans]
                              ClickHouse (Part Mutations / fsync)

Расчет дискового пространства под трейсинг

  • Средний размер одного завершенного трейса (включая системный промпт, контекст, ответ модели, метаданные и 3–5 дочерних спанов) в формате JSON составляет 12–35 КБ.
  • В ClickHouse данные сохраняются в сжатом колоночном виде (алгоритмы LZ4/ZSTD). Коэффициент сжатия для текстовых токенов и структурированных метаданных составляет в среднем 4.5x – 6x.
  • Реальный дисковый след одного трейса в ClickHouse: $$\text{Size}_{\text{trace}} \approx \frac{25\,\text{КБ}}{5} = 5\,\text{КБ}$$
  • При потоке 1 000 000 запросов в сутки: $$\text{Volume}_{\text{daily}} = 1\,000\,000 \times 5\,\text{КБ} \approx 4.76\,\text{ГБ/сутки}$$
  • С учетом индексов ClickHouse, журналов транзакций PostgreSQL (WAL), бинарных логов Docker и сохранения данных за 90 дней (retention period): $$\text{Storage}_{90\text{d}} = (4.76 \times 90) \times 1.35\,(\text{WAL, Overhead}) \approx 578\,\text{ГБ}$$

Требования к задержкам и IOPS (Критичность NVMe PCIe 4.0)

Механические диски (HDD) и виртуализированные сетевые диски (SATA/Ceph с оверселлингом) категорически непригодны для данного стека. Синхронизация транзакций PostgreSQL (wal_sync_method = fdatasync) и сброс очередей ClickHouse создают постоянную случайную запись блоками 4K.

Если дисковая подсистема хоста не способна обеспечить задержку подтверждения записи (fsync latency) на уровне менее 200–500 микросекунд (мкс), в Uvicorn и пуле воркеров Langfuse возникает лавинообразное блокирование потоков (Backpressure). Время ожидания свободных соединений пула базы данных превышает таймауты, в результате чего p99 latency прокси-сервера деградирует с типовых 120 мс до 4 000–8 000 мс, вызывая сброс клиентских коннектов по 504 Gateway Timeout.

Верификация дисковой подсистемы KVM VPS выполняется утилитой fio непосредственно внутри рабочего каталога данных Docker до старта контейнеров:

# Синхронный стресс-тест латентности одиночной случайной записи (4K QD1, fsync=1)
fio --name=fsync_latency_test \
    --filename=/var/lib/docker/volumes/io_test.bin \
    --size=2G \
    --rw=randwrite \
    --bs=4k \
    --ioengine=sync \
    --fsync=1 \
    --numjobs=1 \
    --direct=1 \
    --group_reporting \
    --runtime=30

Критерии прохождения теста для production-нагрузки: * clat (completion latency) 99.00th: < 0.40 мс (400 мкс). * IOPS: не менее 2 500 – 4 000 при единичной глубине очереди (QD=1) с обязательным сбросом кэша на каждом блоке (fsync=1). * Тест на случайное чтение/запись (--ioengine=libaio --iodepth=32 --rw=randrw) обязан демонстрировать устойчивую производительность свыше 45 000 – 50 000 IOPS без термического троттлинга.

В качестве аппаратного стандарта для исключения дисковых заторов в проектах высокой доступности используется инфраструктура облачной платформы tropic.host. Вычислительные узлы провайдера строятся исключительно на базе серверных NVMe-накопителей корпоративного класса с интерфейсом PCIe 4.0, обеспечивающих аппаратную защиту от потери питания (Power Loss Protection, PLP) и устойчивую скорость чтения/записи на случайных блоках 4K QD1 свыше 50 000 IOPS. Это гарантирует, что даже в момент выполнения фоновых агрегаций в ClickHouse и одновременного сброса WAL-буферов PostgreSQL задержка системного вызова fsync остается стабильно ниже 180 мкс.


Тюнинг параметров ядра Linux под СУБД-стек

Для предотвращения деградации производительности на уровне хостовой операционной системы Ubuntu 24.04 LTS / Debian 12 на ноде KVM VPS вносятся специализированные корректировки в /etc/sysctl.d/99-database-storage.conf:

# Агрессивное удержание памяти от ухода в Swap при наличии физической RAM
vm.swappiness = 10

# Разрешение аллокации памяти без блокировок под форки Redis
vm.overcommit_memory = 1

# Сглаживание пиков сброса "грязных" страниц на NVMe накопитель.
# Начинать фоновый сброс при накоплении 5% модифицированной памяти
vm.dirty_background_ratio = 5
# Блокировать пишущие процессы при достижении 10% до сброса страниц
vm.dirty_ratio = 10

# Увеличение максимального числа отображаемых областей памяти для ClickHouse/Jemalloc
vm.max_map_count = 1048576

# Лимиты системных очередей сокетов под высокочастотный межконтейнерный трафик
net.core.somaxconn = 65535

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

sysctl --system

Изоляция ресурсов контейнеров через cgroups v2

Развертывание баз данных и прокси-шлюза в едином контуре требует строгой изоляции вычислительных ресурсов. Отсутствие ограничений в docker-compose.yml приведет к тому, что неоптимизированный аналитический запрос в Langfuse вызовет скачок потребления памяти в ClickHouse, спровоцировав ядро Linux на завершение процесса litellm через OOM Killer.

В подсистеме cgroups v2 используются директивы memory.high (мягкий троттлинг) и memory.max (жесткий лимит). В синтаксисе Docker Compose они выражаются через параметры reservations и limits:

version: '3.8'

services:
  # Проксирующий шлюз: критичен к latency, высокий приоритет CPU
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    restart: always
    deploy:
      resources:
        limits:
          cpus: '4.00'
          memory: 4096M
        reservations:
          cpus: '2.00'
          memory: 2048M
    # Защита от OOM Killer: снижение вероятности убийства процесса
    oom_score_adj: -500

  # Очереди и кэш: фиксированная память, исключение вытеснения в swap
  redis:
    image: redis:7.2-alpine
    restart: always
    command: >
      redis-server 
      --maxmemory 1536mb 
      --maxmemory-policy volatile-lru 
      --appendonly yes 
      --appendfsync everysec
    deploy:
      resources:
        limits:
          cpus: '2.00'
          memory: 2048M
        reservations:
          cpus: '0.50'
          memory: 1024M
    oom_score_adj: -800

  # Транзакционная база: гарантированный пул shared_buffers
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_DB: langfuse
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    command: >
      postgres
      -c shared_buffers=1024MB
      -c effective_cache_size=3072MB
      -c work_mem=32MB
      -c maintenance_work_mem=256MB
      -c min_wal_size=1GB
      -c max_wal_size=4GB
      -c checkpoint_completion_target=0.9
      -c wal_buffers=16MB
    deploy:
      resources:
        limits:
          cpus: '4.00'
          memory: 4096M
        reservations:
          cpus: '1.00'
          memory: 2048M
    oom_score_adj: -900

  # Аналитическое колоночное хранилище: изоляция от сетевого контура
  clickhouse:
    image: clickhouse/clickhouse-server:24.3-alpine
    restart: always
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    deploy:
      resources:
        limits:
          cpus: '6.00'
          memory: 8192M
        reservations:
          cpus: '2.00'
          memory: 4096M
    oom_score_adj: 0

Параметр oom_score_adj задает приоритет вмешательства подсистемы Out-Of-Memory ядра Linux. Значение -900 практически полностью исключает завершение postgres, в то время как clickhouse со значением 0 при аварийном дефиците памяти принесет в жертву свой воркер, сохранив целостность транзакционных данных и непрерывность работы шлюза LiteLLM.


Матрица сайзинга KVM VPS под разные профили нагрузки

При выборе конфигурации виртуального сервера критическим фактором является отсутствие конкуренции за процессорное время со стороны соседей по гипервизору. Метрика CPU Steal Time (%st) в выводе утилит top или vmstat обязана строго равняться 0.0%. Наличие %st > 1.5% указывает на оверселлинг физических ядер CPU хостером, что вызывает мгновенный разброс времени отклика Uvicorn и срыв таймингов фоновых слияний в ClickHouse.

Платформа tropic.host предоставляет изолированные виртуальные серверы KVM на базе высокочастотных процессоров AMD EPYC и AMD Ryzen 9 с гарантированным отсутствием оверселлинга (%st = 0.0%), что позволяет точно сопоставить требования контейнерного стека с тарифной сеткой нод.

Профиль нагрузки Объем трафика (RPS / Трейсов в сутки) Аппаратная спецификация (vCPU / RAM / NVMe) Конфигурация ноды tropic.host Архитектурное назначение и поведение стека
Starter (Dev / Staging) До 25 RPS
~250 000 трейсов/сутки
4 vCPU (KVM, %st = 0.0%)
8 ГБ RAM
80 ГБ NVMe PCIe 4.0
KVM-Standard-4 (AMD EPYC, NVMe) Тестовые окружения, отладка промптов командой разработки до 10 инженеров. ClickHouse ограничен 3 ГБ RAM, хранение логов до 30 дней.
Production (Medium Load) До 150 RPS
~2 500 000 трейсов/сутки
8 vCPU (High-Freq, %st = 0.0%)
16 ГБ RAM
250 ГБ NVMe PCIe 4.0
KVM-Pro-8 (AMD Ryzen 9 / EPYC, NVMe) Боевой контур клиентских приложений. Выделенные пулы памяти под PostgreSQL (4 ГБ) и ClickHouse (6 ГБ). Retention трейсов до 60 дней без задержек в аналитике.
High-Load Enterprise До 600+ RPS
> 10 000 000 трейсов/сутки
16 vCPU (High-Freq, %st = 0.0%)
32–64 ГБ RAM
800+ ГБ NVMe Enterprise
KVM-Ultra-16 (AMD EPYC, PCIe 4.0 NVMe) Масштабный enterprise-шлюз. ClickHouse аллоцирует до 24 ГБ RAM под сложные группировки по спанам, параллельный стриминг 400+ SSE-соединений без деградации p99.

Представленный расчет гарантирует стабильную эксплуатацию связки LiteLLM Proxy и Langfuse v3 внутри изолированной среды Docker, исключая взаимное влияние аналитических нагрузок на транзакционную надежность шлюза.

Развертывание полного стека LiteLLM + Langfuse в Docker Compose

Эксплуатация аналитического контура LLM в production-среде требует жесткого разграничения сетевых зон, изоляции вычислительных ресурсов на уровне ядра Linux (cgroups v2) и тонкой настройки файловых дескрипторов. Архитектура стека строится на разделении транзакционного шлюза LiteLLM Proxy и трейсинг-платформы Langfuse v3, где критически важно исключить конкуренцию за дисковый ввод-вывод между фоновыми агрегациями ClickHouse и стриминговыми ответами FastAPI/Uvicorn.

При развертывании стека на изолированном KVM VPS от tropic.host гарантируется отсутствие шумных соседей: метрика CPU Steal Time (%st) удерживается на отметке 0.0%, а высокоскоростной NVMe-накопитель PCIe 4.0 обеспечивает достаточный запас IOPS на случайных операциях записи 4K (QD1 > 50 000 IOPS), что исключает блокировку очередей PostgreSQL при сбросе WAL-логов и буферов ClickHouse.

                  [ Входящий трафик: Клиентские приложения / SDK ]
                                         │
                                         ▼ (Порт 4000: HTTPS/TCP BBR)
                     ┌───────────────────────────────────────┐
                     │          LiteLLM Proxy (FastAPI)      │
                     │  - cgroups: 2 vCPU, 2 GB RAM          │
                     │  - libuv / epoll loop, SSE Streaming  │
                     └───────┬───────────────────────┬───────┘
                             │                       │
     (Асинхронные трейсы)    │                       │  (Upstream API)
                             ▼                       ▼
            ┌────────────────────────────┐     [ OpenAI / Anthropic / Gemini ]
            │ gateway_internal (Bridge)  │
            └────────────┬───────────────┘
                         │
      ┌──────────────────┴──────────────────┐
      ▼                                     ▼
┌───────────────┐                     ┌───────────────┐
│ Langfuse Web  │ ◄──(Async Queue)──► │Langfuse Worker│
│ (Next.js/API) │                     │ (Batch Flush) │
└───────┬───────┘                     └───────┬───────┘
        │                                     │
        ├──────────────────┬──────────────────┤
        ▼                  ▼                  ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│ PostgreSQL 16 │  │ ClickHouse 24 │  │    Redis 7    │
│  (Метаданные) │  │  (Аналитика)  │  │   (Очереди)   │
└───────────────┘  └───────────────┘  └───────────────┘

Подготовка операционной системы и ядра Linux

Перед запуском контейнеров необходимо скорректировать лимиты виртуальной памяти ядра и дескрипторов сокетов. По умолчанию значения vm.max_map_count и fs.file-max в Ubuntu 24.04 LTS недостаточны для ClickHouse и высоконагруженных очередей соединений Uvicorn.

Создайте конфигурационный файл тюнинга ядра /etc/sysctl.d/99-llm-gateway.conf:

# Максимальное количество областей отображения памяти (требование ClickHouse и Redis)
vm.max_map_count=262144

# Защита от OOM при агрессивных аллокациях памяти
vm.overcommit_memory=1

# Размер очереди соединений на сокете для сглаживания всплесков RPS
net.core.somaxconn=65535

# Максимальный размер очередей входящих сетевых пакетов на уровне сетевого интерфейса
net.core.netdev_max_backlog=10000

# Диапазон временных портов для сокетов обратного прокси к Upstream LLM
net.ipv4.ip_local_port_range=10240 65535

# Активация алгоритма управления перегрузкой TCP BBR и очереди fq
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

# Лимит открытых файловых дескрипторов на систему
fs.file-max=2097152

Примените параметры на работающем узле без перезагрузки:

sysctl -p /etc/sysctl.d/99-llm-gateway.conf

Установите системные лимиты дескрипторов (nofile) для демона Docker в файле /etc/systemd/system/docker.service.d/override.conf:

[Service]
LimitNOFILE=1048576
LimitNPROC=524288
LimitMEMLOCK=infinity

Перечитайте конфигурацию systemd и перезапустите службу контейнеризации:

systemctl daemon-reload && systemctl restart docker

Структура каталогов и разграничение прав доступа

Создайте дерево рабочих директорий на NVMe-разделе хоста. Все конфигурационные файлы, содержащие токены доступа и мастер-ключи, должны иметь маску прав 0600, а каталоги — 0700.

mkdir -p /opt/llm-stack/{config,data/{postgres,clickhouse,redis,minio}}
chmod -R 700 /opt/llm-stack
cd /opt/llm-stack

Сгенерируйте криптографически стойкие секреты для шифрования сессий и сервисной аутентификации:

cat <<EOF > /opt/llm-stack/.env
# ==============================================================================
# Сетевые параметры и порты хоста
# ==============================================================================
HOST_IP=0.0.0.0
LITELLM_PORT=4000
LANGFUSE_PORT=3000

# ==============================================================================
# Мастер-ключи аутентификации
# ==============================================================================
LITELLM_MASTER_KEY=sk-$(openssl rand -hex 24)
[email protected]
LANGFUSE_INIT_USER_PASSWORD=$(openssl rand -base64 24)
LANGFUSE_INIT_PROJECT_ID=production-gateway
LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-lf-$(openssl rand -hex 16)
LANGFUSE_INIT_PROJECT_SECRET_KEY=sk-lf-$(openssl rand -hex 16)
NEXTAUTH_SECRET=$(openssl rand -base64 32)
SALT=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)

# ==============================================================================
# Базы данных
# ==============================================================================
POSTGRES_DB=langfuse
POSTGRES_USER=postgres
POSTGRES_PASSWORD=$(openssl rand -base64 24)

CLICKHOUSE_DB=langfuse
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=$(openssl rand -base64 24)

REDIS_PASSWORD=$(openssl rand -base64 24)

# ==============================================================================
# S3 / MinIO Object Storage (хранение raw-пейлоадов)
# ==============================================================================
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=$(openssl rand -base64 24)
EOF

chmod 600 /opt/llm-stack/.env

Конфигурация LiteLLM Proxy: Маршрутизация и экспорт трейсов

Создайте файл /opt/llm-stack/config/litellm_config.yaml. Конфигурация включает локальный Redis-кэш для снижения нагрузки на апстримы, пулинг соединений HTTPX и нативный асинхронный коллбэк для выгрузки спанов в Langfuse.

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
      rpm: 1000
      timeout: 30
      stream_timeout: 60

  - model_name: claude-3-5-sonnet
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 800
      timeout: 30

  - model_name: meta-llama-3.1-70b
    litellm_params:
      model: hosted_vllm/meta-llama/Meta-Llama-3.1-70B-Instruct
      api_base: http://internal-vllm.infra:8000/v1
      api_key: none

litellm_settings:
  # Асинхронная отправка трейсов в локальный инстанс Langfuse
  callbacks: ["langfuse"]
  telemetry: false
  drop_params: true
  set_verbose: false
  # Настройки пула постоянных соединений HTTPX (keep-alive)
  request_timeout: 45
  max_retries: 2

router_settings:
  routing_strategy: "latency-based-routing"
  redis_host: "redis"
  redis_port: 6379
  redis_password: "os.environ/REDIS_PASSWORD"
  enable_pre_call_checks: true

general_settings:
  master_key: "os.environ/LITELLM_MASTER_KEY"
  database_url: "postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/litellm_db"
  store_model_in_db: true

Эталонный production docker-compose.yml

Стек разворачивается в двух изолированных виртуальных сетях Docker: 1. gateway_public: Bridge-сеть, через которую на публичные порты хоста пробрасываются исключительно интерфейс Langfuse (порт 3000) и шлюз LiteLLM Proxy (порт 4000). 2. gateway_internal: Изолированная внутренняя сеть (internal: true), к которой подключены хранилища (PostgreSQL, ClickHouse, Redis, MinIO) и воркеры. Прямой доступ к портам баз данных извне хоста физически заблокирован на уровне правил iptables/nftables Docker.

Сохраните следующий манифест в /opt/llm-stack/docker-compose.yml:

name: llm-infrastructure

networks:
  gateway_public:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.10.0/24
  gateway_internal:
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.28.20.0/24

volumes:
  postgres_data:
    driver: local
  clickhouse_data:
    driver: local
  redis_data:
    driver: local
  minio_data:
    driver: local

services:
  # ============================================================================
  # Слой шлюза: LiteLLM Proxy
  # ============================================================================
  litellm:
    image: ghcr.io/berriai/litellm:main-v1.61.15
    container_name: litellm-proxy
    restart: unless-stopped
    ports:
      - "${HOST_IP}:${LITELLM_PORT}:4000"
    networks:
      - gateway_public
      - gateway_internal
    volumes:
      - ./config/litellm_config.yaml:/app/config.yaml:ro
    environment:
      - PORT=4000
      - NUM_WORKERS=4
      - LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/litellm_db
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      - LANGFUSE_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY}
      - LANGFUSE_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY}
      - LANGFUSE_HOST=http://langfuse-web:3000
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      langfuse-web:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: "4.0"
          memory: 4096M
        reservations:
          cpus: "1.0"
          memory: 1024M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:4000/health/readiness || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 15s

  # ============================================================================
  # Слой трейсинга: Langfuse Web (Next.js API & UI)
  # ============================================================================
  langfuse-web:
    image: langfuse/langfuse:3
    container_name: langfuse-web
    restart: unless-stopped
    ports:
      - "${HOST_IP}:${LANGFUSE_PORT}:3000"
    networks:
      - gateway_public
      - gateway_internal
    environment:
      - NODE_ENV=production
      - NEXTAUTH_URL=http://localhost:3000
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - SALT=${SALT}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      - CLICKHOUSE_URL=http://clickhouse:8123
      - CLICKHOUSE_USER=${CLICKHOUSE_USER}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
      - CLICKHOUSE_DB=${CLICKHOUSE_DB}
      - REDIS_CONNECTION_STRING=redis://:${REDIS_PASSWORD}@redis:6379
      - LANGFUSE_INIT_ORG_ID=tropic-org
      - LANGFUSE_INIT_ORG_NAME="Tropic Infrastructure"
      - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID}
      - LANGFUSE_INIT_PROJECT_NAME="Production LLM Gateway"
      - LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY}
      - LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY}
      - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL}
      - LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD}
    depends_on:
      postgres:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 4096M
        reservations:
          cpus: "0.5"
          memory: 1024M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/api/public/health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # ============================================================================
  # Асинхронный фоновый воркер: Langfuse Worker
  # ============================================================================
  langfuse-worker:
    image: langfuse/langfuse-worker:3
    container_name: langfuse-worker
    restart: unless-stopped
    networks:
      - gateway_internal
    environment:
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      - CLICKHOUSE_URL=http://clickhouse:8123
      - CLICKHOUSE_USER=${CLICKHOUSE_USER}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
      - CLICKHOUSE_DB=${CLICKHOUSE_DB}
      - REDIS_CONNECTION_STRING=redis://:${REDIS_PASSWORD}@redis:6379
      - SALT=${SALT}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2048M

  # ============================================================================
  # Транзакционная база данных: PostgreSQL 16
  # ============================================================================
  postgres:
    image: postgres:16-alpine
    container_name: gateway-postgres
    restart: unless-stopped
    networks:
      - gateway_internal
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    command: >
      postgres -c shared_buffers=1024MB
               -c effective_cache_size=3072MB
               -c work_mem=32MB
               -c maintenance_work_mem=256MB
               -c min_wal_size=1GB
               -c max_wal_size=4GB
               -c checkpoint_completion_target=0.9
               -c wal_buffers=16MB
               -c max_connections=200
               -c synchronous_commit=off
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 3072M
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 3s
      retries: 5

  # ============================================================================
  # Аналитическое колоночное хранилище: ClickHouse 24
  # ============================================================================
  clickhouse:
    image: clickhouse/clickhouse-server:24.8-alpine
    container_name: gateway-clickhouse
    restart: unless-stopped
    networks:
      - gateway_internal
    volumes:
      - clickhouse_data:/var/lib/clickhouse
    environment:
      - CLICKHOUSE_DB=${CLICKHOUSE_DB}
      - CLICKHOUSE_USER=${CLICKHOUSE_USER}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
      - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    deploy:
      resources:
        limits:
          cpus: "4.0"
          memory: 6144M
        reservations:
          cpus: "1.0"
          memory: 2048M
    healthcheck:
      test: ["CMD-SHELL", "clickhouse-client --user ${CLICKHOUSE_USER} --password ${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ============================================================================
  # Очередь задач и кэширование: Redis 7
  # ============================================================================
  redis:
    image: redis:7.4-alpine
    container_name: gateway-redis
    restart: unless-stopped
    networks:
      - gateway_internal
    volumes:
      - redis_data:/data
    command: >
      redis-server --requirepass ${REDIS_PASSWORD}
                   --maxmemory 1536mb
                   --maxmemory-policy allkeys-lru
                   --appendonly yes
                   --appendfsync everysec
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 2048M
    healthcheck:
      test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD} ping | grep PONG"]
      interval: 5s
      timeout: 3s
      retries: 5

Инициализация служебных баз данных и запуск контура

Перед первым стартом стека необходимо инициализировать отдельную базу данных для внутреннего состояния LiteLLM Proxy внутри кластера PostgreSQL. Выполните запуск СУБД в фоновом режиме:

docker compose up -d postgres

Дождитесь успешного прохождения проверки доступности (healthcheck healthy):

until [ "$(docker inspect -f '{{.State.Health.Status}}' gateway-postgres)" = "healthy" ]; do
    echo "Ожидание готовности PostgreSQL..."
    sleep 2
done

Создайте изолированную базу данных litellm_db:

docker exec -i gateway-postgres psql -U postgres -d postgres -c "CREATE DATABASE litellm_db;"

Запустите все сервисы стека:

docker compose up -d

Аудит состояния сервисов и проверка задержек

Убедитесь, что все контейнеры перешли в статус healthy и не испытывают дефицита памяти по лимитам cgroups:

docker compose ps

Вывод команды должен подтвердить статус healthy для всех основных узлов:

NAME                 IMAGE                                 STATUS                    PORTS
gateway-clickhouse   clickhouse/clickhouse-server:24.8     Up (healthy)              
gateway-postgres     postgres:16-alpine                    Up (healthy)              
gateway-redis        redis:7.4-alpine                      Up (healthy)              
langfuse-web         langfuse/langfuse:3                   Up (healthy)              0.0.0.0:3000->3000/tcp
langfuse-worker      langfuse/langfuse-worker:3            Up                        
litellm-proxy        ghcr.io/berriai/litellm:main-v1.61.15 Up (healthy)              0.0.0.0:4000->4000/tcp

Проверьте потребление системных ресурсов на хосте через cgroups v2:

docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"

Выполните тестовый запрос генерации через LiteLLM Proxy с проверкой передачи спана в Langfuse:

source /opt/llm-stack/.env

curl -i -X POST http://127.0.0.1:4000/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "Ping cluster health"}
    ],
    "max_tokens": 10,
    "metadata": {
      "trace_id": "test-cluster-trace-01"
    }
  }'

Проверьте лог воркера Langfuse на предмет успешного батчинга событий в ClickHouse:

docker logs --tail 20 langfuse-worker

В логах должна присутствовать запись о фиксации спана без ошибок подключения к ClickHouse:

[ClickHouse] Successfully flushed batch of 1 events (traces/spans) in 12ms.

На виртуальных серверах tropic.host за счет аппаратной изоляции KVM, прямого подключения к BGP-точкам обмена трафиком и включенного TCP BBR, накладные расходы LiteLLM Proxy на проксирование запроса удерживаются в пределах 2–4 мс (p99 latency), что полностью нивелирует задержки шлюзовой архитектуры для конечных клиентских сервисов.

Настройка семантического кэширования и автоматического фейловера между провайдерами

Маршрутизация запросов к внешним LLM API сопряжена с двумя фундаментальными проблемами: непредсказуемой сетевой задержкой провайдеров (где time-to-first-token колеблется от 400 мс до 3500 мс) и периодическими сбоями доступности (HTTP 429 Rate Limit, HTTP 500/503 Internal Server Error, либо блокировками сетевых маршрутов).

Построение отказоустойчивого производственного шлюза на базе LiteLLM Proxy требует реализации двух контуров оптимизации: 1. Семантическое кэширование (Redis Similarity Cache): перехват входящих промптов с векторным сравнением по косинусному расстоянию. Если аналогичный по смыслу запрос уже обрабатывался, ответ отдается из оперативной памяти за единицы миллисекунд без обращения к внешним API. 2. Многоуровневый автоматический фейловер (Multi-tier Provider Fallback): прозрачное переключение пользовательского контекста на резервные коммерческие аккаунты или локально развернутый инференс-стек (Ollama / vLLM) при исчерпании лимитов или отказе основного апстрима.


Архитектура векторного кэша и подготовка подсистемы ядра Linux

В отличие от классического кэширования по точному SHA-256 хэшу строки запроса, семантический кэш преобразует текст messages[last].content в вектор размерностью $N$ через embedding-модель, после чего выполняет поиск k-ближайших соседей (k-NN) в индексе HNSW (Hierarchical Navigable Small World).

Перед интеграцией векторных операций в Redis необходимо устранить узкие места в ядре Linux хост-системы. Сериализация и поиск по векторным индексам под высокой параллельной нагрузкой чувствительны к задержкам выделения страниц памяти и блокировкам аллокатора.

Примените параметры ядра через /etc/sysctl.d/99-redis-llm.conf:

cat << 'EOF' > /etc/sysctl.d/99-redis-llm.conf
# Разрешение выделения виртуальной памяти без предварительной проверки (критично для Redis BGSAVE)
vm.overcommit_memory = 1

# Увеличение длины очереди сокетов для предотвращения сброса SYN-пакетов
net.core.somaxconn = 65535

# Расширение диапазона эфемерных портов для параллельных исходящих TCP-соединений к LLM API
net.ipv4.ip_local_port_range = 10240 65535

# Включение быстрого повторного использования TIME_WAIT сокетов
net.ipv4.tcp_tw_reuse = 1

# Увеличение лимита дескрипторов файлов ядра
fs.file-max = 2097152
EOF

sysctl --system

Отключите механизм Transparent Huge Pages (THP), который вызывает фрагментацию памяти и деградацию латентности p99 в Redis при форках фонового сохранения:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

Для персистентного сохранения состояния THP после перезагрузки создайте systemd-юнит:

cat << 'EOF' > /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable Transparent Huge Pages (THP) for Redis Stack
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=mongod.service redis.service docker.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=basic.target
EOF

systemctl daemon-reload
systemctl enable --now disable-thp.service

Развертывание локального контура инференса (Ollama / vLLM) в Docker Compose

Для реализации нулевого времени простоя при глобальных сбоях облачных провайдеров в общий стек добавляется легковесный локальный рантайм Ollama. На KVM-нодах без дискретного GPU инференс квантованных моделей (например, llama3.1:8b-instruct-q4_K_M или qwen2.5:7b-instruct-q4_K_M) выполняется на ядрах CPU с использованием инструкций AVX-512.

Отредактируйте /opt/llm-stack/docker-compose.yml, добавив сервис Ollama и подключив модуль векторного поиска к Redis через образ redis/redis-stack-server:

  # Замена стандартного Redis на Redis Stack Server с поддержкой модуля RediSearch/RedisJSON
  gateway-redis:
    image: redis/redis-stack-server:7.4.2-v0
    container_name: gateway-redis
    restart: unless-stopped
    command: >
      redis-server
      --requirepass ${REDIS_PASSWORD}
      --maxmemory 4gb
      --maxmemory-policy allkeys-lru
      --save 900 1
      --save 300 10
      --tcp-backlog 65535
    volumes:
      - redis_data:/data
    networks:
      - llm_net
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 5G
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  # Локальный резервный инференс-движок на CPU
  gateway-ollama:
    image: ollama/ollama:0.5.7
    container_name: gateway-ollama
    restart: unless-stopped
    environment:
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=4
      - OLLAMA_MAX_LOADED_MODELS=1
    volumes:
      - ollama_models:/root/.ollama
    networks:
      - llm_net
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 8G
    healthcheck:
      test: ["CMD-SHELL", "ollama list || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3

Запустите контейнеры и загрузите компактную квантованную модель для обслуживания аварийного трафика:

docker compose up -d gateway-redis gateway-ollama
docker exec -it gateway-ollama ollama pull llama3.1:8b

Благодаря тому, что виртуализация KVM на tropic.host гарантирует нулевой оверселлинг процессорных ресурсов (%st = 0.0%), а серверные NVMe-накопители корпоративного класса (PCIe 4.0) обеспечивают скорость последовательного чтения более 3500 МБ/с, холодный запуск и загрузка 4.7 ГБ весов модели GGUF в оперативную память выполняются менее чем за 3 секунды без просадки дисковой очереди подсистемы I/O.


Конфигурация LiteLLM Proxy: Семантический кэш и маршрутизация отказоустойчивости

Вся логика перехвата запросов, вычисления эмбеддингов и каскадного переключения описывается в конфигурационном файле /opt/llm-stack/litellm-config.yaml.

Семантический кэш настраивается с порогом сходства similarity_threshold: 0.85. Если косинусное сходство между новым запросом и ранее сохраненным превышает 0.85, LiteLLM мгновенно возвращает кэшированный completion.

Для формирования векторных представлений используется быстрая и экономичная модель text-embedding-3-small.

Обновите /opt/llm-stack/litellm-config.yaml:

model_list:
  # Основной продакшн-роут (OpenAI Direct)
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: "os.environ/OPENAI_API_KEY"
      timeout: 15
      max_retries: 1

  # Вторичный резервный роут (Azure OpenAI Service)
  - model_name: azure-gpt-4o
    litellm_params:
      model: azure/gpt-4o-deployment
      api_base: "os.environ/AZURE_API_BASE"
      api_key: "os.environ/AZURE_API_KEY"
      api_version: "2024-08-01-preview"
      timeout: 15
      max_retries: 1

  # Локальный резервный роут (Ollama на хосте VPS)
  - model_name: local-llama-fallback
    litellm_params:
      model: ollama/llama3.1:8b
      api_base: "http://gateway-ollama:11434"
      timeout: 30

  # Модель генерации эмбеддингов для семантического кэша
  - model_name: text-embedding-cache
    litellm_params:
      model: openai/text-embedding-3-small
      api_key: "os.environ/OPENAI_API_KEY"

litellm_settings:
  # Подключение Langfuse для трассировки кэш-хитов и фейловеров
  success_callback: ["langfuse"]
  failure_callback: ["langfuse"]

  # Настройка семантического кэша в Redis
  cache:
    type: "redis-semantic"
    supported_call_types: ["completion", "acompletion"]
    similarity_threshold: 0.85
    embedding_model: "text-embedding-cache"
    redis_host: "gateway-redis"
    redis_port: 6379
    redis_password: "os.environ/REDIS_PASSWORD"
    namespace: "litellm:semantic_cache"
    ttl: 86400  # Время жизни кэша: 24 часа

router_settings:
  # Алгоритм маршрутизации: наименьшая задержка среди активных
  routing_strategy: "latency-based-routing"

  # Ограничение частоты опроса неисправных апстримов (Circuit Breaker)
  allowed_fails: 2
  cooldown_time: 60  # Время вывода провайдера из пула при ошибках (сек)
  num_retries: 2
  retry_after: 1

  # Матрица каскадного переключения: Primary -> Azure -> Local Ollama
  fallbacks:
    - gpt-4o: ["azure-gpt-4o", "local-llama-fallback"]

  # Переключение при переполнении контекстного окна
  context_window_fallbacks:
    - gpt-4o: ["azure-gpt-4o"]

Перезапустите LiteLLM Proxy для применения обновлений:

docker compose restart litellm-proxy

Верификация семантического кэша и анализ профиля задержки (Latency p99)

Для проверки работы семантического кэша отправим два запроса с различной грамматической структурой, но идентичным смысловым интентом.

Первый запрос (Cache MISS) — обращение к внешнему API с сохранением эмбеддинга в Redis:

source /opt/llm-stack/.env

time curl -s -X POST http://127.0.0.1:4000/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "Каковы минимальные системные требования для PostgreSQL 16 в production?"}
    ],
    "temperature": 0.0
  }' | jq '.choices[0].message.content'

Замеренное время выполнения: ~940 мс (запрос ушел в OpenAI, токен прошел генерацию, эмбеддинг записан в Redis).

Второй запрос (Cache HIT) — переформулированный вопрос:

time curl -i -s -X POST http://127.0.0.1:4000/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "Какие требования к оборудованию и RAM нужны для базы данных Postgres 16 на проде?"}
    ],
    "temperature": 0.0
  }'

В HTTP-заголовках ответа LiteLLM возвращает подтверждение кэш-попадания:

HTTP/1.1 200 OK
content-type: application/json
x-litellm-cache-key: litellm:semantic_cache:...
x-litellm-cache-status: HIT
x-litellm-response-time: 0.0052

Время ответа снижается с 940 мс до 5.2 мс (падение задержки более чем в 180 раз).

Проверьте наличие созданного HNSW-индекса и объем ключей в Redis:

docker exec -it gateway-redis redis-cli -a "${REDIS_PASSWORD}" FT._LIST
docker exec -it gateway-redis redis-cli -a "${REDIS_PASSWORD}" INFO memory | grep used_memory_human

Тестирование автоматического фейловера при деградации внешнего API

Протестируем сценарий полного отказа внешнего API OpenAI через блокировку сетевого трафика средствами ядра Linux (iptables), чтобы верифицировать поведение Circuit Breaker в LiteLLM Proxy.

Заблокируйте исходящие соединения к диапазонам публичных адресов OpenAI на хосте:

iptables -I DOCKER-USER -p tcp -m string --string "api.openai.com" --algo bm -j DROP

Выполните запрос к виртуальной модели gpt-4o:

curl -i -X POST http://127.0.0.1:4000/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "Explain Linux cgroups v2 memory controller in one sentence."}
    ],
    "metadata": {
      "trace_id": "simulated-failure-drill-01"
    }
  }'

Проанализируйте лог контейнера LiteLLM Proxy в реальном времени:

docker logs --tail 30 litellm-proxy

Журнал зафиксирует срабатывание цепочки отказоустойчивости:

[LiteLLM Router] Exception occurred for model=openai/gpt-4o: APIConnectionError - Request timed out.
[LiteLLM Router] Cooldown triggered for model=openai/gpt-4o. Fails: 1/2.
[LiteLLM Router] Falling back to model=azure-gpt-4o.
[LiteLLM Router] Exception occurred for model=azure/gpt-4o-deployment: APIConnectionError - Connection refused.
[LiteLLM Router] Falling back to model=local-llama-fallback (ollama/llama3.1:8b).
[LiteLLM Router] Success! Request served by local-llama-fallback.

Откройте интерфейс Langfuse (http://<IP_СЕРВЕРА>:3000). В трейсе с идентификатором simulated-failure-drill-01 будет зафиксировано дерево вызовов: 1. openai/gpt-4o — Status: ERROR (Timeout / Network Unreachable). 2. azure-gpt-4o — Status: ERROR. 3. ollama/llama3.1:8b — Status: SUCCESS, с указанием локальной длительности инференса и потребления токенов.

Снимите тестовую блокировку iptables:

iptables -D DOCKER-USER -p tcp -m string --string "api.openai.com" --algo bm -j DROP

Надежность описанной архитектуры опирается на стабильность сетевого стека и отсутствие аппаратных просадок гипервизора. Развертывание шлюза на платформе tropic.host обеспечивает аплинки пропускной способностью 1–10 Гбит/с с активированным по умолчанию алгоритмом управления перегрузками TCP BBR и прямым BGP-пирингом в крупнейших узлах обмена трафиком (DE-CIX Frankfurt, AMS-IX Amsterdam, LINX London). Это гарантирует, что даже в моменты пикового фейловера задержка при перенаправлении трафика между региональными точками API и локальным сервером удерживается в строгих рамках p99 без потери пакетов на L3/L4 уровнях.

Управление бюджетами команд, квоты токенов и маскирование PII-данных

Централизация доступа к upstream-провайдерам нейросетевых моделей требует изоляции бюджетов, предотвращения утечек конфиденциальных данных и гранулярного аудита каждой транзакции. Развертывание связки litellm proxy vps langfuse docker переносит функции enterprise-шлюза авторизации непосредственно на ваш собственный сервер: LiteLLM Proxy выступает в роли защищенного реверс-прокси с поддержкой виртуальных мастер-ключей, а Langfuse аккумулирует метаданные трассировки, оценивая стоимость каждого вызова с детализацией до пользователя и подразделения.

Архитектура виртуальных ключей и многоуровневого биллинга

Прямая передача нативных ключей OpenAI, Anthropic или Azure в клиентские микросервисы и среды разработки создает критические риски компрометации квот и перерасхода бюджета. LiteLLM решает эту задачу с помощью механизма Virtual Keys (псевдо-ключей с префиксом sk-), которые сохраняются в захешированном виде (SHA-256) в базе данных PostgreSQL и кэшируются в оперативной памяти Redis.

Иерархия контроля строится на трех уровнях: 1. Global Master Key — ключ администратора (LITELLM_MASTER_KEY), обладающий полными правами управления прокси через REST API. 2. Team Entity — логическая сущность рабочей группы (команды инженеров или конкретного микросервиса) с собственным лимитом расходов (max_budget), валютой, интервалом сброса баланса (budget_duration: 30d) и пулом разрешенных моделей. 3. User / End-User Key — виртуальный ключ конечного разработчика или приложения, привязанный к команде. Ограничивается собственным бюджетом, лимитами скорости RPM (Requests Per Minute) и TPM (Tokens Per Minute).

Для развертывания этой функциональности в Docker Compose инстансу LiteLLM передаются параметры подключения к базе данных и Redis, управляющему скользящими окнами счетчиков (Leaky Bucket):

services:
  litellm-proxy:
    image: ghcr.io/berriai/litellm-database:main-v1.49.2
    container_name: litellm-proxy
    restart: unless-stopped
    ports:
      - "127.0.0.1:4000:4000"
    environment:
      - DATABASE_URL=postgresql://litellm_user:SecretPGPass2026@postgres:5432/litellm_db
      - REDIS_URL=redis://:SecretRedisPass2026@redis:6379/0
      - STORE_MODEL_IN_DB=True
      - LITELLM_MASTER_KEY=sk-master-superadmin-cluster-token-2026
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: '4.00'
          memory: 4096M
        reservations:
          cpus: '2.00'
          memory: 2048M

На уровне ядра операционной системы Linux для высоконагруженного шлюза под управлением Redis и Uvicorn (LiteLLM) требуется исключить задержки на аллокацию памяти и переполнение очередей соединений. В /etc/sysctl.d/99-litellm.conf задаются системные вызовы ядра:

# Предотвращение сбоев фонового снапшота Redis (BGSAVE)
vm.overcommit_memory = 1

# Расширение очереди неполных сокетов TCP для минимизации SYN-дропов
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096

# Быстрая утилизация сокетов в состоянии TIME_WAIT
net.ipv4.tcp_tw_reuse = 1

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

sysctl -p /etc/sysctl.d/99-litellm.conf

Программное управление квотами через Management API

Создание команд и выпуск виртуальных ключей автоматизируются через REST API прокси. Сначала создается команда разработки с выделенным бюджетом $150 на расчетный период 30 дней:

curl -s -X POST "http://127.0.0.1:4000/team/new" \
  -H "Authorization: Bearer sk-master-superadmin-cluster-token-2026" \
  -H "Content-Type: application/json" \
  -d '{
    "team_alias": "backend-ml-core",
    "max_budget": 150.00,
    "budget_duration": "30d",
    "models": ["openai/gpt-4o", "azure-gpt-4o", "local-llama-fallback"],
    "tpm_limit": 200000,
    "rpm_limit": 120
  }'

Ответ шлюза содержит уникальный team_id:

{
  "team_id": "b3f81e82-2091-4cf1-8c01-789a241d019f",
  "team_alias": "backend-ml-core",
  "max_budget": 150.0,
  "budget_duration": "30d",
  "spend": 0.0
}

Далее для конкретного инженера или микросервиса генерируется виртуальный ключ с жестким персональным лимитом $25 внутри бюджета команды:

curl -s -X POST "http://127.0.0.1:4000/key/generate" \
  -H "Authorization: Bearer sk-master-superadmin-cluster-token-2026" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "b3f81e82-2091-4cf1-8c01-789a241d019f",
    "key_alias": "dev-user-ivanov",
    "user_id": "[email protected]",
    "max_budget": 25.00,
    "duration": "60d",
    "metadata": {
      "department": "core-backend",
      "env": "staging"
    }
  }'

Выходной JSON возвращает токен вида sk-litellm-7hK9..., который передается клиенту. При превышении заданного значения max_budget шлюз моментально блокирует обработку запросов, возвращая клиенту код ответа HTTP 429 Too Many Requests с телом:

{
  "error": {
    "message": "LiteLLM Virtual Key budget exceeded. Current spend: 25.02, Max budget: 25.00",
    "type": "budget_exceeded",
    "code": "budget_exceeded_error"
  }
}

Все проверки расходов происходят атомарно в Redis с помощью скомпилированных Lua-скриптов LiteLLM. Это исключает возникновение состояния гонки (race conditions) при параллельных асинхронных запросах от распределенных воркеров, а накладные расходы на валидацию ключа не превышают 0.8–1.2 мс на запрос.

Маскирование персональных данных (PII/DLP Guardrails)

Передача персональных идентификаторов (PII), номеров банковских карт, секретных токенов и медицинских данных во внешние облачные API противоречит регламентам безопасности (152-ФЗ, GDPR, PCI-DSS). Настройка маскирования на уровне шлюза перехватывает полезную нагрузку до ее отправки в апстрим.

В LiteLLM встроен коннектор к Microsoft Presidio (движку обнаружения и десенсибилизации PII) и механизм пре-хуков (Guardrails). Развертывание дополнительного контейнера Presidio в общем сетевом пространстве Docker:

  presidio-analyzer:
    image: mcr.microsoft.com/presidio-analyzer:latest
    container_name: presidio-analyzer
    restart: unless-stopped
    ports:
      - "127.0.0.1:5001:3000"

  presidio-anonymizer:
    image: mcr.microsoft.com/presidio-anonymizer:latest
    container_name: presidio-anonymizer
    restart: unless-stopped
    ports:
      - "127.0.0.1:5002:3000"

В конфигурационном файле config.yaml шлюза активируется пре-обработка входящих запросов:

litellm_settings:
  callbacks: ["langfuse"]
  guardrails:
    - prompt_injection: false
    - presidio:
        api_base: "http://presidio-analyzer:3000"
        anonymizer_url: "http://presidio-anonymizer:3000"
        entities:
          - "EMAIL_ADDRESS"
          - "PHONE_NUMBER"
          - "CREDIT_CARD"
          - "CRYPTO"
          - "IP_ADDRESS"
          - "PERSON"
        masking_strategy: "redact" # Замена на <EMAIL_ADDRESS>, <PHONE_NUMBER> и т.д.

router_settings:
  routing_strategy: "latency-based-routing"

Когда клиент отправляет промпт:

"Заказчик John Doe (тел. +7-999-123-45-67, e-mail: [email protected]) заблокировал карту 4111222233334444. Сформируй ответ службы поддержки."

LiteLLM перед выполнением сетевого вызова к OpenAI преобразует текст в десенсибилизированный вид:

"Заказчик <PERSON> (тел. <PHONE_NUMBER>, e-mail: <EMAIL_ADDRESS>) заблокировал карту <CREDIT_CARD>. Сформируй ответ службы поддержки."

При этом downstream-сервис получает ответ без единой утечки исходных пользовательских данных за пределы локального контура VPS.

Сквозной аудит и аналитика затрат в Langfuse

Аудит операций реализуется через прямую интеграцию с платформой наблюдаемости Langfuse. Связка регистрирует не только факт вызова, но и сопоставляет исходный входящий запрос, модифицированный (десенсибилизированный) промпт, точное число токенов (Prompt, Completion, Total) и итоговую стоимость генерации в долларах США на основе встроенной матрицы цен LiteLLM.

Передача переменных окружения в контейнер litellm-proxy:

LANGFUSE_PUBLIC_KEY="pk-lf-0824bba7-827c-4029-a1b9-38e9a22d4f21"
LANGFUSE_SECRET_KEY="sk-lf-71ac984e-37bb-4180-87b6-19e4823da09c"
LANGFUSE_HOST="http://langfuse-server:3000"

При каждом вызове клиента виртуальный ключ передает контекст в заголовках. Запрос через LiteLLM Proxy:

curl -s -X POST "http://127.0.0.1:4000/v1/chat/completions" \
  -H "Authorization: Bearer sk-litellm-7hK9..." \
  -H "Content-Type: application/json" \
  -H "X-Langfuse-Session-Id: session-checkout-991" \
  -H "X-Langfuse-Tags: production,checkout-flow" \
  -d '{
    "model": "openai/gpt-4o",
    "messages": [
      {"role": "system", "content": "Ты ассистент службы безопасности."},
      {"role": "user", "content": "Проверь статус транзакции для клиента [email protected]"}
    ],
    "temperature": 0.2
  }'

На стороне веб-интерфейса Langfuse формируется развернутый трейс: * Trace Name: chat/completions * User ID: [email protected] (из метаданных виртуального ключа) * Metadata: team_id: b3f81e82-..., team_alias: backend-ml-core, key_alias: dev-user-ivanov * Calculated Cost: $0.000125 * Latency breakdown: * presidio_masking: 18 ms * litellm_proxy_overhead: 1.4 ms * llm_time_to_first_token: 240 ms * total_execution_time: 480 ms

Это позволяет финансовым и DevOps-инженерам в реальном времени строить отчеты Cost Allocation, отслеживать аномальные всплески потребления токенов конкретными микросервисами и автоматически отключать скомпрометированные виртуальные токены без остановки общего пайплайна.

Требования к серверной инфраструктуре и оптимизация дискового стека

В архитектуре, где каждый входящий запрос порождает транзакцию в PostgreSQL (списание баланса виртуального ключа), атомарное обновление счетчиков в Redis и асинхронный HTTP-экспорт событий в Langfuse, производительность ввода-вывода (I/O) и отсутствие виртуализационных задержек играют определяющую роль.

При пиковых нагрузках в 300–800 rps медленные дисковые операции при записи журналов упреждающей записи PostgreSQL (wal_level = minimal / replica) и сброса снапшотов Redis AOF (appendfsync everysec) приводят к образованию очередей на системном вызове epoll_wait, из-за чего latency p99 всего шлюза деградирует с предсказуемых 15–20 мс до катастрофических сотен миллисекунд.

Именно поэтому для продуктивного развертывания шлюза критически важен выбор аппаратной платформы. Хостинг-провайдер tropic.host предоставляет чистую виртуализацию KVM с полным отсутствием оверселлинга процессоров (показатель CPU Steal Time в выводе vmstat или top строго зафиксирован на значении %st = 0.0%). Инстансы работают на высокочастотных серверных ядрах AMD EPYC и Ryzen 9, а дисковая подсистема построена на серверных NVMe-накопителях PCIe 4.0 корпоративного уровня с показателями случайного чтения 4K QD1 свыше 50 000 IOPS.

Такой аппаратный фундамент исключает троттлинг при тяжелых транзакционных операциях PostgreSQL и гарантирует, что локальная обработка квот, десенсибилизация PII и маршрутизация сетевых пакетов выполняются без задержек. В сочетании с выделенными аплинками до 10 Гбит/с и защитой от DDoS на уровнях L3/L4/L7, облачная инфраструктура tropic.host гарантирует непрерывную доступность и изолированность корпоративного контура инференса моделей.

Отказоустойчивость, бэкапы баз данных и регламент аварийного восстановления

Архитектура распределенного стека, объединяющего шлюз LiteLLM Proxy и платформу наблюдаемости Langfuse в среде Docker на VPS, разделяет персистентное состояние на два изолированных уровня с принципиально разным профилем записи. Первый уровень — строго транзакционные метаданные PostgreSQL (виртуальные API-ключи, бюджетные лимиты команд, RBAC-роли, настройки роутинга моделей и пользовательские сессии). Второй уровень — аналитический массив временных рядов (time-series traces, спаны вызовов, токены, скоринг и метаданные промптов), агрегируемый Langfuse в колоночной СУБД ClickHouse и таблицах PostgreSQL. Потеря или повреждение любого из этих хранилищ вследствие аварии файловой системы, программного сбоя контейнера или некорректной миграции приводит к остановке всего инференс-контура. Для обеспечения целевых показателей RPO (Recovery Point Objective) $\le 15$ минут и RTO (Recovery Time Objective) $\le 5$ минут необходима комбинированная стратегия горячего резервирования с последующим сценарием Disaster Recovery (DR) без прерывания обработки клиентского трафика.

Горячее резервирование транзакционного состояния PostgreSQL

LiteLLM Proxy критически чувствителен к консистентности таблиц LiteLLM_VerificationToken, LiteLLM_UserTable и LiteLLM_Config. Использование стандартного docker exec pg_dump > backup.sql в продакшне недопустимо: однопоточный вывод блокирует буферы ввода-вывода, не гарантирует целостность при параллельных транзакциях без блокирующих блокировок и создает пиковую нагрузку на подсистему памяти. Резервное копирование реализуется через многопоточный дамп в каталожном формате (directory format) с утилитой сжатия pigz, использующей системный вызов splice для минимизации копирования данных между пространством ядра и пользователя.

Для исключения деградации latency p99 основного шлюза процесс резервного копирования изолируется в отдельном контейнере или запускается через systemd-таймер с жесткими лимитами cgroups v2 по CPU и дисковому I/O:

#!/usr/bin/env bash
set -Eeuo pipefail

BACKUP_DIR="/var/backups/postgres"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
TARGET_DIR="${BACKUP_DIR}/dump_${TIMESTAMP}"
POSTGRES_CONTAINER="litellm-postgres-1"
POSTGRES_USER="litellm_admin"
POSTGRES_DB="litellm_db"
RETENTION_DAYS=7

mkdir -p "${TARGET_DIR}"

# Создание консистентного snapshot-дампа в 4 потока через pg_dump custom directory format
docker exec -i "${POSTGRES_CONTAINER}" \
  pg_dump \
    -U "${POSTGRES_USER}" \
    -d "${POSTGRES_DB}" \
    --format=directory \
    --jobs=4 \
    --compress=6 \
    --no-owner \
    --no-privileges \
    --file="/tmp/dump_${TIMESTAMP}"

# Перенос артефактов из контейнера на хостовую NVMe-подсистему
docker cp "${POSTGRES_CONTAINER}:/tmp/dump_${TIMESTAMP}/." "${TARGET_DIR}/"
docker exec -i "${POSTGRES_CONTAINER}" rm -rf "/tmp/dump_${TIMESTAMP}"

# Формирование SHA256 контрольной суммы для валидации целостности
cd "${BACKUP_DIR}"
sha256sum "dump_${TIMESTAMP}"/* > "dump_${TIMESTAMP}.sha256"

# Асинхронная синхронизация в S3-совместимое объектное хранилище
rclone copy "${TARGET_DIR}" "s3-backup:litellm-backups/postgres/dump_${TIMESTAMP}" \
  --fast-list \
  --transfers 4 \
  --buffer-size 32M

# Ротация локальных копий старше RETENTION_DAYS
find "${BACKUP_DIR}" -mindepth 1 -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} +
find "${BACKUP_DIR}" -mindepth 1 -maxdepth 1 -name "*.sha256" -mtime +${RETENTION_DAYS} -delete

Параметр --jobs=4 распределяет выгрузку таблиц по независимым сессиям СУБД с синхронизацией момента снимка через механизм транзакционных снэпшотов PostgreSQL (Export Snapshot), что предотвращает фантомные чтения и логическую рассинхронизацию балансов виртуальных ключей LiteLLM.

Резервное копирование аналитического хранилища ClickHouse

ClickHouse, используемый современными версиями Langfuse для хранения миллионов спанов и тяжелых трейсов инференса моделей, оперирует иммутабельными партами данных (data parts). Создание резервных копий через SQL-запросы SELECT INTO OUTFILE приводит к исчерпанию оперативной памяти и падению OOM Killer. Стандартом резервирования ClickHouse в Docker является профильная утилита clickhouse-backup, взаимодействующая с ядром СУБД через механизм создания жестких ссылок (hard links) посредством системного вызова linkat при выполнении команды ALTER TABLE ... FREEZE.

Конфигурационный файл /etc/clickhouse-backup/config.yml монтируется в сайдкар-контейнер:

clickhouse:
  username: "default"
  password: "${CLICKHOUSE_PASSWORD}"
  host: "clickhouse"
  port: 9000
  disk_mapping:
    default: "/var/lib/clickhouse"
  timeout: "5m"
  freeze_by_part: true

s3:
  type: "s3"
  endpoint: "s3.storage.example.com"
  bucket: "langfuse-clickhouse-backups"
  access_key: "${S3_ACCESS_KEY}"
  secret_key: "${S3_SECRET_KEY}"
  compression_level: 3
  compression_format: "zstd"
  part_size: 104857600 # 100 MB
  concurrency: 4

Выполнение бэкапа инициируется командой:

docker exec -i langfuse-clickhouse-backup clickhouse-backup create --tables=langfuse.* backup_$(date +%Y%m%d_%H%M%S)
docker exec -i langfuse-clickhouse-backup clickhouse-backup upload backup_$(date +%Y%m%d_%H%M%S)

Механизм freeze_by_part исключает копирование данных внутри файловой системы: операция создания точки восстановления занимает менее 200 мс вне зависимости от объема базы данных (даже при размере аналитики свыше 500 ГБ), а сброс данных в S3 выполняется в фоновом режиме через потоковый zstd-компрессор.

Аппаратная изоляция I/O и лимиты cgroups v2

Резервное копирование и компрессия дампов создают взрывную нагрузку на дисковую подсистему и процессор. При некорректной настройке ввода-вывода дисковый планировщик Linux забивает очередь запросов к контроллеру, из-за чего системные вызовы fdatasync со стороны PostgreSQL блокируют транзакции списания квот LiteLLM.

На виртуальных серверах хостинг-провайдера tropic.host аппаратная база исключает оверселлинг процессорных мощностей (%st = 0.0% по данным vmstat 1), а серверные NVMe-накопители PCIe 4.0 обеспечивают параллельную обработку случайных операций чтения-записи (4K QD1 свыше 50 000 IOPS). Однако на уровне ядра ОС хоста требуется явное разграничение весов ввода-вывода через cgroups v2, чтобы фоновый бэкап не вытеснял из кэша горячие страницы оперативной памяти.

В файле docker-compose.yml для сервисов бэкапа и основных баз данных задаются приоритеты и лимиты:

services:
  litellm-postgres:
    image: postgres:16-alpine
    restart: always
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 8G
        reservations:
          cpus: '2.0'
          memory: 4G
    # Приоритезация блочного ввода-вывода cgroups v2
    blkio_config:
      weight: 800

  postgres-backup-runner:
    image: alpine:3.20
    restart: "no"
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 1G
    blkio_config:
      weight: 100 # Минимальный приоритет I/O относительно базы данных

На уровне хостовой операционной системы тюнингуются параметры подсистемы виртуальной памяти ядра (/etc/sysctl.d/99-dr-io.conf):

# Предотвращение накопления грязных страниц и резких зависаний I/O при записи дампов
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 1500

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

sysctl --system

Регламент аварийного восстановления (Disaster Recovery) без остановки продакшна

Восстановление состояния шлюза при повреждении базы данных или аппаратной замене хоста выполняется по принципу Shadow Instance (теневого контура) с атомарным переключением сетевых маршрутов. Прямое накатывание дампа поверх работающей СУБД с активными входящими подключениями от LiteLLM Proxy приводит к дедлокам, частичной записи данных и выдаче клиентам ошибок 500 Internal Server Error.

Фаза 1. Развертывание изолированного теневого инстанса СУБД

На VPS в существующем сетевом пространстве Docker запускается временный изолированный контейнер PostgreSQL без публикации портов наружу:

docker run -d \
  --name postgres-shadow \
  --network litellm-internal \
  --env POSTGRES_USER=litellm_admin \
  --env POSTGRES_PASSWORD=recovery_secret_pass \
  --env POSTGRES_DB=litellm_recovery \
  --tmpfs /dev/shm:rw,noexec,nosuid,size=1g \
  -v postgres-shadow-data:/var/lib/postgresql/data \
  postgres:16-alpine \
  -c shared_buffers=2GB \
  -c work_mem=64MB \
  -c maintenance_work_mem=1GB \
  -c max_wal_size=16GB \
  -c checkpoint_timeout=30min \
  -c synchronous_commit=off

Временное отключение synchronous_commit и агрессивное выделение maintenance_work_mem ускоряют перестроение индексов B-tree при импорте в 4–6 раз.

Фаза 2. Параллельный импорт и валидация целостности данных

Загрузка дампа из хранилища и накат схемы с данными выполняются с параллелизмом по числу доступных ядер vCPU:

# Скачивание бэкапа из S3
rclone copy "s3-backup:litellm-backups/postgres/dump_20261011_120000" /var/backups/restore_tmp/

# Проверка контрольной суммы
cd /var/backups/restore_tmp
sha256sum -c ../dump_20261011_120000.sha256

# Восстановление через pg_restore в 4 потока
docker run --rm \
  --network litellm-internal \
  -v /var/backups/restore_tmp:/restore:ro \
  postgres:16-alpine \
  pg_restore \
    -h postgres-shadow \
    -U litellm_admin \
    -d litellm_recovery \
    --jobs=4 \
    --exit-on-error \
    --no-owner \
    --no-privileges \
    /restore

Перед переключением боевого трафика выполняется строгая проверка логической целостности ключей, схем и реляционных связей:

docker exec -i postgres-shadow psql -U litellm_admin -d litellm_recovery -v ON_ERROR_STOP=1 <<'EOF'
-- Проверка существования ключевых таблиц и ненулевого количества ключей
DO $$
DECLARE
    token_count INT;
    user_count INT;
BEGIN
    SELECT COUNT(*) INTO token_count FROM "LiteLLM_VerificationToken";
    SELECT COUNT(*) INTO user_count FROM "LiteLLM_UserTable";

    IF token_count = 0 THEN
        RAISE EXCEPTION 'DR Validation Failed: Таблица LiteLLM_VerificationToken пуста!';
    END IF;

    RAISE NOTICE 'DR Validation Success: Обнаружено токенов: %, пользователей: %', token_count, user_count;
END $$;

-- Проверка целостности внешних ключей и индексов
SELECT schemaname, relname, indexrelname 
FROM pg_statio_all_indexes 
WHERE idx_blks_read IS NULL AND schemaname = 'public';
EOF

Фаза 3. Восстановление аналитических спанов ClickHouse

Восстановление ClickHouse осуществляется целевой выгрузкой партов без блокировки сетевых портов:

docker exec -i langfuse-clickhouse-backup clickhouse-backup download backup_20261011_120000
docker exec -i langfuse-clickhouse-backup clickhouse-backup restore --rm backup_20261011_120000

Флаг --rm принудительно очищает поврежденные таблицы перед заливкой hard links, предотвращая конфликт метаданных в каталоге metadata/langfuse/.

Фаза 4. Бесшовное переключение сетевых маршрутов (Zero-Downtime Switchover)

Архитектура LiteLLM Proxy построена на пуле асинхронных соединений SQLAlchemy с драйвером asyncpg. Чтобы шлюз переключился на восстановленную базу данных без перезапуска контейнера и сброса кэша маршрутов в оперативной памяти, шлюз настраивается на работу через внутренний DNS-алиас Docker или локальный балансировщик (HAProxy/PgBouncer).

Если используется стандартный Docker Compose, переключение реализуется манипуляцией сетевыми алиасами (network aliases):

# 1. Возврат настроек теневой БД в строгий production-режим
docker exec -i postgres-shadow psql -U litellm_admin -d litellm_recovery -c "ALTER SYSTEM SET synchronous_commit = 'on';"
docker exec -i postgres-shadow psql -U litellm_admin -d litellm_recovery -c "SELECT pg_reload_conf();"

# 2. Отключение поврежденного инстанса от внутренней сети
docker network disconnect litellm-internal litellm-postgres-1

# 3. Присоединение теневого инстанса с боевым сетевым псевдонимом СУБД
docker network connect --alias db-postgres litellm-internal postgres-shadow

# 4. Graceful-сигнал LiteLLM Proxy для ротации невалидных сессий пула соединений
docker kill --signal=SIGHUP litellm-proxy-1

LiteLLM Proxy перехватывает сигнал конфигурационной ротации (или автоматически сбрасывает разорванные сокеты при включенном параметре DATABASE_POOL_PRE_PING=True), инициируя переподключение к IP-адресу db-postgres, который теперь разрешается ядром Docker DNS (127.0.0.11) на IP контейнера postgres-shadow. Входящие HTTP-запросы на эндпоинты /chat/completions в течение 100–300 мс удерживаются во входящем бэклоге сетевого сокета без разрыва TCP-сессий с клиентами.

Использование высоконадежной инфраструктуры tropic.host гарантирует, что даже во время масштабных операций восстановления и интенсивного I/O пропускная способность физического сетевого интерфейса (до 10 Гбит/с) и аппаратная изоляция ядер процессора предотвращают возникновение сетевых джиттеров и потерю пакетов во взаимодействии между LiteLLM Proxy, Langfuse и базами данных. После успешного переключения поврежденный том данных демонтируется для последующего форензик-анализа, а postgres-shadow переименовывается в основной рабочий узел с обновлением маппинга постоянных томов volumes.

Часто задаваемые вопросы (FAQ)

Сколько ресурсов требует связка LiteLLM Proxy и Langfuse на VPS?

Для команды до 50 разработчиков оптимален сервер с 4 vCPU, 8 GB RAM и 80 GB NVMe. LiteLLM легковесен, но Langfuse использует PostgreSQL и ClickHouse для трекинга трейсов.

Как семантический кэш LiteLLM экономит бюджет на токены?

При повторяющихся или семантически близких запросах ответ отдается напрямую из локального Redis-кэша за 5 мс без обращения к платным API, что снижает расходы на 30–40%.