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

# Записи та масовий інджест

> Семантика upsert рядків, патчу властивостей і видалення, кодування векторів JSON та base64 fp16, фонова компакція і бінарний шлях масового інджесту.

## Життєвий цикл рядка

Три види кроків покривають життєвий цикл за ключем-id, усі через `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`** — повна заміна рядка за зовнішнім `id`. Наявні рядки з цим id видаляються, потім вставляється новий. Заміна, не злиття: upsert без вектора над векторизованим рядком скидає вектор.
* **`SetProps`** — перезаписує конкретні ключі у відповідному потоці (починайте запит з `NWhere`); значення `null` видаляє ключ. Сам `id` змінити не можна — це `UpsertN`. Підходить для нечастих переписувань атрибутів, не підходить для лічильників за кожен запит.
* **`DeleteN`** — видаляє відповідний потік (`NWhere` → `DeleteN`) або передайте `ids` для прямого видалення за зовнішнім id.

## Кодування векторів

Для рядка — одне з двох wire-кодувань (відправляти обидва — це `400`):

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

`vector_b64` — це base64 вектора як **little-endian f32 масив** — приблизно у 4 рази менший на дроті і декодується memcpy замість парсингу чисел JSON. Кодер Python:

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

Обидва кодування дають бітово ідентичні збережені вектори та ідентичні результати пошуку. Розмірність фіксується першим векторизованим записом; невідповідність — це `400`.

## Масовий інджест: `POST /v1/namespaces/{ns}/bulk-vectors`

Високопропускний шлях засіву — один бінарний фрейм на запит (`Content-Type: application/octet-stream`): невеликий JSON-заголовок (`dims`, `label`, `ids`, спільні `props`), за яким ідуть значення **L2-унормованого fp16** у row-major порядку. У порівнянні з JSON+base64, це уникає повторного кодування багатогігабайтних payload-ів кілька разів поспіль.

### Restart-safe завантаження

Відновлювані завантажувачі передають `?expected_generation=<G>&expected_rows=<N>`. Фрейм приймається лише коли простір імен саме в цьому стані:

* Той самий фрейм уже комітнутий → `200` з `"replayed": true` і відповідним `request_fingerprint` — жодних дублювань рядків.
* Інший payload на тій самій позиції → `409 write_conflict`.
* Успішні умовні записи повертають `request_fingerprint`, `rows_before`, `rows_after` — робіть чекпоінт свого батчу лише після отримання однієї з цих квитанцій.

Це робить «краш, рестарт, повторна відправка» цілісною історією відновлення: жодних імпорт-джобів, які треба доглядати.

## Компакція

Автокомпакція геометрична, тож загальна робота компакції залишається лінійною за прийнятими байтами. Завершіть будь-яке масове завантаження явною компакцією — надішліть `"compact": true` на верхньому рівні **останнього** запиту запису:

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

Відповідь повідомляє `"compacted": true/false`. Компакція також будує ANN-індекс, коли таблиця стає достатньо великою; рядки, записані після знімка індексу, brute-force вливаються в результати, тож свіжістю ніколи не жертвують.

## Дедуплікація масового джерела

Масові фрейми за задумом дописують сліпо — джерело з дублікатами id тихо їх приземляє, а дублікати спалюють слоти top-k. `{"DedupN": {}}` узгоджує сховище з контрактом унікальності зовнішніх id (зберігає останній записаний рядок на id), а `{"DedupN": {"dry_run": true}}` — це аудит-перепис лише для census, який будь-яке масове завантаження має запускати, коли його джерело не може довести унікальність id — збіжна загальна кількість рядків не може розкрити дублікати, що ховаються всередині.

## Правила ретраїв

Читання завжди можна повторити після транспортного збою. Для записів автоматично ретраїзуються лише умовні bulk-фрейми (ідентичний фрейм + параметри CAS → оригінальна квитанція або `replayed: true`). Ніколи сліпо не переграйте безумовний запис після втраченої відповіді — спершу узгодьте стан.
