# Анализ бенчмарков (kv_ops.rs)

Текущая реализация бенчмарков в `armdb/benches/kv_ops.rs` имеет несколько логических изъянов, из-за которых цифры могут искажать реальную картину производительности. Ниже собраны ключевые наблюдения и рекомендации по исправлению.

## 1. Write-бенчмарки тестируют только In-Memory операции
Значение `WRITE_BATCH = 10_000`. Размер одной записи ~30 байт, что в сумме дает около 300 КБ данных на итерацию. Согласно архитектуре, `write_buffer_size` по умолчанию равен 1 МБ. 
Это означает, что во время выполнения циклов в `seq-write` и `rand-write` **буфер не переполняется и сброс на диск не происходит**. Измеряется только скорость вставки в `SkipList` / `HashMap` и `memcpy` в ring buffer.

**Как исправить:**
- Увеличить `WRITE_BATCH` до `100_000`–`500_000`, чтобы гарантированно триггерить I/O.
- Либо вызывать `tree.close().unwrap()` или `tree.flush_buffers().unwrap()` прямо внутри измеряемого замыкания (в `iter_batched`), чтобы замерять полный цикл с учетом I/O:
```rust
|(dir, tree)| {
    for i in 0..WRITE_BATCH { tree.put(...); }
    tree.close().unwrap(); // Замеряем сброс на диск
}
```

## 2. Загрязнение Read-бенчмарков через Write Buffer
В сценариях `seq-read`, `rand-read-hot`, `read-write-mix` база наполняется через `populate_*` (50_000 записей = ~1.5 МБ). При буфере 1 МБ первый мегабайт сбросится на диск, а последние ~500 КБ останутся в `WriteBuffer` активного шарда. 
В результате при вызовах `get(...)` часть ключей будет читаться из write буфера (что требует кратковременного захвата `shard.lock()`), а другая часть — с диска / из Block Cache.

**Как исправить:**
Сразу после фазы заполнения (`populate_*(&tree, NUM_ENTRIES);`) обязательно выполнять `tree.flush_buffers().unwrap();`, чтобы все данные легли на диск и тесты `get()` шли по честному (боевому) read-пути.

## 3. Неявное состояние Block Cache для VarTree
Везде используется конфигурация `Config::test()`. Если Block Cache по умолчанию в этом конфиге выключен (`max_size = 0`), `VarTree` будет делать системный вызов `pread` на каждый `get()`. Сравнение с `ConstTree` (где значения лежат inline в памяти) покажет неадекватную разницу.

**Как исправить:**
Для бенчмарков стоит явно инициализировать и задавать параметры кэша, чтобы тестировать конкретные сценарии (hit/miss):
```rust
let mut config = Config::test();
config.cache.max_size = 16 * 1024 * 1024; // 16 MB
```

## 4. Отсутствует профиль `rand-read-cold`
В стратегии тестирования заявлен профиль `rand-read-cold` (чтение из working set, размер которого больше кэша), но в коде реализован только `rand-read-hot`, где диапазон ключей (`0..1_000`) легко и гарантированно ложится в кэш. 

**Как исправить:**
Добавить бенчмарк `bench_rand_read_cold`, где диапазон выборки `NUM_ENTRIES` существенно превышает объем Block Cache, а чтение идет случайным образом. Это покажет реальную цену (penalty) на cache miss.

## 5. Скан измеряет только `ConstTree`
В `bench_scan` замеряются итераторы только для `ConstTree`. У этого дерева значения лежат в узлах `SkipList` (zero I/O). Для `VarTree` скан работает радикально иначе — он подтягивает блоки через Block Cache и диск, читая реальные файлы.

**Как исправить:**
Обязательно добавить аналогичные тесты итераторов и префикс-сканирования для `VarTree`.

## 6. Несбрасываемый счетчик в `overwrite`
В бенчмарке `overwrite` счетчик `counter` не сбрасывается между итерациями цикла `iter`. Поскольку `iter()` выполняется много раз в секунду, `counter` быстро улетит за значения `NUM_ENTRIES`.
Из-за `make_key(counter % NUM_ENTRIES)` новые ключи создаваться не будут (т.е. это честный оверрайт ключа), однако это означает, что будут генерироваться новые значения (`make_value(counter)` будет расти) и старые записи будут активно помечаться как `dead_bytes`, что может спровоцировать агрессивную компакцию прямо во время замера read/write производительности. Это допустимо, но важно понимать, что это скорее стресс-тест сборщика мусора, а не только чистый оверрайт.

## 7. Проблема длительного выполнения на macOS (APFS ловушка)
Особо стоит отметить причину, по которой `criterion` бенчмарки на macOS могут выполняться неоправданно долго (вплоть до часа): **использование `BatchSize::PerIteration` совместно с созданием/удалением файлов внутри цикла**.

В `seq-write` и `rand-write`:
```rust
group.bench_function("const_tree", |b| {
    b.iter_batched(
        || {
            let dir = TempDir::new().unwrap();
            let tree = ConstTree::<...>::open(dir.path(), Config::test()).unwrap();
            (dir, tree)
        },
        |(dir, tree)| {
            for i in 0..WRITE_BATCH { tree.put(...); }
            (dir, tree)
        },
        BatchSize::PerIteration, // <--- КОРЕНЬ ПРОБЛЕМЫ
    );
});
```

Внутри `iter_batched` Criterion будет сотни или тысячи раз (пока не истекут заданные `measurement_time` в 5 секунд):
1. Выполнять `setup` (создание директории и всех файлов БД).
2. Выполнять вставку (что в памяти займет пару миллисекунд).
3. Вызывать `Drop` (удаляя папку со всеми файлами).

На macOS это вызывает огромные задержки из-за:
1. **APFS Metadata Sync:** Удаление папок с файлами на APFS работает медленнее, чем на ext4, из-за синхронизации журнала файловой системы.
2. **Фоновые демоны (Spotlight и XProtect):** Как только создается файл, демоны macOS мгновенно пытаются его просканировать. Создание/удаление 10 000 папок за короткое время вызывает лавину прерываний (демоны `mds` и `kernel_task` забивают CPU).

Поскольку Criterion не учитывает время фазы `setup` и `teardown` в лимите `measurement_time`, тест визуально "зависает" на долгие минуты, ожидая пока macOS переварит удаление/создание тысяч директорий.

**Как исправить:**
Вместо того чтобы создавать директорию и файлы каждую итерацию (через `iter_batched`), лучше создать базу **ОДИН** раз до цикла `iter`, и писать туда батчами:
```rust
group.bench_function("const_tree", |b| {
    // Создаем директорию ОДИН раз
    let dir = TempDir::new().unwrap();
    let tree = ConstTree::<[u8; 8], 16>::open(dir.path(), Config::test()).unwrap();
    let mut counter = 0u64;

    b.iter(|| {
        // Пишем в одну и ту же БД последовательно
        for _ in 0..WRITE_BATCH {
            tree.put(&make_key(counter), &make_value(counter)).unwrap();
            counter += 1;
        }
        // (Опционально) tree.flush_buffers().unwrap(); чтобы замерить I/O
    });
});
```
Это полностью устранит влияние APFS на время выполнения тестов и позволит им пробегать за несколько минут.