# Migration fichiers texte vers base de données **Date:** 2026-01-27 **Auteur:** Équipe 4NK ## Objectif Évaluer la migration des fichiers texte (`utxo_list.txt`, `hash_list.txt`, `fees_list.txt`) vers une base de données pour améliorer les performances et la maintenabilité. ## État actuel ### Fichiers texte utilisés | Fichier | Taille | Lignes | Format | |---------|--------|--------|--------| | `utxo_list.txt` | 6.3 MB | 68 397 | `category;txid;vout;amount;confirmations;isAnchorChange;blockTime` | | `hash_list.txt` | 5.2 MB | 32 718 | `hash;txid;blockHeight;confirmations;date` | | `fees_list.txt` | 411 KB | 2 666 | `txid;fee;fee_sats;blockHeight;blockTime;confirmations;changeAddress;changeAmount` | | **Total** | **11.9 MB** | **103 781** | - | ### Opérations actuelles 1. **Lecture complète** : Chargement de tout le fichier en mémoire 2. **Parsing ligne par ligne** : `split(';')` pour chaque ligne 3. **Recherche** : Parsing de toutes les lignes (O(n)) 4. **Mise à jour** : Réécriture complète du fichier 5. **Comptage** : Parsing de toutes les lignes ### Problèmes identifiés #### Performance - **Lecture complète nécessaire** : Même pour un seul élément, tout le fichier doit être lu - **Pas d'indexation** : Recherche linéaire O(n) pour trouver un élément - **Parsing coûteux** : Split et parsing de 68k+ lignes à chaque chargement - **Mise à jour lourde** : Réécriture complète du fichier pour une seule modification #### Maintenabilité - **Pas de validation de schéma** : Format libre, erreurs possibles - **Pas de transactions** : Risque de corruption en cas d'interruption - **Pas de concurrence** : Accès concurrent non sécurisé - **Pas de requêtes complexes** : Impossible de faire des JOIN, GROUP BY, etc. #### Exemples de lenteur ```javascript // Lecture complète de 68k lignes const content = readFileSync(utxoListPath, 'utf8').trim(); const lines = content.split('\n'); for (const line of lines) { const parts = line.split(';'); // Parsing de chaque ligne // ... } // Recherche: parsing de toutes les lignes for (const line of lines) { if (line.includes('ancrages')) { // Recherche linéaire // ... } } // Mise à jour: réécriture complète writeFileSync(outputPath, allLines.join('\n')); // Réécriture de 6.3 MB ``` ## Solution proposée: Base de données ### Choix de la base de données **Recommandation: SQLite** **Avantages:** - Pas de serveur séparé nécessaire (fichier local) - Très performant pour ce volume de données - Support SQL complet - Transactions ACID - Indexation native - Faible empreinte mémoire - Facile à migrer vers PostgreSQL/MySQL si nécessaire **Alternatives:** - **PostgreSQL** : Si besoin de serveur centralisé, accès réseau - **MySQL/MariaDB** : Si déjà utilisé dans l'infrastructure - **IndexedDB** : Si besoin côté client (navigateur) ### Schéma proposé #### Table `utxos` ```sql CREATE TABLE utxos ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, -- 'bloc_rewards', 'ancrages', 'changes' txid TEXT NOT NULL, vout INTEGER NOT NULL, address TEXT, amount REAL NOT NULL, confirmations INTEGER DEFAULT 0, is_anchor_change BOOLEAN DEFAULT FALSE, block_time INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(txid, vout) ); CREATE INDEX idx_utxos_category ON utxos(category); CREATE INDEX idx_utxos_txid_vout ON utxos(txid, vout); CREATE INDEX idx_utxos_confirmations ON utxos(confirmations); CREATE INDEX idx_utxos_amount ON utxos(amount); ``` #### Table `anchors` (hash_list.txt) ```sql CREATE TABLE anchors ( id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL UNIQUE, txid TEXT NOT NULL, block_height INTEGER, confirmations INTEGER DEFAULT 0, date TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_anchors_hash ON anchors(hash); CREATE INDEX idx_anchors_txid ON anchors(txid); CREATE INDEX idx_anchors_block_height ON anchors(block_height); ``` #### Table `fees` ```sql CREATE TABLE fees ( id INTEGER PRIMARY KEY AUTOINCREMENT, txid TEXT NOT NULL UNIQUE, fee REAL NOT NULL, fee_sats INTEGER NOT NULL, block_height INTEGER, block_time INTEGER, confirmations INTEGER DEFAULT 0, change_address TEXT, change_amount REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_fees_txid ON fees(txid); CREATE INDEX idx_fees_block_height ON fees(block_height); ``` ## Comparaison des performances ### Opérations courantes #### 1. Chargement de la liste complète **Fichier texte:** ```javascript // Temps: ~200-500ms pour 68k lignes const content = readFileSync(path, 'utf8'); // ~50ms const lines = content.split('\n'); // ~20ms for (const line of lines) { const parts = line.split(';'); // ~200-300ms // ... } ``` **Base de données:** ```sql -- Temps: ~50-100ms avec index SELECT * FROM utxos; ``` **Gain:** 2-5x plus rapide #### 2. Recherche par catégorie **Fichier texte:** ```javascript // Temps: ~200-300ms (parsing de toutes les lignes) const anchors = lines.filter(line => line.startsWith('ancrages;')); ``` **Base de données:** ```sql -- Temps: ~5-10ms avec index SELECT * FROM utxos WHERE category = 'ancrages'; ``` **Gain:** 20-40x plus rapide #### 3. Recherche par txid **Fichier texte:** ```javascript // Temps: ~200-300ms (recherche linéaire) const utxo = lines.find(line => line.includes(`;${txid};`)); ``` **Base de données:** ```sql -- Temps: ~1-2ms avec index SELECT * FROM utxos WHERE txid = ? AND vout = ?; ``` **Gain:** 100-200x plus rapide #### 4. Mise à jour d'un élément **Fichier texte:** ```javascript // Temps: ~300-500ms (réécriture complète) // 1. Lire tout le fichier // 2. Modifier la ligne // 3. Réécrire tout le fichier writeFileSync(path, allLines.join('\n')); ``` **Base de données:** ```sql -- Temps: ~2-5ms UPDATE utxos SET confirmations = ? WHERE txid = ? AND vout = ?; ``` **Gain:** 60-250x plus rapide #### 5. Comptage **Fichier texte:** ```javascript // Temps: ~100-200ms (parsing de toutes les lignes) const count = lines.filter(line => line.startsWith('ancrages;')).length; ``` **Base de données:** ```sql -- Temps: ~1-2ms (pas de parsing, index uniquement) SELECT COUNT(*) FROM utxos WHERE category = 'ancrages'; ``` **Gain:** 50-200x plus rapide #### 6. Requêtes complexes **Fichier texte:** ```javascript // Impossible ou très lent // Exemple: UTXOs disponibles pour ancrage avec confirmations >= 6 const available = lines .filter(line => line.startsWith('ancrages;')) .map(line => { const parts = line.split(';'); return { amount: parseFloat(parts[3]), confirmations: parseInt(parts[4]) }; }) .filter(u => u.amount >= 0.00002 && u.confirmations >= 6); // Temps: ~300-500ms ``` **Base de données:** ```sql -- Temps: ~5-10ms SELECT * FROM utxos WHERE category = 'ancrages' AND amount >= 0.00002 AND confirmations >= 6; ``` **Gain:** 30-100x plus rapide ### Résumé des gains de performance | Opération | Fichier texte | Base de données | Gain | |-----------|---------------|-----------------|------| | Chargement complet | 200-500ms | 50-100ms | 2-5x | | Recherche par catégorie | 200-300ms | 5-10ms | 20-40x | | Recherche par txid | 200-300ms | 1-2ms | 100-200x | | Mise à jour | 300-500ms | 2-5ms | 60-250x | | Comptage | 100-200ms | 1-2ms | 50-200x | | Requêtes complexes | 300-500ms | 5-10ms | 30-100x | ## Avantages supplémentaires ### 1. Intégrité des données - **Contraintes** : UNIQUE, NOT NULL, CHECK - **Transactions** : Rollback en cas d'erreur - **Validation** : Types de données stricts ### 2. Accès concurrent - **Verrous** : Gestion automatique des accès concurrents - **Isolation** : Transactions isolées - **Pas de corruption** : Pas de risque de fichier partiellement écrit ### 3. Requêtes avancées ```sql -- Exemple: Statistiques par catégorie SELECT category, COUNT(*) as count, SUM(amount) as total_amount, AVG(amount) as avg_amount FROM utxos GROUP BY category; -- Exemple: Ancrages récents SELECT a.*, f.fee_sats FROM anchors a LEFT JOIN fees f ON a.txid = f.txid WHERE a.block_height > ? ORDER BY a.block_height DESC LIMIT 100; ``` ### 4. Maintenance - **Backup** : Copie simple du fichier SQLite - **Migration** : Scripts SQL pour évolutions de schéma - **Monitoring** : Requêtes SQL pour diagnostics ## Migration ### Étape 1: Création de la base de données ```javascript // signet-dashboard/src/database.js import Database from 'better-sqlite3'; const db = new Database('./data/signet.db'); // Créer les tables db.exec(` CREATE TABLE IF NOT EXISTS utxos (...); CREATE TABLE IF NOT EXISTS anchors (...); CREATE TABLE IF NOT EXISTS fees (...); `); ``` ### Étape 2: Migration des données existantes ```javascript // Script de migration unique async function migrateFromTextFiles() { // 1. Lire utxo_list.txt const utxoLines = readFileSync('utxo_list.txt', 'utf8').split('\n'); // 2. Insérer en batch const insert = db.prepare(` INSERT INTO utxos (category, txid, vout, amount, confirmations, ...) VALUES (?, ?, ?, ?, ?, ...) `); const insertMany = db.transaction((utxos) => { for (const utxo of utxos) { insert.run(...); } }); insertMany(parsedUtxos); // Répéter pour hash_list.txt et fees_list.txt } ``` ### Étape 3: Adaptation du code ```javascript // Avant (fichier texte) async getUtxoList() { const content = readFileSync('utxo_list.txt', 'utf8'); const lines = content.split('\n'); // Parsing... } // Après (base de données) async getUtxoList() { const utxos = db.prepare(` SELECT * FROM utxos `).all(); return { blocRewards: utxos.filter(u => u.category === 'bloc_rewards'), anchors: utxos.filter(u => u.category === 'ancrages'), changes: utxos.filter(u => u.category === 'changes'), }; } ``` ### Étape 4: Mise à jour incrémentale ```javascript // Au lieu de réécrire tout le fichier async function updateUtxo(txid, vout, updates) { db.prepare(` UPDATE utxos SET confirmations = ?, updated_at = CURRENT_TIMESTAMP WHERE txid = ? AND vout = ? `).run(updates.confirmations, txid, vout); } ``` ## Impact attendu ### Performance - **Chargement initial** : 2-5x plus rapide - **Recherches** : 20-200x plus rapide - **Mises à jour** : 60-250x plus rapide - **Requêtes complexes** : 30-100x plus rapide ### Maintenabilité - **Code plus simple** : Pas de parsing manuel - **Moins de bugs** : Validation automatique - **Évolutivité** : Facile d'ajouter de nouvelles tables/colonnes - **Requêtes SQL** : Plus expressives que le parsing manuel ### Fiabilité - **Pas de corruption** : Transactions ACID - **Accès concurrent** : Gestion automatique - **Backup** : Copie simple du fichier ## Recommandations ### Priorité haute 1. **Migrer vers SQLite** pour les gains de performance immédiats 2. **Créer les index** pour optimiser les recherches 3. **Adapter le code** pour utiliser la base de données ### Priorité moyenne 4. **Scripts de migration** pour les données existantes 5. **Tests de performance** pour valider les gains 6. **Documentation** des nouvelles requêtes SQL ### Priorité basse 7. **Monitoring** des performances de la base de données 8. **Optimisations** supplémentaires si nécessaire 9. **Migration vers PostgreSQL** si besoin de serveur centralisé ## Conclusion **Oui, une base de données serait significativement plus performante** pour ce cas d'usage : - **Gains de performance** : 2-200x selon l'opération - **Meilleure maintenabilité** : Code plus simple, moins de bugs - **Plus de fonctionnalités** : Requêtes complexes, transactions, etc. - **Meilleure fiabilité** : Pas de corruption, accès concurrent sécurisé **Recommandation:** Migrer vers SQLite pour un gain immédiat avec un effort minimal (pas de serveur à configurer).