5.2 KiB
Correction du gel de l'application lors de l'ouverture du Graph View
Problème identifié
L'application gelait complètement pendant plusieurs secondes lors du passage à la vue graphe, empêchant même la prise de captures d'écran.
Cause racine
Complexité algorithmique O(N²) dans VaultService.graphData()
L'ancien code effectuait une recherche linéaire (.find()
) pour CHAQUE lien trouvé dans CHAQUE note :
// AVANT (O(N × M × N))
for (const note of notes) { // Boucle 1: N notes
while ((match = linkRegex.exec(note.content)) !== null) { // Boucle 2: M liens
const targetNote = notes.find(n => ...) // Boucle 3: N notes PAR LIEN!
}
}
Résultat : Avec 100 notes et 10 liens/note → 100,000+ opérations synchrones bloquant le thread principal.
Effets en cascade
VaultService.graphData()
se recalcule (O(N²))GraphViewContainerV2Component
déclenche multiples recalculs de computed signalsGraphCanvasComponent
recrée le worker, les nodes, et le spatial index- Tout cela de manière SYNCHRONE → gel total
Solutions implémentées
1. Optimisation O(N²) → O(N×M) dans VaultService
Fichier: src/services/vault.service.ts
Remplacement des .find()
par des Map.get()
(O(1)) :
// Build fast lookup maps
const noteById = new Map<string, Note>();
const noteByTitle = new Map<string, Note>();
const notesByAlias = new Map<string, Note>();
// Index all notes once
for (const note of notes) {
noteById.set(note.id, note);
noteByTitle.set(note.title, note);
if (Array.isArray(note.frontmatter?.aliases)) {
for (const alias of note.frontmatter.aliases) {
notesByAlias.set(alias, note);
}
}
}
// Fast lookup during link extraction
const targetNote = noteById.get(linkPath)
|| noteByTitle.get(rawLink)
|| notesByAlias.get(rawLink);
Gain : Complexité réduite de O(N×M×N) à O(N×M)
2. Debounce des mises à jour dans GraphCanvasComponent
Fichier: src/app/graph/graph-canvas.component.ts
Ajout d'un délai de 100ms pour éviter les cascades de recalculs :
// React to data changes with debounce
let updateTimer: ReturnType<typeof setTimeout> | null = null;
effect(() => {
const nodes = this.nodes();
const links = this.links();
if (updateTimer) clearTimeout(updateTimer);
updateTimer = setTimeout(() => {
this.updateWorkerData(nodes, links);
}, 100);
});
3. Throttle de reconstruction du Spatial Index
Fichier: src/app/graph/graph-canvas.component.ts
Augmentation de l'intervalle de reconstruction de 200ms → 500ms :
// Rebuild spatial index at most every 500ms to reduce main thread load
const now = performance.now();
if (now - lastIndexBuild > 500) {
this.spatialIndex = new SpatialIndex(this.simulationNodes());
lastIndexBuild = now;
}
4. Éviter les réinitialisations inutiles du worker
Fichier: src/app/graph/graph-canvas.component.ts
Vérification du nombre de nodes/links avant de réinitialiser :
private updateWorkerData(nodes: GraphNodeWithVisuals[], links: GraphLink[]): void {
// Skip update if data hasn't substantially changed
if (nodes.length === this.lastNodeCount && links.length === this.lastLinkCount && this.session) {
console.log(`[GraphCanvas] Skipping update - same data size`);
return;
}
this.lastNodeCount = nodes.length;
this.lastLinkCount = links.length;
// ... reste de l'initialisation
}
5. Logs de performance pour diagnostic
Ajout de traces pour mesurer les performances :
// Dans VaultService
console.log(`[GraphData] Computed in ${duration.toFixed(2)}ms: ${nodes.length} nodes, ${edges.length} edges`);
// Dans GraphCanvasComponent
console.log(`[GraphCanvas] Updating worker: ${nodes.length} nodes, ${links.length} links`);
Résultats attendus
- ✅ Réduction drastique du temps de calcul : O(N²) → O(N×M)
- ✅ Évite les recalculs en cascade grâce au debounce
- ✅ Réduit la charge du thread principal avec throttle spatial index
- ✅ Skip les updates inutiles quand les données n'ont pas changé
- ✅ Visibilité sur les performances via les console logs
Validation
Pour tester les améliorations :
- Ouvrir DevTools Console
- Passer à la vue Graph
- Observer les logs :
[GraphData] Computed in XXms: YY nodes, ZZ edges [GraphCanvas] Updating worker: YY nodes, ZZ links
- Vérifier que l'application ne gèle plus
- Essayer de prendre une capture d'écran pendant le chargement → devrait fonctionner
Optimisations futures possibles
Si des problèmes de performance persistent :
- Lazy loading : Charger le graph view uniquement quand l'utilisateur clique dessus
- Virtual scrolling pour les graphes très larges (>1000 nodes)
- Web Worker pour le parsing des liens : Déplacer l'extraction des liens dans un worker séparé
- Cache du graphData : Mémoriser le résultat et n'invalider que si les notes changent
- Pagination du graph : Afficher seulement les N nodes les plus connectés par défaut
Fichiers modifiés
src/services/vault.service.ts
- Optimisation O(N²) → O(N×M)src/app/graph/graph-canvas.component.ts
- Debounce, throttle, logs