Chemin d'écriture
Comment une écriture de document entre dans AACsearch — depuis POST /v1/indexes/.../documents via SearchIngestBuffer / SearchSyncOutbox et le worker, jusqu'à l'alias Typesense.
AACsearch est DB-first : chaque écriture atterrit d'abord dans PostgreSQL avant d'être projetée vers Typesense. La couche HTTP n'appelle jamais Typesense de manière synchrone depuis une requête client. C'est la Hard Invariant #2 — la durabilité, la gestion des échecs partiels et le reindex sans interruption en dépendent.
Flux
Description
Le diagramme montre une écriture HTTP synchrone côté client qui est authentifiée, soumise au rate-limit et au gate de quota, puis mise en file de manière durable dans PostgreSQL (SearchIngestBuffer / SearchSyncOutbox) — le client reçoit immédiatement 202 Accepted. Un worker en arrière-plan réclame ensuite les lignes en attente, attache les embeddings et importe le batch dans l'alias Typesense, en réconciliant succès ou échec ligne par ligne avec un backoff exponentiel.
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
endCe que garantit chaque étape
- Auth (
verifySearchApiKey). Le token est haché (sha256) puis comparé à la colonneSearchApiKey.hash. Les clés de connecteur (ss_connector_*) et de recherche (ss_search_*) partagent le même espace de hash ; la colonnescopespilote l'autorisation. - Rate limit. Fenêtre glissante par clé issue de
SearchRateLimitBucket. DépasserrateLimitPerMinuterenvoie 429 avant toute écriture en base. - Gate de quota. Les entitlements du plan et l'overage wallet sont vérifiés une fois par requête ; le quota d'écriture est consommé atomiquement avec l'enqueue.
- Enqueue durable. Les lignes sont écrites dans
SearchIngestBuffer(chemin legacy) ouSearchSyncOutbox(chemin canonique, idempotent). La réponse HTTP est202— le document n'a pas besoin d'être dans Typesense avant que le client ne reçoive sa réponse. - Projection par le worker. Un process en arrière-plan réclame les lignes avec
lockedBy = WORKER_ID, exécute l'auto-embedding si l'index a un champ vectoriel, appellecollection.documents().import()et réconcilie succès / échec ligne par ligne. - Cible alias. Le worker écrit toujours dans
aliasName(organizationId, slug), qui pointe sur la version physique courante de la collection. Le reindex permute l'alias atomiquement ; les écritures en vol suivent le nouveau pointeur.
Pourquoi DB-first
- Durabilité. Les redémarrages serveur ou les pannes Typesense ne perdent pas d'écritures.
- Récupération d'échecs partiels. Seules les lignes en échec sont rejouées ; les succès ne sont pas dupliqués.
- Isolation tenant. Le worker tague chaque document avec le champ
tenantIdavant l'import ; l'alias appliquefilter_byà la lecture. - Backpressure. Le buffer absorbe les full-syncs en pic du CMS sans surcharger Typesense.
Liens associés
- Cycle de vie du connecteur — comment les modules CMS alimentent ce même chemin d'écriture.
- Chemin de lecture — le flux miroir pour les requêtes.
- Reindex et zero-downtime — mécanique de la permutation d'alias.
Architecture
Référence visuelle des rouages internes d'AACsearch — chemin d'écriture, chemin de lecture, modèle de sécurité, cycle de vie du connecteur et boucle de feedback analytique.
Chemin de lecture
Comment une requête de recherche transite dans AACsearch — du SDK client à /search/public/multi, en passant par public-auth et la combinaison du filtre tenant, jusqu'au multi_search de Typesense, puis retour en réponse assainie.