
# Partitionnement

`DocStore` écrit un fichier par enregistrement. Sans partitionnement, une grande collection entasserait tous ses fichiers dans un seul répertoire, et les opérations sur ce répertoire ralentiraient à mesure que le nombre d’entrées augmente. storh répartit donc les fichiers d’enregistrement par groupes de deux caractères de l’identifiant :

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

Le groupe est déterminé par le début du dernier segment de l’identifiant UUID (caractères 24 et 25), qui contient des bits aléatoires. Deux caractères hexadécimaux donnent 256 groupes ; une collection de 100k enregistrements compte donc en moyenne moins de 400 fichiers par répertoire.

## Pourquoi utiliser la fin plutôt que le début

Les identifiants UUIDv7 commencent par un horodatage à la milliseconde sur 48 bits ; ceux créés à peu d’intervalle partagent donc les mêmes premiers caractères. Un partitionnement par préfixe concentrerait chaque pic d’écriture dans le même répertoire très sollicité et ne ferait tourner les groupes qu’au fil du temps. Les octets de fin sont aléatoires pour chaque enregistrement ; les écritures se répartissent donc uniformément dès le premier enregistrement.

Le partitionnement est invisible lors des lectures et des requêtes : `get()` calcule le groupe à partir de l’identifiant, et `stream()` renvoie les enregistrements triés par identifiant dans tous les groupes. La pagination par curseur se comporte donc comme si la collection était une seule liste ordonnée.

## À grande échelle

Le nombre de groupes ne change jamais : deux caractères hexadécimaux donnent 256 répertoires, quelle que soit la taille de la collection. C’est le nombre de fichiers par groupe qui augmente :

| Enregistrements | Fichiers par groupe |
| ------- | ---------------- |
| 10k     | ~40              |
| 100k    | ~390             |
| 1M      | ~3,900           |
| 10M     | ~39,000          |

Les systèmes de fichiers modernes indexent les entrées de répertoire : quelques milliers de fichiers par compartiment suffisent donc à garder les recherches ponctuelles rapides ; 1M d’enregistrements convient bien à `get()` et aux requêtes indexées. L’ouverture d’un magasin prend un temps constant, quelle que soit la taille de la collection : le nettoyage des fichiers temporaires orphelins ne s’exécute que lorsqu’un marqueur d’écriture abandonné indique qu’un processus précédent a planté en cours d’écriture. À grande échelle, les opérations qui croissent linéairement sont celles qui portent sur toute la collection : les parcours sans index, `reindex()` et `verify()` parcourent chacun tous les fichiers d’enregistrement ; traitez-les donc comme des tâches par lots. Au-delà d’environ 10M d’enregistrements par collection, le nombre de fichiers par compartiment et le nombre total d’inœuds commencent à compter : séparez les collections ou utilisez le journal segmenté pour les données parcourues séquentiellement, ce qui limite par conception le nombre de fichiers.

## Les autres moteurs

`SegmentedLog` et `Queue` ne répartissent pas les données, car ils ne créent pas un fichier par enregistrement. Le journal ajoute des données aux fichiers de segment sous `segments/`, avec une taille limitée par `max_segment_bytes` (1 MiB par défaut), et la file ajoute les événements à un seul `queue.log`. Le nombre de fichiers reste faible par conception.
