**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)
222 lines
7.2 KiB
JavaScript
222 lines
7.2 KiB
JavaScript
/**
|
|
* Script de migration des fichiers texte vers SQLite
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { createRequire } from 'module';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// Utiliser better-sqlite3 depuis signet-dashboard/node_modules
|
|
const require = createRequire(join(__dirname, '../signet-dashboard/package.json'));
|
|
const Database = require('better-sqlite3');
|
|
|
|
const dbPath = join(__dirname, 'signet.db');
|
|
const db = new Database(dbPath);
|
|
|
|
// Activer les clés étrangères
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
// Préparer les requêtes d'insertion
|
|
const insertUtxo = db.prepare(`
|
|
INSERT OR REPLACE INTO utxos
|
|
(category, txid, vout, amount, confirmations, is_anchor_change, block_time, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
`);
|
|
|
|
const insertAnchor = db.prepare(`
|
|
INSERT OR REPLACE INTO anchors
|
|
(hash, txid, block_height, confirmations, date, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
`);
|
|
|
|
const insertFee = db.prepare(`
|
|
INSERT OR REPLACE INTO fees
|
|
(txid, fee, fee_sats, block_height, block_time, confirmations, change_address, change_amount, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
`);
|
|
|
|
const insertManyUtxos = db.transaction((utxos) => {
|
|
for (const utxo of utxos) {
|
|
insertUtxo.run(
|
|
utxo.category,
|
|
utxo.txid,
|
|
utxo.vout,
|
|
utxo.amount,
|
|
utxo.confirmations,
|
|
utxo.is_anchor_change ? 1 : 0,
|
|
utxo.block_time || null
|
|
);
|
|
}
|
|
});
|
|
|
|
const insertManyAnchors = db.transaction((anchors) => {
|
|
for (const anchor of anchors) {
|
|
insertAnchor.run(
|
|
anchor.hash,
|
|
anchor.txid,
|
|
anchor.block_height,
|
|
anchor.confirmations,
|
|
anchor.date
|
|
);
|
|
}
|
|
});
|
|
|
|
const insertManyFees = db.transaction((fees) => {
|
|
for (const fee of fees) {
|
|
insertFee.run(
|
|
fee.txid,
|
|
fee.fee,
|
|
fee.fee_sats,
|
|
fee.block_height,
|
|
fee.block_time,
|
|
fee.confirmations,
|
|
fee.change_address,
|
|
fee.change_amount
|
|
);
|
|
}
|
|
});
|
|
|
|
// Migrer utxo_list.txt
|
|
console.log('📦 Migration de utxo_list.txt...');
|
|
const utxoListPath = join(__dirname, '../utxo_list.txt');
|
|
if (existsSync(utxoListPath)) {
|
|
const content = readFileSync(utxoListPath, 'utf8').trim();
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
const utxos = [];
|
|
|
|
for (const line of lines) {
|
|
const parts = line.split(';');
|
|
if (parts.length >= 6) {
|
|
let category, txid, vout, amount, confirmations, isAnchorChange, blockTime;
|
|
|
|
if (parts.length === 7 && !isNaN(parseFloat(parts[3]))) {
|
|
// Nouveau format: category;txid;vout;amount;confirmations;isAnchorChange;blockTime
|
|
[category, txid, vout, amount, confirmations, isAnchorChange, blockTime] = parts;
|
|
} else if (parts.length >= 6) {
|
|
// Ancien format: category;txid;vout;address;amount;confirmations;isAnchorChange
|
|
[category, txid, vout, , amount, confirmations] = parts;
|
|
isAnchorChange = parts.length > 6 ? parts[6] === 'true' : false;
|
|
blockTime = parts.length > 7 ? parseInt(parts[7], 10) || null : null;
|
|
}
|
|
|
|
utxos.push({
|
|
category: category.trim(),
|
|
txid: txid.trim(),
|
|
vout: parseInt(vout, 10),
|
|
amount: parseFloat(amount),
|
|
confirmations: parseInt(confirmations, 10) || 0,
|
|
is_anchor_change: isAnchorChange === 'true' || isAnchorChange === true,
|
|
block_time: blockTime ? parseInt(blockTime, 10) : null
|
|
});
|
|
}
|
|
}
|
|
|
|
insertManyUtxos(utxos);
|
|
console.log(`✅ ${utxos.length} UTXOs migrés`);
|
|
} else {
|
|
console.log('⚠️ utxo_list.txt non trouvé');
|
|
}
|
|
|
|
// Migrer hash_list.txt
|
|
console.log('📦 Migration de hash_list.txt...');
|
|
const hashListPath = join(__dirname, '../hash_list.txt');
|
|
if (existsSync(hashListPath)) {
|
|
const content = readFileSync(hashListPath, 'utf8').trim();
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
const anchors = [];
|
|
|
|
for (const line of lines) {
|
|
const parts = line.split(';');
|
|
if (parts.length >= 2) {
|
|
const [hash, txid, blockHeight, confirmations, date] = parts;
|
|
anchors.push({
|
|
hash: hash.trim(),
|
|
txid: txid.trim(),
|
|
block_height: blockHeight ? parseInt(blockHeight, 10) : null,
|
|
confirmations: confirmations ? parseInt(confirmations, 10) : 0,
|
|
date: date || new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
insertManyAnchors(anchors);
|
|
console.log(`✅ ${anchors.length} ancrages migrés`);
|
|
} else {
|
|
console.log('⚠️ hash_list.txt non trouvé');
|
|
}
|
|
|
|
// Migrer fees_list.txt
|
|
console.log('📦 Migration de fees_list.txt...');
|
|
const feesListPath = join(__dirname, '../fees_list.txt');
|
|
if (existsSync(feesListPath)) {
|
|
const content = readFileSync(feesListPath, 'utf8').trim();
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
const fees = [];
|
|
|
|
for (const line of lines) {
|
|
const parts = line.split(';');
|
|
// Format: txid;fee;fee_sats;blockHeight;blockTime;confirmations;changeAddress;changeAmount
|
|
if (parts.length >= 3) {
|
|
const [txid, fee, feeSats, blockHeight, blockTime, confirmations, changeAddress, changeAmount] = parts;
|
|
fees.push({
|
|
txid: txid.trim(),
|
|
fee: parseFloat(fee) || 0,
|
|
fee_sats: parseInt(feeSats, 10) || 0,
|
|
block_height: blockHeight ? parseInt(blockHeight, 10) : null,
|
|
block_time: blockTime ? parseInt(blockTime, 10) : null,
|
|
confirmations: confirmations ? parseInt(confirmations, 10) : 0,
|
|
change_address: changeAddress || null,
|
|
change_amount: changeAmount ? parseFloat(changeAmount) : null
|
|
});
|
|
}
|
|
}
|
|
|
|
insertManyFees(fees);
|
|
console.log(`✅ ${fees.length} frais migrés`);
|
|
} else {
|
|
console.log('⚠️ fees_list.txt non trouvé');
|
|
}
|
|
|
|
// Migrer les caches
|
|
console.log('📦 Migration des caches...');
|
|
const utxoCachePath = join(__dirname, '../utxo_list_cache.txt');
|
|
if (existsSync(utxoCachePath)) {
|
|
const cacheContent = readFileSync(utxoCachePath, 'utf8').trim();
|
|
const parts = cacheContent.split(';');
|
|
if (parts.length >= 2) {
|
|
const insertCache = db.prepare('INSERT OR REPLACE INTO cache (key, value) VALUES (?, ?)');
|
|
insertCache.run('utxo_list_cache', cacheContent);
|
|
console.log('✅ Cache UTXO migré');
|
|
}
|
|
}
|
|
|
|
const hashCachePath = join(__dirname, '../hash_list_cache.txt');
|
|
if (existsSync(hashCachePath)) {
|
|
const cacheContent = readFileSync(hashCachePath, 'utf8').trim();
|
|
const parts = cacheContent.split(';');
|
|
if (parts.length >= 2) {
|
|
const insertCache = db.prepare('INSERT OR REPLACE INTO cache (key, value) VALUES (?, ?)');
|
|
insertCache.run('hash_list_cache', cacheContent);
|
|
console.log('✅ Cache hash migré');
|
|
}
|
|
}
|
|
|
|
console.log('\n✅ Migration terminée avec succès!');
|
|
console.log(`📁 Base de données: ${dbPath}`);
|
|
|
|
// Afficher les statistiques
|
|
const utxoCount = db.prepare('SELECT COUNT(*) as count FROM utxos').get();
|
|
const anchorCount = db.prepare('SELECT COUNT(*) as count FROM anchors').get();
|
|
const feeCount = db.prepare('SELECT COUNT(*) as count FROM fees').get();
|
|
|
|
console.log('\n📊 Statistiques:');
|
|
console.log(` UTXOs: ${utxoCount.count}`);
|
|
console.log(` Ancrages: ${anchorCount.count}`);
|
|
console.log(` Frais: ${feeCount.count}`);
|
|
|
|
db.close();
|