Back to blog
EngineeringJul 5, 2026·8 min read

How we built real-time collaboration for 10,000 concurrent users

A deep-dive into our WebSocket infrastructure, CRDT implementation, and the tradeoffs we made building Ajath Canvas.

The Challenge of Real-Time

When we set out to build Ajath Canvas, our collaborative visual workspace for planning software architecture, we knew that real-time collaboration was not a "nice-to-have" — it was the entire point of the product. Teams needed to be able to drag, drop, connect, and annotate architecture diagrams together, seamlessly.

Our target was ambitious: support up to 10,000 concurrent users interacting with the Canvas simultaneously without noticeable latency. This post outlines the engineering journey, the architecture we settled on, and the lessons we learned.

Choosing CRDTs over OT

The first major architectural decision was how to handle state synchronization. The two dominant paradigms for real-time collaborative editing are Operational Transformation (OT) and Conflict-free Replicated Data Types (CRDTs).

While OT is battle-tested (powering tools like Google Docs), it typically requires a central server to sequence operations, which can become a bottleneck at scale. CRDTs, on the other hand, allow any replica to merge updates from any other replica without conflicts, making them inherently more scalable and suitable for peer-to-peer architectures.

We chose to implement our synchronization engine using Yjs, a high-performance CRDT library. Yjs allowed us to represent the Canvas state — a complex graph of nodes, edges, and nested properties — as a shared data type.

Scaling WebSockets with Elixir and Phoenix

With the data structure solved, we needed a robust transport layer to distribute the CRDT updates between clients. We initially prototyped with Node.js and Socket.io, but quickly ran into CPU bottlenecks when broadcasting frequent mouse movement events to hundreds of clients in a single room.

We pivoted to Elixir and the Phoenix Framework. The Erlang VM (BEAM), upon which Elixir is built, is legendary for its ability to handle massive numbers of concurrent connections.

Using Phoenix Channels, we were able to isolate each Canvas session into its own lightweight process. If one room experienced a sudden spike in activity (e.g., someone dragging a large selection of nodes), it didn't impact the performance of other rooms.

Optimizing the Frontend

Even with a highly scalable backend, rendering updates from thousands of users in the browser is non-trivial. React's virtual DOM is fast, but re-rendering the entire Canvas for every cursor movement was out of the question.

We implemented a hybrid rendering approach:

  1. React for the UI chrome (menus, toolbars, sidebars).
  2. HTML5 Canvas API (via a custom WebGL renderer) for the collaborative area.

By decoupling the high-frequency updates (cursors, dragging) from the React component tree and rendering them directly via WebGL, we achieved a consistent 60fps even under heavy load.

The Result

The combination of Yjs for conflict resolution, Elixir/Phoenix for WebSocket scaling, and WebGL for rendering allowed us to exceed our initial target. In stress tests, we successfully sustained 15,000 concurrent connections modifying a single massive Canvas document, with p99 latencies remaining under 50ms globally.


Build your next idea with Maya

Join 50,000+ developers shipping faster with Ajath.

Get started for free