AACsearch
Эксплуатация

Производительность, SLA, кэширование и масштабирование

Как AACsearch масштабируется, где находятся слои кэша, какой SLA по тарифам и какие рычаги доступны, если задержка или throughput становятся узким местом.

Эта страница — инженерный справочник: насколько быстр AACsearch, почему и что можно поменять. Клиентскую матрицу тарифов смотри в Тарифы и лимиты; про 429 конкретно — в Операции → Rate limits.

SLA по тарифам

Тарифp95 чтенияp95 ack записиДоступностьВремя реакции
Freebest-effortbest-effort99.0%Community
Starter250 мс500 мс99.5%Email, 1 раб. дн.
Pro150 мс300 мс99.9%Приоритет, 4 ч
Business100 мс200 мс99.9%Выделенный, 1 ч
EnterpriseПо договоруПо договору99.95%SLA-контракт

p95 чтения — время от прихода POST /search в API-шлюз до отправки 200-ответа, считается по организации за последние 30 дней. ack записи — время от приёма write-запроса до того, как запись становится видна последующим чтениям (alias-swap latency на reindex'ах не учитывается — см. Reindexing).

Эти цели — SLO, не контрактные SLA. Для Enterprise приоритет — SLA-контракт.

Бюджет латентности на чтение

Запрос POST /search проходит пять хопов. Стандартный бюджет на хоп:

ХопБюджетКод-путь
API gateway + auth< 5 мсpackages/api/modules/search/lib/access.ts (проверка токена)
Сборка tenant-фильтра< 2 мсpackages/search/lib/search.ts собирает scoped filter_by
Policy cache< 1 мсpackages/search/lib/policy-cache.ts (TTL 60 с, см. ниже)
Typesense multi_search30–100 мсЗависит от размера коллекции, dim векторов и per_page
Шейпинг ответа + лог< 5 мсВключает emit аналитического события (fire-and-forget)

Общий p95-таргет на Pro: 150 мс для запроса per_page=10 к коллекции в 100k документов.

Слои кэша

В AACsearch три слоя кэша, каждый со своей целью и TTL:

1. Policy cache (серверный)

  • Что: Разрешённый план организации, feature flags, ограничения scoped-токенов.
  • Где: packages/search/lib/policy-cache.ts.
  • TTL: 60 секунд, in-process LRU на каждый API-инстанс.
  • Зачем: Резолв плана/entitlement требует 2–3 запросов в БД — кэш убирает их с хот-пути.
  • Инвалидация: Неявная (TTL). Апгрейд плана вступает в силу за ≤ 60 с. Для мгновенной инвалидации (например, лимит поднят по запросу клиента) — рестарт API-контейнера.

2. InstantSearch adapter cache (браузерный)

  • Что: Одинаковые запросы в коротком окне возвращают предыдущий ответ.
  • Где: packages/instantsearch-adapter/src/cache.ts.
  • TTL: Конфигурируется, по умолчанию выключен (адаптер не навязывает кэширование).
  • Зачем: Полезно когда пользователь возвращает фасет в предыдущее значение — экономит round-trip.
  • Инвалидация: Ручная через client.clearCache().

3. CDN cache (на edge, опционально)

  • Что: GET-эндпоинты (схема коллекции, публичный widget-конфиг) можно кэшировать на edge, если перед AACsearch стоит CDN.
  • TTL: Задаётся через Cache-Control заголовки AACsearch на GET. POST /search намеренно не кэшируется.
  • Зачем: Снижает нагрузку на хот-путь для read-only метаданных.

Не кэшируйте POST /search на edge. Тело ответа tenant-scoped по bearer-токену; edge-кэш по URL утечёт данные между тенантами.

Throughput и масштабирование

Per-API-key rate limit

Поле rateLimitPerMinute у SearchApiKey (по умолчанию 600) применяется на ключ, в минуту, sliding window в packages/api/modules/search/lib/rate-limit.ts. Тариф поднимает потолок, но не дефолт — лимит ставится на ключ явно.

ТарифМакс. rateLimitPerMinute на ключ
Free60
Starter300
Pro1 200
Business6 000
EnterpriseПо договору

Если один виджет разделяет ключ между сотнями браузеров — per-key ограничение это не та абстракция. Вместо «поднимите кэп» выпустите несколько ключей (по окружению, региону, крупному клиенту) — кэп существует чтобы локализовать побег клиентов.

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

Независимо от per-minute rate limit, у каждой организации есть месячная квота Search Units (maxSearchesPerMonth). Один поиск ИЛИ одна запись документа = один Search Unit. См. Тарифы → Search Units.

Два режима применения:

  • Soft cap (по умолчанию для Free/Starter): 80% — warning, 100% — quota_exceeded 429 с grace-read окном (24 ч) перед тем как и записи начнут падать.
  • Hard cap: Настраивается per-org. Записи падают на 100%; чтения работают (виджеты не ломаются сразу) до истечения grace-окна.

Логика grace-режимов лежит в packages/payments/lib/entitlements.ts. Дашборд показывает оба состояния в Settings → Billing.

Typesense кластер

Коллекции каждой организации живут в шаредном Typesense-кластере на Free/Starter/Pro. Business и Enterprise могут перейти на выделенный кластер (см. Enterprise → Dedicated cluster).

Характеристики шаред-кластера:

МетрикаШаред (Pro)Выделенный (Enterprise)
Коллекций на кластерДо ~5 000Под клиента
Документов на коллекциюПротестировано до 5 млнШардируется выше 10 млн
Лимит размерности векторов1 536 (OpenAI ada / Cohere v3)До 4 096
Параллельных reindex-job2 на организациюПод клиента

Выше потолка шаред-кластера alias-swap reindex (см. Reindexing) начинает заметно конкурировать с другими тенантами. Выделенный кластер — рекомендованный путь после 5 млн документов на коллекцию или устойчивых > 1k QPS.

Postgres

Источник истины AACsearch — Postgres (packages/database схема). Для latency-чувствительных путей API не ходит в Postgres на хот-пути поиска — он идёт в Typesense и в policy cache. В Postgres ходят за:

  • Резолвом плана/entitlement (кэшируется, см. policy cache).
  • Записями аудит-лога (fire-and-forget — никогда не блокирует ответ).
  • Оркестрацией reindex (SearchSyncOutbox).
  • Подсчётом квоты (SearchUsageEvent, батчится).

Пулинг коннектов настроен per-app (apps/saas, apps/marketing, packages/api). Дефолтный размер пула — 20 на реплику. Выше ~50 API-реплик стоит поставить PgBouncer в transaction-pooling режиме чтобы не получить exhaustion.

Observability

За чем смотреть при регрессе производительности:

СигналГде
p50 / p95 / p99 search latencyОперации → Observability
Доля 429 (rate-limit + quota)Дашборд → Analytics → Errors
Lag reindex (ingest → searchable)Дашборд → Indexes → История reindex
Typesense memory / CPUCoolify / Grafana (на шаред-кластере: только ops-команде)
Насыщение пулов PostgresCoolify / Grafana

Runbook'и — в Операции → Мониторинг и Операции → Troubleshooting.

Когда апскейлиться

Триггеры и рекомендованное действие:

СимптомВероятная причинаДействие
p95 поиска ползёт выше таргетаРазмер коллекции у потолка шаред-кластераАпгрейд тарифа или переход на dedicated
429 rate_limit_exceeded с одного ключаФронт стреляет search на каждое нажатиеDebounce клиента (200 мс); см. Rate limits
429 quota_exceeded стабильно до конца месяцаУстойчивый рост выше месячного кэпаАпгрейд тарифа, либо hard cap повыше на Business+
Reindex-job встают в очередьПараллельно триггернуто несколько reindexСериализовать на уровне приложения; alias-swap — по одному на индекс
Vector search заметно медленнее текстовогоDim векторов близки к потолку кластераСнизить dim (1 536 → 768) или dedicated

Smoke-тесты производительности

В репо лежит базовая нагрузочная утилита packages/loadtest. Запускать против staging до публичного запуска:

cd packages/loadtest && bun run smoke

Профиль по умолчанию: 10 параллельных виртуальных пользователей × 60 с × POST /search против коллекции _demo. Выхлоп — p50/p95/p99 + error rate.

Не запускайте load-тесты против app.aacsearch.com без согласования с ops — per-key rate limit включится и тест начнёт мерить throughput 429-х, а не поиска.

Что ещё почитать

On this page