ObsiViewer/docs/GRAPH/GRAPH_FREEZE_FIX.md

5.2 KiB
Raw Permalink Blame History

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

  1. VaultService.graphData() se recalcule (O(N²))
  2. GraphViewContainerV2Component déclenche multiples recalculs de computed signals
  3. GraphCanvasComponent recrée le worker, les nodes, et le spatial index
  4. 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 :

  1. Ouvrir DevTools Console
  2. Passer à la vue Graph
  3. Observer les logs :
    [GraphData] Computed in XXms: YY nodes, ZZ edges
    [GraphCanvas] Updating worker: YY nodes, ZZ links
    
  4. Vérifier que l'application ne gèle plus
  5. Essayer de prendre une capture d'écran pendant le chargement → devrait fonctionner

Optimisations futures possibles

Si des problèmes de performance persistent :

  1. Lazy loading : Charger le graph view uniquement quand l'utilisateur clique dessus
  2. Virtual scrolling pour les graphes très larges (>1000 nodes)
  3. Web Worker pour le parsing des liens : Déplacer l'extraction des liens dans un worker séparé
  4. Cache du graphData : Mémoriser le résultat et n'invalider que si les notes changent
  5. 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