> ## 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.

# Writes & Bulk-Ingest

> Row-Upsert, Prop-Patch und Delete-Semantik, JSON- und base64-fp16-Vektor-Encodings, Hintergrund-Compaction sowie der binäre bulk-vectors-Ingest-Pfad.

## Row-Lifecycle

Drei Step-Arten decken den id-basierten Lifecycle ab, alle über `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`** – vollständiger Zeilenersatz mit externer `id` als Schlüssel. Bestehende Zeilen mit dieser id werden gelöscht, danach wird die neue Zeile eingefügt. Ersetzen, nicht mergen: Ein vektorloser Upsert einer vektorisierten Zeile lässt den Vektor fallen.
* **`SetProps`** – überschreibt bestimmte Keys auf einem gematchten Stream (Query mit `NWhere` starten); ein `null`-Wert entfernt den Key. `id` selbst kann nicht geändert werden – dafür ist ein `UpsertN` da. Richtig für gelegentliches Umschreiben von Attributen, falsch für Zähler pro Query.
* **`DeleteN`** – löscht einen gematchten Stream (`NWhere` → `DeleteN`) oder übergibt `ids` für direkte externe-id-Löschungen.

## Vektor-Encodings

Pro Zeile eine von zwei Wire-Encodings (beide zu senden ergibt ein `400`):

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

`vector_b64` ist Base64 des Vektors als **Little-Endian f32-Array** – ca. 4× kleiner auf der Leitung und mit einem memcpy statt JSON-Zahlen-Parsing dekodiert. Python-Encoder:

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

Beide Encodings erzeugen bitgleiche gespeicherte Vektoren und identische Suchergebnisse. Die Dimension wird durch den ersten vektorisierten Schreibvorgang festgelegt; ein Mismatch ergibt ein `400`.

## Bulk-Ingest: `POST /v1/namespaces/{ns}/bulk-vectors`

Der Hochdurchsatz-Seed-Pfad – ein binärer Frame pro Request (`Content-Type: application/octet-stream`): ein kleiner JSON-Header (`dims`, `label`, `ids`, geteilte `props`) gefolgt von row-major **L2-normalisierten fp16**-Werten. Verglichen mit JSON+Base64 vermeidet das mehrfaches Neucodieren von Multi-GB-Payloads.

### Restart-sicheres Laden

Wiederaufnehmbare Loader übergeben `?expected_generation=<G>&expected_rows=<N>`. Der Frame wird nur akzeptiert, wenn der Namespace genau in diesem Zustand ist:

* Derselbe Frame ist bereits committet → `200` mit `"replayed": true` und passendem `request_fingerprint` – keine Duplikatzeilen.
* Andere Payload an derselben Position → `409 write_conflict`.
* Erfolgreiche konditionale Writes liefern `request_fingerprint`, `rows_before`, `rows_after` zurück – checkpoint deinen Batch erst nach einer solchen Quittung.

Damit ist „Crash, Restart, erneut senden" die vollständige Resume-Story: keine Import-Jobs zu babysitten.

## Compaction

Auto-Compaction ist geometrisch, sodass die gesamte Compaction-Arbeit linear zu den ingested Bytes bleibt. Schließe jeden Bulk-Load mit einem expliziten Compact ab – sende `"compact": true` auf oberster Ebene des **letzten** Write-Requests:

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

Die Response meldet `"compacted": true/false`. Compaction baut auch den ANN-Index, sobald die Tabelle groß genug ist; Zeilen, die nach dem Index-Snapshot geschrieben wurden, werden per Brute Force in die Ergebnisse gemerged, damit Frische nie geopfert wird.

## Eine Bulk-Quelle deduplizieren

Bulk-Frames hängen bewusst blind an – eine Quelle mit doppelten ids landet sie stumm, und Duplikate verbrennen Top-k-Slots. `{"DedupN": {}}` gleicht einen Store zur externen id-Eindeutigkeitszusage ab (behält die zuletzt geschriebene Zeile pro id), und `{"DedupN": {"dry_run": true}}` ist der reine Zensus-Audit, den jeder Bulk-Load ausführen sollte, wenn seine Quelle keine id-Eindeutigkeit garantieren kann – eine passende Gesamt-Zeilenzahl kann darin verborgene Duplikate nicht enttarnen.

## Retry-Regeln

Lesevorgänge können nach einem Transportfehler stets wiederholt werden. Bei Writes sind nur konditionale Bulk-Frames automatisch retry-fähig (identischer Frame + CAS-Params → ursprüngliche Quittung oder `replayed: true`). Wiederhole einen unkonditionalen Write nie blind nach verlorener Response – gleiche zuerst den Zustand ab.
