armdb 0.1.13

sharded bitcask key-value storage optimized for NVMe
Documentation
# План: Дизайн документ FixedStore для armdb

## Context

armdb использует Bitcask (append-only log) для всех типов записей. Для данных фиксированного размера с частыми обновлениями (счётчики, статусы, метрики) это неоптимально: каждый update создаёт новую запись на диске, требует compaction, увеличивает write amplification. Нужен альтернативный storage engine — **FixedStore** — оптимизированный для in-place updates на NVMe.

## Что создаём

Файл: `armdb/docs/fixed_store_design.md` — дизайн документ в стиле существующего `armdb/docs/design.md` (на русском).

## Структура документа

### 1. Цели и применение
- Фиксированный размер записей, частые обновления
- Use cases: счётчики, rate limiters, сессии, статусы, метрики

### 2. Архитектура: Page-aligned fixed-slot store
- Slot = key + value фиксированного размера
- `offset = slot_id * slot_size` — прямая адресация
- In-place update вместо append
- Шардирование (переиспользовать существующий `shard_for()` из `armdb/src/engine.rs`)

### 3. Формат файла на диске
- File header (4KB page 0): magic, version, slot_size, slot_count, free bitmap
- Data pages: слоты выровнены по 4KB страницам NVMe
- Free list / bitmap для отслеживания свободных слотов

### 4. Формат слота на диске
- Сравнение с текущим EntryHeader (16 bytes) из `armdb/src/entry.rs`
- Slot header: status (1 byte) + CRC32 (4 bytes) + key + value
- Без GSN? Или с GSN для репликации?

### 5. Индексы — переиспользовать существующие
- **Решение: использовать существующие SkipList и HashMap** из `armdb/src/skiplist/` и `armdb/src/hashmap/`
- Вместо `DiskLoc { offset, len, file_id, shard_id }` хранить `SlotLoc { slot_id, shard_id }` или просто переиспользовать DiskLoc (offset = slot_id * slot_size)
- ConstTree/ConstMap уже хранят значения inline — для FixedStore индекс просто маппит key → slot_id
- Не нужно создавать новые индексы

### 6. Read/Write paths
- Read: index lookup → compute offset → pread (O_DIRECT, 4KB aligned)
- Write: index lookup → compute offset → pwrite in-place
- io_uring для батчинга (переиспользовать `armdb/src/io/uring.rs`)

### 7. Crash safety
- Вариант A: NVMe atomic page write (запись ≤ 4KB)
- Вариант B: WAL для записей, пересекающих границу страницы
- Сравнение с текущим подходом (без WAL, потеря write buffer)

### 8. Сравнительный анализ с Bitcask

#### Write amplification
- Bitcask: каждый update = полная запись (header 16B + key + value + padding)
- FixedStore: каждый update = перезапись только слота

#### Disk usage
- Bitcask: мёртвые записи до compaction, space amplification 1.3-2x
- FixedStore: 1x, нет мусора

#### Compaction
- Bitcask: обязательна, CPU + I/O + complexity
- FixedStore: не нужна вообще

#### Latency (расчёты)
- Bitcask put: ~300-800ns (memcpy в buffer, без disk I/O)
- FixedStore put: ~10-20μs (NVMe pwrite), или ~300ns с write buffer
- Bitcask get (ConstTree): ~50-400ns (in-memory)
- FixedStore get: ~50-400ns (in-memory, как ConstTree) или ~10-20μs (disk read)

#### Throughput
- Bitcask: ~50-60M reads/sec, ~30-50M writes/sec (in-memory index)
- FixedStore с in-memory index: аналогично для reads; writes ограничены NVMe IOPS (~1-3M random writes/sec)

#### Recovery time
- Bitcask: scan всех файлов O(disk_size)
- FixedStore: scan файла O(n_slots) или использование bitmap

### 9. Преимущества FixedStore
- Нет compaction → предсказуемая latency, нет background I/O storms
- Нет write amplification от мёртвых записей
- Экономия диска: ровно N * slot_size (+ header)
- Простота реализации и отладки
- Идеально для NVMe random write

### 10. Ограничения / Минусы
- Только фиксированный размер записей
- Нет append-only семантики → сложнее репликация (нет GSN-based log shipping)
- При crash без WAL — частично записанный слот → нужен CRC для детектирования
- Нет пространственной локальности при sequential scan (записи разбросаны по слотам)
- Удаление = пометка свободного слота, не физическое удаление
- Фрагментация при частых insert/delete

### 11. Конфигурация
- `slot_size`, `max_slots_per_file`, `shard_count` (immutable)
- `enable_fsync`, `write_buffer_size` (tunable)

### 12. Feature flag
- `feature = "fixed_store"` в armdb

## Ключевые файлы для референса

| Файл | Что берём |
|------|-----------|
| `armdb/docs/design.md` | Формат и стиль документа |
| `armdb/docs/performance.md` | Формат таблиц latency/throughput |
| `armdb/src/entry.rs` | EntryHeader (16B), entry_size(), serialize_entry() |
| `armdb/src/disk_loc.rs` | DiskLoc struct (offset, len, file_id, shard_id) |
| `armdb/src/config.rs` | Config struct, параметры |
| `armdb/src/shard.rs` | WriteBuffer, Shard, GLOBAL_GSN |
| `armdb/src/compaction.rs` | CompactionIndex trait |
| `armdb/src/io/uring.rs` | UringWriter |
| `armdb/src/skiplist/` | Существующие индексы |

## Верификация

- Документ проверяется визуально (markdown)
- Числа latency/throughput должны быть консистентны с `performance.md`
- Архитектурные решения должны быть совместимы с существующими abstractions (Key trait, DiskLoc, indices)