Перейти до вмісту

Шардинг

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. Кількість файлів залишається низькою завдяки самій структурі.