Blog

6x faster at 500 services: how we rebuilt the Kibana APM service map from canvas to React DOM

Each service node shows alert, SLO and anomaly health so you can filter to breaching services only and embed the result on any Kibana dashboard, with full keyboard navigation across the topology.

Trace every request end-to-end with Elastic. Get hands-on with application performance monitoring, explore the APM docs, or start a free cloud trial today.

We rebuilt the Kibana APM service map in Elastic Observability on React Flow. At 500 services it renders in 64ms, roughly 6x faster than the previous Cytoscape.js implementation, and ships 60% less JavaScript (69 KiB vs 172 KiB). Service nodes now show alert, SLO, and anomaly health so you can filter to breaching services only and embed the result on any Kibana dashboard, with full keyboard navigation across the topology. From alert to failing dependency in four clicks: Elastic APM's embedded service map covers the alert panel, SLO badges, and dashboard embedding in more depth.

Here's the map before this rebuild, and after:

BeforeAfter

Why the APM service map moved from Cytoscape.js to React Flow graph visualization

The payoff is concrete: React Flow renders nodes as real React components instead of a canvas draw loop, so panning and zooming stay smooth on large topologies, our Elastic UI (EUI) nodes and badges render natively (no more re-rendering the full canvas on every update), and DOM-based rendering makes nodes naturally visible to screen readers, unlike canvas-based rendering.

It's lighter, too: the graph library dropped from 172.4 KiB (cytoscape.js) to 69 KiB (@xyflow/react), about 103 KiB less JavaScript to ship, #248470.

How much faster is the APM service map with React Flow?

Before committing to the APM service map migration from Cytoscape.js to React Flow, we benchmarked both libraries at 100, 200, and 500 services (a synthetic chain topology, measured via Lighthouse and component-level timings averaged over multiple runs, #248470):

ServicesCytoscape.js renderReact Flow renderFaster by
10061.6 ms15.0 ms~76%
200102.5 ms28.1 ms~73%
500392.5 ms64.1 ms~84%

At 500 services, React Flow draws the map in ~64 ms versus Cytoscape.js's ~393 ms, roughly 6x faster. The underlying graph layout step alone got ~70-78% faster. Total main-thread blocking time dropped ~20%, and peak memory was slightly lower despite rendering everything as real DOM. Those results and more are available in the benchmark results comment.

Keyboard navigation and accessibility in the APM service map

The map now announces your position in the topology as you navigate, with live screen-reader context for every interaction (#251444).

  • Spatial arrow-key navigation: arrow keys navigate based on visual proximity rather than logical document order. Focus moves to the nearest service in the visual direction you press, even through complex serpentine folds.

  • Direct shortcuts: launch flyouts with Enter or Space, and dismiss them with Escape.

  • Live announcements: every interaction is backed by a screen-reader announcement, such as "Selected connection from A to B."

How the APM service map layout algorithm works

The APM service map uses Dagre for hierarchical graph layout, then applies serpentine folding to prevent long dependency chains from producing unreadable high-aspect-ratio strips:

  1. Dagre hierarchical layout: we use Dagre, a graph layout engine, with a direction toggle between horizontal and vertical that you can change from the options panel. If Dagre fails to compute a layout, the map falls back to a deterministic grid so it stays interactive and error-free.

  2. Serpentine folding for long chains: long dependency pipelines produce thin, unreadable strips that require heavy zooming. When the aspect ratio becomes too extreme, we wrap ranks into stacked bands that snake back and forth, so "fit view" can zoom in much tighter on the actual services. If the topology is already compact or has too many cross-band edges, we skip folding. (#272900).

Service dependency mapping and Kibana dashboard embedding under the hood

The visual refresh is the easy part to see. Some of the architecture changes that underpin the rebuilt map are:

Unified resource nodes for cleaner service dependency mapping

We now group external dependencies into unified resource nodes, reducing visual noise. We also fixed message-queue span grouping so those patterns no longer create orphaned nodes (#252713).

Service map flyout charts powered by ES|QL and Lens

The service flyout's infrastructure and RED metrics use ES|QL and Kibana's core Lens visualization engine, giving the map the same charting experience used across Kibana (#273713).

Embedding the service map on any Kibana dashboard

Adding a service map to a dashboard takes one click. Open the "Copy to dashboard" menu from any map view in APM, and the panel carries your current environment, service filter, KQL query, and filter chips directly across. Relative time ranges like now-15m transfer as-is rather than freezing to an absolute timestamp, so the panel stays live on the dashboard (#272277).

Once embedded, the panel adapts to where it lives. It shows or hides controls based on view mode, and respects global time settings without overwriting your relative ranges (#274551).

What's next for the APM service map

The service map rebuild is a foundation, not a finish line. Beyond what's available in 9.5, some of what's coming next is already on the APM service map roadmap.

Contributors

I led this migration and built these features alongside a great team. Thanks to Samuel Brito, Gonçalo Rica Pais da Silva, Irene Blanco Fabregat, Carlos Crespo, Miriam Aparicio Garcia, Sandra G, and Nathan Smith for the engineering, and to Karolina Kurstak and Roshan Gonsalkorale for the design and product work.

Frequently Asked Questions

Why did Kibana replace Cytoscape.js with React Flow in the APM service map in Elastic Observability?

React Flow renders each service node as a real React DOM component rather than drawing to an HTML canvas, which delivers up to 6x faster rendering at scale. DOM-based rendering also makes nodes natively accessible to screen readers and allows Elastic UI (EUI) components to render without a full canvas redraw on every update. The migration reduced the graph library footprint from 172.4 KiB to 69 KiB.

How much faster is the Kibana APM service map in 9.5?

Benchmarks show React Flow renders 76% faster at 100 services, 73% faster at 200 services, and 84% faster at 500 services compared to the previous Cytoscape.js implementation. The 500-service case puts React Flow at roughly 6x faster (392.5ms down to 64.1ms).

Can I embed the Kibana APM service map in a dashboard?

Yes, as of Kibana 9.5. Open 'Copy to dashboard' from any APM map view and the panel carries your current environment, service filter, KQL query, and filter chips. Relative time ranges like now-15m transfer as live ranges rather than freezing to an absolute timestamp.

Is the Kibana APM service map keyboard-accessible?

Yes, as of Kibana 9.5. Arrow keys move focus to the nearest service node in the visual direction pressed. Enter or Space opens a service flyout; Escape closes it. Every navigation interaction produces a screen-reader announcement, such as 'Selected connection from A to B'.

How does the APM service map handle long dependency chains?

The APM service map uses Dagre for the initial hierarchical layout, followed by serpentine folding for long chains. When a topology's aspect ratio becomes too extreme, ranks are wrapped into stacked bands that snake back and forth, letting 'fit view' zoom in on the actual services.

Does the Kibana APM service map show SLO and alert status?

Yes, in Kibana 9.5. Each service node displays SLO, alert, and anomaly health as badges. Clicking a badge opens a full status flyout. You can filter the entire topology to show only services with active alerts or SLO breaches.

How helpful was this content?

Related Content

Telemetry Policy: change OpenTelemetry sampling and log levels at runtime, no restart

Telemetry Policy: change OpenTelemetry sampling and log levels at runtime, no restart

Jack Shirazi
LLM tracing in Elastic APM: prompts, responses, and token counts in the span view

LLM tracing in Elastic APM: prompts, responses, and token counts in the span view

Jenny Pavlova
Android application monitoring with OpenTelemetry: distributed tracing from tap to backend

Android application monitoring with OpenTelemetry: distributed tracing from tap to backend

Cesar Munoz
How we rebuilt the APM service map for incident time: the design story behind Observability 9.5

How we rebuilt the APM service map for incident time: the design story behind Observability 9.5

Karolina Kurstak
Your SLO is on fire; here's how to find the arsonist in Elastic Observability

Your SLO is on fire; here's how to find the arsonist in Elastic Observability

Roshan Gonsalkorale

Elastic Observability Labs Newsletter