
# Шардирование

`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 записей в коллекции начинают иметь значение число записей в каждой корзине и общее использование inode: разделите коллекции или используйте сегментированный журнал для данных, рассчитанных на сканирование; он изначально поддерживает небольшое число файлов.

## Другие движки

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