anchorage_layer_simple/features/migration-fichiers-texte-vers-base-donnees.md
ncantu 0960e43a45 Optimisation mémoire api-anchorage avec base de données SQLite
**Motivations:**
- Réduction drastique de la consommation mémoire lors des ancrages
- Élimination du chargement de 173k+ UTXOs à chaque requête
- Stabilisation de la mémoire système sous charge élevée (50+ ancrages/minute)

**Root causes:**
- api-anchorage chargeait tous les UTXOs (173k+) via listunspent RPC à chaque ancrage
- Filtrage et tri de 173k+ objets en mémoire pour sélectionner un seul UTXO
- Croissance mémoire de ~16 MB toutes les 12 secondes avec 50 ancrages/minute
- Saturation mémoire système en quelques minutes

**Correctifs:**
- Création du module database.js pour gérer la base de données SQLite partagée
- Remplacement de listunspent RPC par requête SQL directe avec LIMIT 1
- Sélection directe d'un UTXO depuis la DB au lieu de charger/filtrer 173k+ objets
- Marquage des UTXOs comme dépensés dans la DB après utilisation
- Fermeture propre de la base de données lors de l'arrêt

**Evolutions:**
- Utilisation de la base de données SQLite partagée avec signet-dashboard
- Réduction mémoire de 99.999% (173k+ objets → 1 objet par requête)
- Amélioration des performances (requête SQL indexée vs filtrage en mémoire)
- Optimisation mémoire de signet-dashboard (chargement UTXOs seulement si nécessaire)
- Monitoring de lockedUtxos dans api-anchorage pour détecter les fuites
- Nettoyage des intervalles frontend pour éviter les fuites mémoire

**Pages affectées:**
- api-anchorage/src/database.js (nouveau)
- api-anchorage/src/bitcoin-rpc.js
- api-anchorage/src/server.js
- api-anchorage/package.json
- signet-dashboard/src/bitcoin-rpc.js
- signet-dashboard/public/app.js
- features/optimisation-memoire-applications.md (nouveau)
- features/api-anchorage-optimisation-base-donnees.md (nouveau)
2026-01-27 21:12:22 +01:00

457 lines
12 KiB
Markdown

# 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).