Шардинг
DocStore записує один файл на кожен запис. Без шардингу велика колекція
складала б усі файли в одному каталозі, а операції з каталогом сповільнювалися б
зі збільшенням кількості записів. Тому storh розподіляє файли записів за двома символами
ідентифікатора запису:
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. Кількість файлів залишається низькою завдяки самій структурі.