
# Partycjonowanie

`DocStore` zapisuje jeden plik na rekord. Bez partycjonowania duża kolekcja
zgromadziłaby wszystkie pliki w jednym katalogu, a operacje na katalogu zwalniają wraz ze wzrostem
liczby wpisów. Dlatego storh grupuje pliki rekordów w katalogach według dwóch znaków
identyfikatora:

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

Kubełek jest wyznaczany na podstawie początku ostatniej grupy UUID (znaki 24
i 25), która zawiera losowe dane. Dwa znaki szesnastkowe dają 256 kubełków, więc
kolekcja 100k rekordów zawiera średnio mniej niż 400 plików w katalogu.

## Dlaczego końcówka, a nie początek

Identyfikatory UUIDv7 zaczynają się od 48-bitowego znacznika czasu w milisekundach, więc identyfikatory utworzone w podobnym czasie mają te same początkowe znaki. Partycjonowanie według prefiksu kierowałoby każdą serię zapisów do tego samego intensywnie używanego katalogu, a kubełki zmieniałyby się dopiero z upływem czasu. Końcowe bajty są losowe dla każdego rekordu, dzięki czemu zapisy są równomiernie rozłożone od samego początku.

Partycjonowanie jest niewidoczne dla odczytów i zapytań: `get()` oblicza kubełek na podstawie
identyfikatora, a `stream()` zwraca rekordy posortowane według identyfikatora ze wszystkich kubełków, dzięki czemu
stronicowanie kursorem działa tak, jakby kolekcja była jedną uporządkowaną listą.

## Przy dużej skali

Liczba kubełków nigdy nie rośnie: dwa znaki szesnastkowe oznaczają 256 katalogów niezależnie od wielkości
kolekcji. Rośnie natomiast liczba plików w kubełku:

| Rekordy | Plików w kubełku |
| ------- | ---------------- |
| 10k     | ~40              |
| 100k    | ~390             |
| 1M      | ~3,900           |
| 10M     | ~39,000          |

Nowoczesne systemy plików indeksują wpisy katalogów, więc kilka tysięcy plików na kubełek pozwala utrzymać szybkie wyszukiwanie punktowe; 1M rekordów to komfortowa skala dla `get()` i zapytań korzystających z indeksów. Otwarcie magazynu zajmuje stały czas niezależnie od wielkości kolekcji: porządkowanie osieroconych plików tymczasowych odbywa się tylko wtedy, gdy znacznik nieaktywnego zapisu wskazuje, że poprzedni proces uległ awarii podczas zapisu. Operacje na całej kolekcji, które rosną liniowo wraz ze skalą, to skanowanie bez indeksu, `reindex()` i `verify()`, ponieważ każda z nich przechodzi przez wszystkie pliki rekordów. Traktuj je więc jako zadania wsadowe. Przy ponad około 10M rekordów w kolekcji zaczyna mieć znaczenie liczba plików w kubełku i łączna liczba i-węzłów: podziel kolekcje albo użyj dziennika segmentowego dla danych wymagających skanowania, który z założenia ogranicza liczbę plików.

## Pozostałe silniki

`SegmentedLog` i `Queue` nie wymagają partycjonowania, ponieważ nie tworzą jednego pliku na rekord. Dziennik dopisuje wpisy do plików segmentów w `segments/`, których rozmiar jest ograniczony przez `max_segment_bytes` (domyślnie 1 MiB), a kolejka dopisuje zdarzenia do jednego `queue.log`. Liczba plików pozostaje niska z założenia.
