# 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` | appende un événement | | `iter()` / `iter_from(Seq)` | itère (instantané cohérent, seek O(1)) | | `replay(from, to, f) -> io::Result` | 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.