armdb 0.4.1

sharded bitcask key-value storage optimized for NVMe
Documentation
# armdb — Стратегия тестирования

---

## Текущее покрытие (~186 тестов)

Покрыто: CRUD, итераторы (22 теста), recovery (8 тестов вкл. truncated entries),
конкурентность (7 тестов), компакция (6 тестов вкл. concurrent + double),
все кодеки (rapira, bytemuck, zerocopy), WriteHook (8 тестов), migrate (5 тестов),
typed/zero деревья, shard prefix routing, стресс-тесты (12 тестов `#[ignore]`),
loom permutation testing для SkipList (7 тестов, `--features loom`),
Miri UB detection для SkipList (9 тестов, `cargo +nightly miri`).
Паттерн — `tempdir()` + малый датасет (100-1000 записей) для unit tests,
zipfian + 10K-1M записей для stress tests.

**Готово:**
- Фаззинг: 5 fuzz targets (entry, recovery, byte_view, hint) + 2 proptest model tests (BTreeMap comparison)
- Стресс: concurrent ConstTree (6 threads + compaction + flush), crash recovery (fork + SIGKILL × 50), large dataset (1M entries)
- Compaction: concurrent reads/writes (4 теста), double compaction, aggressive pressure

**Главные пробелы:**
- ~~Нет бенчмарков KV-операций~~**готово** (`kv_ops` + `kv_ops_full`, см. раздел 2)
- Concurrent VarTree compaction — transient read corruption, см. `docs/bugs/concurrent-compaction-var-read.md`
- Нет encryption + recovery + compaction комбинированных тестов
- Нет larger-than-RAM профиля

---

## 1. Фаззинг

### a) `cargo-fuzz` (libFuzzer) — 5 targets (готово)

run:
```bash
cargo +nightly fuzz run --fuzz-dir armdb/fuzz fuzz_entry_header  -- -runs=1000
cargo +nightly fuzz run --fuzz-dir armdb/fuzz fuzz_entry_roundtrip  -- -runs=1000
cargo +nightly fuzz run --fuzz-dir armdb/fuzz fuzz_recovery  -- -runs=1000
cargo +nightly fuzz run --fuzz-dir armdb/fuzz fuzz_byte_view  -- -runs=1000
cargo +nightly fuzz run --fuzz-dir armdb/fuzz fuzz_hint_parse  -- -runs=1000
```

| Target | Что фаззит |
|--------|-----------|
| `fuzz_entry_header` | EntryHeader десериализация, CRC валидация |
| `fuzz_entry_roundtrip` | Entry encode/decode + CRC + bit-flip corruption |
| `fuzz_recovery` | Recovery из malformed `.data` файлов (не должен паниковать) |
| `fuzz_byte_view` | ByteView inline/heap boundary (20 байт), refcounting |
| `fuzz_hint_parse` | Hint file parsing для разных key sizes |

### b) Loom — permutation testing для lock-free SkipList (готово)

7 тестов в `armdb/tests/loom_skiplist.rs`. Запуск: `cargo test -p armdb --features loom --test loom_skiplist`.

| Тест | Сценарий | Что проверяет |
|------|----------|---------------|
| `concurrent_get_insert` | get ‖ insert | Reader не видит partial node |
| `concurrent_get_remove` | get ‖ remove | Reader корректно пропускает marked nodes |
| `insert_remove_same_key` | insert ‖ remove (один ключ) | Оба ordering через write_lock корректны |
| `concurrent_iter_insert` | iter ‖ insert | Iterator не паникует; weakly-consistent |
| `concurrent_iter_remove` | iter ‖ remove | Iterator пропускает marked nodes |
| `concurrent_swap_load_data` | swap_data ‖ load_data | RCU: reader видит old или new, не torn |
| `concurrent_swap_load_disk` | swap_disk ‖ load_disk | RCU: reader видит old или new, не torn |

Реализация: conditional `loom::sync::atomic` вместо `std::sync::atomic` (feature `loom`),
`loom::sync::Mutex` вместо `parking_lot::Mutex`, `seize::retire` отключён под loom (leak допустим).

### c) Miri — UB detection для SkipList (готово)

9 тестов в `armdb/tests/miri_skiplist.rs`. Запуск: `cargo +nightly miri test -p armdb --test miri_skiplist`.

**Что ловит (дополняет loom):**
- Stacked/Tree Borrows violations в 54 unsafe блоках
- Use-after-free через реальный `seize::retire` (loom его отключает!)
- Double-free, invalid pointer dereference
- Data races в concurrent тестах

| Тест | Категория | Что проверяет |
|------|-----------|---------------|
| `single_insert_get_remove` | single | Полный lifecycle: alloc → insert → get → remove → retire → Drop |
| `single_overwrite_rcu_const` | single | ConstNode RCU: swap_data/load_data, Stacked Borrows |
| `single_overwrite_rcu_var` | single | VarNode RCU: swap_disk/load_disk |
| `single_drop_populated` | single | Drop 3 нод: нет double-free |
| `single_iter_full` | single | Iterator pointer validity |
| `single_insert_exists` | single | Rejected node freed correctly |
| `concurrent_get_insert` | multi | Reader vs writer: data race detection |
| `concurrent_get_remove` | multi | Reader vs remover с реальным retire |
| `concurrent_iter_modify` | multi | Iterator vs concurrent insert+remove |

Concurrent тесты: `MIRIFLAGS="-Zmiri-preemption-rate=0.1"` для exploration разных schedulings.
Несколько seeds: `MIRIFLAGS="-Zmiri-seed=N -Zmiri-preemption-rate=0.1"`.

**Ключевое**: seize v0.5.1 Miri-совместим (`#[cfg(not(miri))]` на platform barriers).
Retire **активен** под Miri — в отличие от loom, Miri тестирует реальный путь рекламации.

### d) `proptest` — модельный фаззинг (готово, можно расширять)

Два теста в `proptest_model.rs`: `const_tree_model_test` и `var_tree_model_test`.
Сравнение с BTreeMap, до 200 операций за sequence (Put, Get, Delete, Insert, CAS, Compact, CloseReopen, Iter, PrefixIter).

**Можно улучшить:** увеличить keyspace (сейчас 4 группы × 16 ID), увеличить max ops, добавить concurrent model test.

---

## 2. Бенчмарки (criterion) — готово

Два таргета, оба `harness = false`. Общие хелперы — в `armdb/benches/common/`.

### Таргеты

| Таргет | Назначение | Данные |
|--------|-----------|--------|
| `kv_ops` | Быстрый прогон для частого локального запуска | ~50k записей, page-cache only |
| `kv_ops_full` | Широкое сравнение по семействам коллекций и режимам I/O | ~250k записей, page-cache + direct-io |

Матрица коллекций (оба таргета): `ConstTree`, `TypedTree`, `VarTree`, `FixedTree`,
`ConstMap`, `TypedMap`, `VarMap`, `FixedMap`. Профили: `seq-write`, `rand-write`,
`seq-read`, `rand-read-hot`, `read-write-mix`, `overwrite`, `scan` (только деревья).
`kv_ops_full` добавляет `rand-read-cold` (только `Var*`) и режимы `direct_io` для
Bitcask-коллекций.

### Команды

```sh
# Быстрый прогон
cargo bench -p armdb --bench kv_ops

# Короткий smoke (компиляция + минимальный прогон)
cargo bench -p armdb --bench kv_ops -- --sample-size 10 --measurement-time 1 --warm-up-time 1

# Полный прогон (долгий)
cargo bench -p armdb --bench kv_ops_full

# Сравнение с сохранённым базлайном (regression hunting)
cargo bench -p armdb --bench kv_ops -- --save-baseline before
# ... изменения ...
cargo bench -p armdb --bench kv_ops -- --baseline before

# Tail-latency probes (диагностический stdout, выключены по умолчанию)
ARMDB_BENCH_LATENCY=1 cargo bench -p armdb --bench kv_ops_full -- 'latency'
```

### Конфигурация и оговорки

- Все профили используют `enable_fsync=false`. Durability-путь (fsync) не измеряется.
- `direct_io` режимы: `page_cache` (`direct_io=false`) и `direct_io` (`direct_io=true`),
  оба с `enable_fsync=false`. **`direct_io` требует реальной блочной ФС**: `tmpfs`/
  `overlayfs` (т.е. часто `/tmp` и `TempDir`) отклоняют `O_DIRECT`, и слой молча
  откатывается на буферизованный I/O — тогда строки `direct_io` и `page_cache`
  совпадают. Имя бенча всё равно отражает запрошенный конфиг, а не поведение ядра.
- `rand-read-cold` мерит **промах armdb block cache**, а не холодное чтение с диска:
  при ~250k записей датасет влезает в OS page cache, поэтому в режиме `page_cache`
  промах обслуживается из page cache. По-настоящему device-cold путь — только
  `rand-read-cold` под `direct_io=true`. Профиль применяется только к `Var*``Const*/Typed*/Fixed*` `get` не ходит на диск).
- Latency probes используют per-op `Instant::now()` — это добавляет overhead, поэтому
  они для относительного поиска регрессий хвоста, а не для точных наносекундных чисел.

**Остаётся пробелом:** larger-than-RAM профиль (нужен machine-specific sizing,
отдельная категория поведения).

---

## 3. Стресс / нагрузочное тестирование

### a) In-process stress tests (готово)

Все тесты в `armdb/tests/stress_tests.rs`, `#[ignore]`, запуск: `cargo test -- --ignored --nocapture`.

| Тест | Описание |
|------|----------|
| `stress_concurrent` | ConstTree: 6 threads × 100K ops + compaction + flush, zipfian 10K keyspace |
| `stress_concurrent_var` | VarTree: 6 threads × 100K ops, 16MB cache. **Compaction отключён** — transient corruption |
| `stress_compaction_pressure` | ConstTree: 100K inserts, double overwrite, double compact, close/reopen verify |
| `stress_large_dataset` | VarTree: 1M entries × 512B, zipfian/uniform/mixed reads, 4MB cache |
| `stress_var_full_lifecycle` | VarTree: 6 threads + compaction + flush + 32KB files + reopen verify |
| `stress_var_single_thread` | VarTree: 100K single-thread puts, isolates concurrency |
| `stress_var_bisect/minimal/no_rotation/rotation_no_cache` | Isolation тесты для block cache бага |

Инфраструктура: Zipfian distribution, `make_key`/`make_value`/`verify_value` helpers, `Config::test()`.

### b) Crash testing (готово)

| Тест | Описание |
|------|----------|
| `stress_crash_recovery` | ConstTree: 50 итераций fork + SIGKILL + recovery verify |
| `stress_crash_recovery_var` | VarTree: 50 итераций fork + SIGKILL + recovery verify, block cache 4MB |

### c) Larger-than-RAM тест — пробел

Рекомендация: 10GB данных (values по 1KB), 64MB cache → cache miss rate ~90%+, мерить p99 latency.

---

## 4. Недостающие correctness тесты

| Сценарий | Статус |
|----------|--------|
| Concurrent VarTree compaction + reads | **Открытый баг** — transient corruption, см. `docs/bugs/concurrent-compaction-var-read.md` |
| Recovery после truncated entry | Готово (4 теста в `recovery_truncated_tests.rs`) |
| Compaction + concurrent reads/writes (ConstTree) | Готово (4 теста в `compaction_concurrent_tests.rs` + `stress_concurrent`) |
| Encryption + recovery + compaction | Пробел |
| `WriteHook` + compaction | Пробел (hook тесты есть, но без concurrent compaction) |
| `migrate()` на большом дереве | Готово (5 тестов в `migrate_large_tests.rs`) |
| loom для SkipList | **Готово** — 7 тестов (get‖insert, get‖remove, insert‖remove, iter‖insert, iter‖remove, swap_data‖load_data, swap_disk‖load_disk) |

### Deterministic simulation testing (TigerBeetle / FoundationDB подход)

- Заменить I/O на детерминированный мок
- Инжектить ошибки: `pread` returns EIO, `write` partial, disk full
- Воспроизводимые сценарии из seed

---

## 5. Приоритеты

1. **Исправить concurrent VarTree compaction** — открытый баг, блокирует полноценное стресс-тестирование VarTree
2. **criterion бенчмарки** — без них невозможно оценить эффект оптимизаций
3. **encryption комбинированные тесты** — encryption + recovery + compaction
4. **larger-than-RAM профиль** — отдельная категория поведения