Files
bions-rust/bion-tsoinlog/README.md
cloudion-labo 2556698dd3 🦀 bions-rust vague 1 : 6 briques build-your-own-x — on ne les rebuild plus jamais
Principe RS-7 : « dès qu'on build un truc, plus personne n'a à le rebuild —
la seule chose à faire est l'optimisation. » (nexus/RepoVerse)
- bion-vc      : horloges vectorielles + MvReg fork-visible (LA spec
                 xion-relativiste-v0 enfin codée — CRDT testé par permutations)
- bion-triplet : l'Adressage Génératif (gen_hash BLAKE3, coords, résidu ;
                 résidu vide quand déjà-su ; align décidable au bit)
- bion-tsoinlog: journal append-only rejouable (CRC32 maison, crash-recovery)
- bion-kv      : magasin clé-valeur bitcask (compaction atomique, tombstones)
- bion-regex   : moteur Thompson NFA linéaire (jamais exponentiel — Russ Cox)
- bion-git     : mini-git content-addressed (SHA-1 maison + vecteurs officiels,
                 branches divergentes = le fork visible)
129 tests verts, clippy 0 warning, doc française = chaque bion est un cours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:07:31 +00:00

92 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# bion-tsoinlog — le journal append-only rejouable
## Quoi
La **primitive de la machine à tsoins** : un journal d'événements sur disque où
l'on ne fait qu'**appender** (jamais modifier, jamais effacer) et que l'on peut
**rejouer** — en entier ou par tranche `[from, to)`. Chaque événement = un
`topic` (UTF-8) + un `payload` (octets opaques), et reçoit un numéro de séquence
`Seq` strictement croissant.
- **std-only**, zéro dépendance, zéro `unsafe`.
- Format binaire v1 minuscule et documenté : `magic "TSOINLG1"` puis
`len(u32 LE) · topic_len(u16 LE) · topic · payload · crc32(u32 LE)`.
- **CRC-32 (IEEE) implémenté maison** (table générée à la compilation, vecteur
canonique `"123456789" → 0xCBF43926` testé) — pas de lib, build-your-own.
- **Récupération après crash** : à l'ouverture, la queue tronquée ou au CRC faux
est détectée et retaillée ; l'historique valide survit toujours (testé en
tronquant et en corrompant le fichier à la main).
- `fsync` configurable (`Sync::Always` / `Sync::Never` + `sync()` manuel).
- Index en mémoire (offset de chaque record) reconstruit au scan d'ouverture →
`iter_from(seq)` et `replay` démarrent en seek direct, pas de re-scan.
## Pourquoi
C'est la structure au cœur de Kafka, des WAL de bases de données, de l'event
sourcing, de git — et de la machine à tsoins : **enregistrer le réel dans
l'ordre, pouvoir le revivre**. Le complément exact de `tsoin-codec`
(`~/xerboxion-rt/crates/tsoin-codec`) : le codec transforme un contenu en
coordonnée de Babel, le log mémorise durablement la *séquence* des événements.
On appende volontiers des tsoins-de-fil comme payloads ; le log, lui, ne
présuppose rien sur le contenu.
## Exemple
```rust
use bion_tsoinlog::{TsoinLog, Seq, Sync};
let mut log = TsoinLog::open("journal.tsoinlog")?; // Sync::Never par défaut
// ou : TsoinLog::open_with("journal.tsoinlog", Sync::Always)? // durable à chaque append
let s0 = log.append("capteur/temp", b"21.5")?; // → Seq(0)
let s1 = log.append("bus/emit", b"{\"topic\":\"leds\"}")?; // → Seq(1)
// Tout relire :
for rec in log.iter()? {
let rec = rec?;
println!("#{} [{}] {} octets", rec.seq.0, rec.topic, rec.payload.len());
}
// Rejouer une tranche [1, 2) (from inclus, to exclu, comme un Range) :
log.replay(Seq(1), Seq(2), |rec| { /* ré-appliquer l'événement */ })?;
# std::io::Result::Ok(())
```
## API publique (stable — rétrocompatibilité éternelle)
| Élément | Rôle |
|---|---|
| `TsoinLog::open(path)` / `open_with(path, Sync)` | ouvre/crée + scan + réparation |
| `append(topic, payload) -> io::Result<Seq>` | appende un événement |
| `iter()` / `iter_from(Seq)` | itère (instantané cohérent, seek O(1)) |
| `replay(from, to, f) -> io::Result<u64>` | rejoue `[from, to)`, retourne le compte |
| `sync()` / `set_sync(Sync)` | contrôle du `fsync` |
| `len()` / `is_empty()` / `next_seq()` | état du journal |
| `Seq(u64)` / `Record { seq, topic, payload }` / `Sync` | types de données |
| `crc32(&[u8]) -> u32` / `MAGIC` / `MAX_TOPIC_LEN` | briques exposées |
## Build-your-own-x correspondants
Dans [build-your-own-x](https://github.com/codecrafters-io/build-your-own-x) :
- **Build your own Database** — le log est un *write-ahead log* (WAL) minimal ;
- **Build your own Git** — un historique append-only adressé par position ;
- le CRC-32 maison est la brique commune à zip/gzip/PNG/Ethernet (« build your
own checksum » depuis la division polynomiale dans GF(2)).
## Comment l'optimiser (l'invitation au fork)
Le format v1 est volontairement le plus simple qui soit correct. Pistes, dans
l'ordre de rentabilité, **sans jamais casser la lecture des fichiers v1** :
1. **`Sync::EveryN(n)` / group commit** — amortir le `fsync` sur n appends ;
2. **index persistant** (fichier `.idx` side-car, régénérable) — ouverture O(1)
sur les très gros journaux au lieu du scan complet ;
3. **segments + compaction** — découper en fichiers de taille bornée, archiver
ou fusionner les vieux segments (le chemin vers Kafka) ;
4. **mmap en lecture** — itération zéro-copie ;
5. **CRC vectorisé** (slicing-by-8, ou `crc32` matériel SSE4.2) — même résultat,
~10× plus vite ;
6. **compression des payloads** — brancher `tsoin-codec` : appender la
coordonnée de Babel au lieu des octets bruts.