Keeping a Music Catalog Graph Responsive: Performance by Design

How the app keeps interaction cost proportional to what the user explores — partial load, dual indexes, incremental G6 life, layer batching, and shadow-diffed visual state.

Diana Ow ·

Imagine searching for a prolific studio artist — 40 releases, 320 recordings, 180 unique contributors across composer, lyricist, producer, and performer credits. The extraction API returns a graph with roughly 2,800 nodes and 6,400 edges once release combos and entity groupings are applied. That payload fits comfortably in browser memory. Putting all of it on a force-directed canvas at once is a different story: layout thrashes, the first hover scans thousands of elements, and toggling “hide compilations” re-walks the entire edge list.

That gap — memory is cheap, interaction is expensive — is the problem this app is built around. Generic graph tutorials show you how to call graph.setData(). They do not show you how to keep panning, clicking, filtering, and expanding responsive when the underlying catalog is an order of magnitude larger than what the user is looking at right now.

The performance work here is not a late optimization pass. It is a set of design principles baked into load strategy, indexing, caching, and visual state from the first line of the custom graph engine. This post explains how those principles work together — with concrete scenarios and flowcharts at each step — and traces what a single expand gesture actually does under the hood.

How to read the diagrams: Each flowchart pairs a one-line description with the function(s) that implement it. Arrows are call order; dashed boxes and edges mark skipped work. Click a node with an orange border to zoom and view representative code.