Путь записи
Как запись документа попадает в AACsearch — от POST /v1/indexes/.../documents через SearchIngestBuffer / SearchSyncOutbox и воркер в alias Typesense.
AACsearch работает по принципу DB-first: каждая запись сначала приземляется в PostgreSQL и только затем проецируется в Typesense. HTTP-слой никогда не вызывает Typesense синхронно из клиентского запроса. Это Hard Invariant #2 — от него зависят долговечность, обработка частичных отказов и zero-downtime-переиндексация.
Поток
Описание
Диаграмма показывает синхронную со стороны клиента HTTP-запись, которая аутентифицируется, проходит rate-limit и проверку квоты, а затем долговечно ставится в очередь в PostgreSQL (SearchIngestBuffer / SearchSyncOutbox) — клиент сразу получает 202 Accepted. Фоновый воркер позже забирает ожидающие строки, прикрепляет эмбеддинги и импортирует батч в alias Typesense, сверяя успех или отказ по строкам с экспоненциальным backoff.
sequenceDiagram
autonumber
participant Client as Customer / Connector
participant API as packages/api/v1/documents.ts
participant Auth as verifySearchApiKey (scope=ingest)
participant DB as PostgreSQL (SearchIngestBuffer + SearchSyncOutbox)
participant W as Sync worker (sync-worker.ts)
participant Embed as autoEmbedDocuments
participant TS as Typesense alias_name(orgShortId_slug)
Client->>API: POST /v1/indexes/:indexId/documents:batch
API->>Auth: Bearer ss_search_* / ss_connector_*
Auth-->>API: VerifiedSearchKey { organizationId, indexId }
API->>API: rate-limit (per-key, 1m sliding bucket)
API->>API: enforceQuota (plan / overage)
API->>DB: enqueueManySearchIngest() (or SearchSyncOutbox doc_upsert)
API-->>Client: 202 Accepted (jobId)
loop worker tick
W->>DB: claim pending rows (atomic updateMany + lockedBy)
W->>Embed: autoEmbedDocuments(batch)
Embed-->>W: vectors attached
W->>TS: collection.documents().import(batch, action=upsert)
alt all green
W->>DB: markIngestRowsSuccess / outbox.status=done
else partial fail
W->>DB: markIngestRowsFailure + nextRetryAt (exp. backoff)
end
endЧто гарантирует каждый шаг
- Auth (
verifySearchApiKey). Токен хешируется (sha256) и сравнивается с колонкойSearchApiKey.hash. Коннекторные ключи (ss_connector_*) и поисковые ключи (ss_search_*) разделяют пространство хешей; авторизацию определяет колонкаscopes. - Rate limit. Скользящее окно по ключу из
SearchRateLimitBucket. ПревышениеrateLimitPerMinuteвозвращает 429 ещё до любой записи в БД. - Quota gate. Право плана и overage кошелька проверяются один раз за запрос; квота на запись расходуется атомарно вместе с enqueue.
- Долговечный enqueue. Строки пишутся в
SearchIngestBuffer(legacy-путь) илиSearchSyncOutbox(канонический, идемпотентный путь). HTTP-ответ —202: документ не обязан быть в Typesense к моменту возврата клиенту. - Проекция воркером. Фоновый процесс забирает строки с
lockedBy = WORKER_ID, выполняет авто-эмбеддинг, если в индексе есть векторное поле, вызываетcollection.documents().import()и согласует успех / отказ построчно. - Цель alias. Воркер всегда пишет в
aliasName(organizationId, slug), который указывает на текущую физическую версию коллекции. Переиндексация переключает alias атомарно; записи в полёте следуют новому указателю.
Почему DB-first
- Долговечность. Перезапуск сервера или сбои Typesense не теряют записи.
- Восстановление при частичных отказах. Повторяются только упавшие строки; успешные не дублируются.
- Изоляция тенантов. Воркер тегирует каждый документ полем
tenantIdперед импортом; alias принудительно применяетfilter_byна чтении. - Backpressure. Буфер впитывает пиковые full-sync'и CMS, не перегружая Typesense.
См. также
- Жизненный цикл коннектора — как CMS-модули питают тот же путь записи.
- Путь чтения — зеркальный поток для запросов.
- Переиндексация и zero-downtime — механика переключения alias.
Архитектура
Визуальный справочник по внутреннему устройству AACsearch — путь записи, путь чтения, модель безопасности, жизненный цикл коннектора и аналитический контур обратной связи.
Путь чтения
Как поисковый запрос проходит через AACsearch — от клиентского SDK к /search/public/multi, через public-auth и сборку tenant-фильтра, в multi_search Typesense и обратно в виде очищенного ответа.