
# Шардинг

`DocStore` записує один файл на кожен запис. Без шардингу велика колекція
складала б усі файли в одному каталозі, а операції з каталогом сповільнювалися б
зі збільшенням кількості записів. Тому storh розподіляє файли записів за двома символами
ідентифікатора запису:

```text
pages/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc
```

Ідентифікатор бакета береться з початку останньої групи ідентифікатора UUID (символи 24
і 25), де міститься випадкова ентропія. Два шістнадцяткові символи дають 256 бакетів, тож у колекції зі 100k записів у середньому буде менш ніж 400 файлів у каталозі.

## Чому кінець, а не початок

Ідентифікатори UUIDv7 починаються з 48-бітової часової мітки з точністю до мілісекунд, тому ідентифікатори, створені майже одночасно, мають однакові початкові символи. Шардинг за префіксом спрямовував би кожен сплеск записів до того самого завантаженого каталогу, а бакети змінювалися б лише з плином часу. Кінцеві байти випадкові для кожного запису, тож дані рівномірно розподіляються від самого першого запису.

Шардинг непомітний під час читання й запитів: `get()` визначає бакет за ідентифікатором, а `stream()` повертає записи, відсортовані за ідентифікатором у всіх бакетах, тож сторінкова навігація за курсором працює так, ніби колекція — це один упорядкований список.

## У масштабі

Кількість бакетів ніколи не зростає: два шістнадцяткові символи дають 256 каталогів за будь-якого розміру колекції. Зростає кількість файлів у кожному бакеті:

| Записів | Файлів у бакеті |
| ------- | ---------------- |
| 10k     | ~40              |
| 100k    | ~390             |
| 1M      | ~3,900           |
| 10M     | ~39,000          |

Сучасні файлові системи індексують записи каталогів, тому кілька тисяч файлів у бакеті забезпечують швидкий точковий пошук; 1M записів — зручний обсяг для `get()` та запитів з індексами. Відкриття сховища займає сталий час незалежно від розміру колекції: очищення загублених тимчасових файлів запускається лише тоді, коли маркер неактивного процесу свідчить про аварійне завершення попереднього процесу під час запису. Лінійно зі зростанням обсягу збільшуються операції над усією колекцією: сканування без індексів, `reindex()` та `verify()` обходять кожен файл запису, тож плануйте їх як пакетні завдання. Коли в колекції стає приблизно понад 10M записів, починають мати значення кількість файлів у бакетах і загальне використання вузлів файлової системи: розділяйте колекції або використовуйте сегментований журнал для даних, які потрібно сканувати, — він за своєю структурою зберігає кількість файлів низькою.

## Інші рушії

`SegmentedLog` та `Queue` не використовують шардинг, оскільки не створюють окремий файл для кожного запису. Журнал додає дані до сегментних файлів у `segments/`, розмір яких обмежено значенням `max_segment_bytes` (за замовчуванням 1 MiB), а черга додає події до одного `queue.log`. Кількість файлів залишається низькою завдяки самій структурі.
