Why CRDTs Matter for Real‑Time Collaboration
Imagine dozens of developers editing the same document from different continents, each keystroke arriving milliseconds apart. Traditional locking mechanisms either stall users or cause merge conflicts that must be resolved manually. Conflict‑Free Replicated Data Types (CRDTs) guarantee eventual consistency without a central coordinator, making them ideal for real‑time collaborative editors, whiteboards, or shared spreadsheets built on .NET 8. A 2023 benchmark from the ACM showed that CRDT‑based systems processed 1.8 million operations per second with sub‑10 ms latency, far outperforming operational‑transform approaches under high contention.
Selecting the Right CRDT Model
CRDTs come in two families: state‑based (CvRDT) and operation‑based (CmRDT). State‑based types transmit the entire state periodically, which simplifies network code but can be bandwidth‑heavy for large structures. Operation‑based types send compact deltas, perfect for SignalR’s low‑overhead hub messages. For a collaborative counter or list, a G‑Counter (grow‑only) or an RGA (Replicated Growable Array) is often sufficient. The example below implements a G‑Counter in C# using .NET 8 minimal APIs, demonstrating how the merge function resolves divergent counts by taking the maximum per node.
public class GCounter { private readonly Dictionary _counts = new(); public void Increment(string nodeId, long delta = 1) { if (!_counts.ContainsKey(nodeId)) _counts[nodeId] = 0; _counts[nodeId] += delta; } public long Value => _counts.Values.Sum(); public GCounter Merge(GCounter other) { foreach (var kvp in other._counts) { if (_counts.TryGetValue(kvp.Key, out var existing)) { _counts[kvp.Key] = Math.Max(existing, kvp.Value); } else { _counts[kvp.Key] = kvp.Value; } } return this; } } Integrating CRDT with SignalR in .NET 8
SignalR hubs act as the real‑time conduit between browsers and the server. By injecting an ICRDTService into the hub, each incoming operation can be applied locally, merged, and then broadcast to all other participants. The hub below extracts a JSON‑encoded operation, deserializes it, and uses the CRDT service to produce the new state. The updated state is then sent to every client in the same document group, ensuring that all participants see the same view without explicit conflict handling.
public class CollaborationHub : Hub { private readonly ICRDTService _crdt; public CollaborationHub(ICRDTService crdt) { _crdt = crdt; } public async Task ApplyOperation(string docId, string operationJson) { var op = JsonSerializer.Deserialize(operationJson); var updated = await _crdt.ApplyAsync(docId, op); await Clients.OthersInGroup(docId).SendAsync(\"ReceiveOperation\", JsonSerializer.Serialize(updated)); } public override async Task OnConnectedAsync() { var docId = Context.GetHttpContext().Request.Query[\"doc\"]; await Groups.AddToGroupAsync(Context.ConnectionId, docId); await base.OnConnectedAsync(); } } Persisting CRDT State with Azure Cosmos DB
While CRDTs guarantee convergence in memory, a durable store is essential for crash recovery and new participants joining mid‑session. Azure Cosmos DB’s multi‑region, low‑latency guarantees complement the eventual‑consistency model. The snippet below creates a CosmosClient, selects a container, and provides async methods to upsert the current CRDT state and to retrieve it on demand. By using the document ID as the partition key, reads and writes scale linearly with the number of active sessions.
var client = new CosmosClient(Environment.GetEnvironmentVariable(\"COSMOS_CONNECTION\")); var container = client.GetContainer(\"CollabDB\", \"Documents\"); public async Task SaveStateAsync(string docId, GCounter state) { await container.UpsertItemAsync(state, new PartitionKey(docId)); } public async Task LoadStateAsync(string docId) { try { var response = await container.ReadItemAsync(docId, new PartitionKey(docId)); return response.Resource; } catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { return new GCounter(); } } Testing, Monitoring, and Performance Tips
Before shipping, simulate 500 concurrent SignalR connections using k6 or Azure Load Testing. Measure round‑trip latency for the ApplyOperation call; keep it below 30 ms to preserve a fluid UI. Enable Cosmos DB’s Request Units (RU) calculator and allocate at least 400 RU/s per 100 active documents; this prevents throttling during burst edits. In .NET 8, take advantage of the new Native AOT compilation for the hub assembly to reduce cold‑start times in Azure Functions or containerized deployments.
Conclusion
By combining CRDT algorithms, SignalR’s real‑time messaging, and Azure Cosmos DB’s globally distributed storage, developers can deliver truly collaborative experiences on the latest .NET 8 platform. The approach eliminates server‑side locks, guarantees convergence, and scales effortlessly across regions. Start with a simple G‑Counter, evolve to complex sequence CRDTs, and let the .NET ecosystem handle the heavy lifting.
Sources
- Microsoft Docs – SignalR for ASP.NET Core
- Azure Cosmos DB Documentation
- CRDT Survey – 2023 (ACM Computing Surveys)
Author: Mahmut Sarıkaya — sarikayadev.com