Schreibpfad
Wie ein Dokument-Write in AACsearch ankommt — von POST /v1/indexes/.../documents über SearchIngestBuffer / SearchSyncOutbox und den Worker bis in den Typesense-Alias.
AACsearch ist DB-first: Jeder Write landet zuerst in PostgreSQL, bevor er nach Typesense projiziert wird. Die HTTP-Schicht ruft Typesense niemals synchron aus einer Kundenanfrage heraus auf. Das ist Hard Invariant #2 — Haltbarkeit, Partial-Fail-Handling und Zero-Downtime-Reindex hängen davon ab.
Ablauf
Beschreibung
Das Diagramm zeigt einen aus Client-Sicht synchronen HTTP-Write, der authentifiziert, rate-limitiert und quota-geprüft wird und anschließend dauerhaft in PostgreSQL (SearchIngestBuffer / SearchSyncOutbox) eingereiht wird — der Kunde erhält sofort 202 Accepted. Ein Hintergrund-Worker übernimmt später die ausstehenden Zeilen, hängt Embeddings an und importiert den Batch in den Typesense-Alias, wobei Erfolg oder Fehlschlag pro Zeile mit exponentiellem Backoff abgeglichen wird.
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
endWas jeder Schritt garantiert
- Auth (
verifySearchApiKey). Das Token wird gehasht (sha256) und gegen die SpalteSearchApiKey.hashverglichen. Connector-Schlüssel (ss_connector_*) und Search-Schlüssel (ss_search_*) teilen sich den Hash-Raum; diescopes-Spalte bestimmt die Autorisierung. - Rate Limit. Pro-Schlüssel-Sliding-Window aus
SearchRateLimitBucket. Ein Überschreiten vonrateLimitPerMinuteliefert 429 vor jedem DB-Write. - Quota-Gate. Plan-Entitlements und Wallet-Overage werden einmal pro Request geprüft; das Schreib-Quota wird atomar mit dem Enqueue verbraucht.
- Haltbares Enqueue. Zeilen werden in
SearchIngestBuffer(Legacy-Pfad) oderSearchSyncOutbox(kanonischer, idempotenter Pfad) geschrieben. Die HTTP-Antwort ist202— das Dokument muss nicht in Typesense sein, bevor der Client zurückkehrt. - Worker-Projektion. Ein Hintergrundprozess klaut Zeilen mit
lockedBy = WORKER_ID, führt Auto-Embedding aus, wenn der Index ein Vektorfeld hat, ruftcollection.documents().import()auf und versöhnt Erfolg / Fehlschlag pro Zeile. - Alias-Ziel. Der Worker schreibt immer in
aliasName(organizationId, slug), das auf die aktuelle physische Collection-Version zeigt. Ein Reindex tauscht den Alias atomar; laufende Writes folgen dem neuen Pointer.
Warum DB-first
- Haltbarkeit. Server-Neustarts oder Typesense-Ausfälle verlieren keine Writes.
- Partial-Fail-Recovery. Nur fehlgeschlagene Zeilen wiederholen; erfolgreiche werden nicht dupliziert.
- Mandanten-Isolation. Der Worker markiert jedes Dokument mit dem Feld
tenantIdvor dem Import; der Alias erzwingtfilter_bybeim Lesen. - Backpressure. Der Buffer absorbiert stoßweise CMS-Full-Syncs, ohne Typesense zu überlasten.
Verwandt
- Konnektor-Lebenszyklus — wie CMS-Module denselben Schreibpfad speisen.
- Lesepfad — der spiegelnde Flow für Queries.
- Reindexing & Zero-Downtime — Mechanik des Alias-Swaps.
Architektur
Visuelle Referenz für die Interna von AACsearch — Schreibpfad, Lesepfad, Sicherheitsmodell, Konnektor-Lebenszyklus und der Analyse-Feedback-Loop.
Lesepfad
Wie eine Suchanfrage durch AACsearch fließt — vom Kunden-SDK über /search/public/multi, durch public-auth und die Mandanten-Filter-Kombination, in Typesense multi_search und zurück als sanitisierte Antwort.