> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conare.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Écritures et ingestion en masse

> Sémantique d'upsert, patch de propriétés et suppression, encodages vecteur JSON et fp16 base64, compaction et chemin binaire d'ingestion en masse.

## Cycle de vie des lignes

Trois types d'étapes couvrent le cycle de vie clé sur id, tous via `POST /v1/namespaces/{ns}/query` :

```json theme={null}
{"UpsertN": {"id": "chunk-123", "label": "chunk", "props": {"text": "..."}, "vector": [0.1]}}
{"SetProps": {"props": {"project": "v2", "stale_key": null}}}
{"DeleteN": {"ids": ["chunk-123"]}}
```

* **`UpsertN`** — remplacement de ligne complet clé sur l'`id` externe. Les lignes existantes avec cet id sont supprimées, puis la nouvelle ligne est insérée. Remplacement, pas fusion : un upsert sans vecteur d'une ligne vectorisée supprime le vecteur.
* **`SetProps`** — écrase des clés spécifiques sur un flux correspondant (débutez la requête par `NWhere`) ; une valeur `null` supprime la clé. `id` lui-même ne peut être modifié — c'est un `UpsertN`. Approprié pour des réécritures d'attributs occasionnelles, inapproprié pour des compteurs par requête.
* **`DeleteN`** — supprime un flux correspondant (`NWhere` → `DeleteN`), ou passe `ids` pour des suppressions par id externe direct.

## Encodages de vecteurs

Par ligne, l'un des deux encodages sur fil (envoyer les deux est un `400`) :

```json theme={null}
{"vector": [0.0123, -0.0456]}
{"vector_b64": "zcxMPZqZmb4K16M8..."}
```

`vector_b64` est le base64 du vecteur en tant que **tableau f32 little-endian** — \~4× plus petit sur le fil et décodé par une memcpy plutôt qu'un parsing JSON de nombres. Encodeur Python :

```python theme={null}
base64.b64encode(np.asarray(vec, dtype="<f4").tobytes()).decode("ascii")
```

Les deux encodages produisent des vecteurs stockés identiques au bit et des résultats de recherche identiques. La dimension est fixée par la première écriture vectorisée ; une incompatibilité est un `400`.

## Ingestion en masse : `POST /v1/namespaces/{ns}/bulk-vectors`

Le chemin d'amorçage à haut débit — une trame binaire par requête (`Content-Type: application/octet-stream`) : un petit en-tête JSON (`dims`, `label`, `ids`, `props` partagés) suivi de valeurs **fp16 L2-normalisées** en ligne par ligne. Comparé à JSON+base64, cela évite de ré-encoder des charges de plusieurs Go plusieurs fois.

### Chargement sûr au redémarrage

Les chargeurs reprenables passent `?expected_generation=<G>&expected_rows=<N>`. La trame n'est acceptée que lorsque le namespace est exactement à cet état :

* Même trame déjà committée → `200` avec `"replayed": true` et le `request_fingerprint` correspondant — pas de lignes dupliquées.
* Charge différente à la même position → `409 write_conflict`.
* Les écritures conditionnelles réussies renvoient `request_fingerprint`, `rows_before`, `rows_after` — validez votre batch uniquement après avoir reçu l'un de ces reçus.

Cela fait de « crash, redémarrage, renvoi » toute l'histoire de reprise : pas de job d'import à surveiller.

## Compaction

L'auto-compaction est géométrique, donc le travail total de compaction reste linéaire en octets ingérés. Terminez tout chargement en masse par une compaction explicite — envoyez `"compact": true` au niveau supérieur de la **dernière** requête d'écriture :

```json theme={null}
{"request_type": "write", "compact": true, "query": {"...": "..."}}
```

La réponse indique `"compacted": true/false`. La compaction construit également l'index ANN lorsque la table est suffisamment grande ; les lignes écrites après le snapshot d'index sont fusionnées en brute-force dans les résultats, si bien que la fraîcheur n'est jamais sacrifiée.

## Déduplication d'une source en masse

Les trames en masse ajoutent aveuglément par conception — une source qui contient des ids dupliqués les stocke silencieusement, et les doublons occupent des places dans le top-k. `{"DedupN": {}}` réconcilie un magasin avec le contrat d'unicité par id externe (garde la dernière ligne écrite par id), et `{"DedupN": {"dry_run": true}}` est l'audit de recensement seul que tout chargement en masse devrait exécuter lorsque sa source ne peut prouver l'unicité des ids — un nombre total de lignes correspondant ne peut révéler les doublons cachés à l'intérieur.

## Règles de réessai

Les lectures peuvent toujours être réessayées après un échec de transport. Pour les écritures, seules les trames conditionnelles en masse sont automatiquement réessayables (trame identique + paramètres CAS → reçu original ou `replayed: true`). Ne rejouez jamais aveuglément une écriture non conditionnelle après une réponse perdue — réconciliez d'abord l'état.
