Перейти к содержимому

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

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. Небольшое число файлов обеспечивается самой архитектурой.