Compression zstd Décidé
Compression par chunk, en zstd, à un seul emplacement possible : entre le découpage et l'AEAD. À 2 Mio de chunk, chaque chunk se compresse pleinement seul — donc pas de dictionnaire.
Où — et quand — la compression a lieu
Dans le pipeline décidé, il n’y a qu’un seul créneau viable : par chunk, après FastCDC, après la dérivation de la clé depuis le clair, et juste avant l’AEAD.
clé_chunk = BLAKE3_keyed(secret, chunk) # clé dérivée du CLAIR
payload = compresse(chunk) # ← la compression a lieu ICI
chiffré = XChaCha20-Poly1305(clé_chunk, payload)
adresse = BLAKE3(chiffré)
La clé reste dérivée du clair, pas du compressé : la compression ne change que le payload chiffré, jamais la logique de clé ni de manifeste. Les trois autres emplacements sont exclus :
- Compresser puis découper (CDC sur le flux compressé) → tue la dédup : la compression rend le flux positionnel, un octet modifié en tête décale tout l'aval et tous les chunks changent d'adresse. Le découpage se fait sur le clair.
- Compresser après chiffrement → impossible : le chiffré est à entropie maximale, incompressible.
- Par fichier plutôt que par chunk → le chunk est l'unité de dédup et de chiffrement ; on compresse chaque chunk indépendamment.
La contrainte qui domine : déterminisme figé
adresse = BLAKE3(chiffré) et que la dédup se fait sur l'adresse, deux machines d'un même tenant doivent produire un chiffré bit-à-bit identique pour le même chunk. La clé et le nonce le sont déjà ; reste le payload. Les octets compressés doivent donc être identiques partout, pour toujours — sinon le même chunk logique se stocke deux fois et la dédup se dégrade en silence.
En pratique, on épingle un profil de compression versionné, rangé dans la config du tenant (même discipline que les paramètres de découpage) : codec + version de zstd + niveau + framing + la règle de décision. zstd est déterministe pour un (version, niveau, params) donné, mais pas garanti stable entre versions majeures — d’où l’épinglage.
Compresser seulement si ça aide
Beaucoup de chunks sont déjà compressés (jpeg, mp4, zip, docx…). Les recompresser gâche du CPU et, par le principe des tiroirs, gonfle légèrement le résultat : zstd ajoute son framing (en-tête de frame + en-têtes de bloc), et sur du contenu incompressible il range un « raw block » ≈ taille + une dizaine d'octets.
Règle : tenter zstd, garder le compressé seulement s’il est plus petit, sinon stocker brut — avec 1 octet de flag brut|zstd à l’intérieur du payload chiffré (couvert par le tag AEAD, ne fuit pas). Cette décision fait elle aussi partie du profil épinglé : deux clients doivent trancher pareil, ou le chiffré diffère et la dédup casse.
Le codec
zstd, niveau ~3. Meilleur ratio/vitesse, déterministe, niveaux réglables. La cible inclut des NAS bas de gamme (déjà l’argument du choix d’AEAD) : on vise un niveau modéré, pas 19. lz4 serait plus rapide mais moins bon, inutile car zstd niveau bas est déjà très rapide ; brotli/xz trop lents pour du débit backup.
Dictionnaires zstd — écartés
On les écarte donc tant qu’on reste sur de gros chunks. Piste conservée si un jour un cas d’usage impose de petits chunks : rendre chaque chunk auto-descriptif (un index de profil dans le payload chiffré, un registre de profils et un objet dictionnaire chiffré par tenant), pour qu’une flotte mixte restaure toujours correctement. Hors périmètre actuel.