Skip to Content

Versioning & Resync

This guide explains how Mesh handles record versioning and resynchronization to ensure data consistency across clients.

The Challenge of Distributed State

In a distributed real-time system, maintaining consistent state across multiple clients is challenging:

  • Clients may disconnect temporarily
  • Network issues can cause messages to be lost
  • Updates may arrive out of order
  • Clients may join after many updates have occurred

Mesh addresses these challenges with a robust versioning and resynchronization system for records.

How Versioning Works

Every record in Mesh has a version number that increments with each update:

  1. When a record is first created, it starts at version 1
  2. Each update increments the version by 1
  3. Clients track the current version they have
  4. Updates include the new version number

This versioning system allows clients to detect if they’ve missed any updates.

Version Tracking

When a client subscribes to a record, it receives the current value and version:

const { success, record, version } = await client.subscribeRecord( "user:123", (update) => { console.log(`Received update v${update.version}`); // Handle update... } ); console.log(`Initial record: ${record}, version: ${version}`);

The client stores this version number and expects each subsequent update to have a version that is exactly one higher than its current version.

Detecting Missed Updates

In patch mode, version numbers are critical for detecting missed updates:

let currentVersion = version; client.subscribeRecord("user:123", (update) => { // Check if we missed any updates if (update.version !== currentVersion + 1) { console.log(`Version mismatch! Expected ${currentVersion + 1}, got ${update.version}`); // Resync will happen automatically } currentVersion = update.version; // Handle update... });

If a client detects that update.version !== currentVersion + 1, it knows it has missed one or more updates.

Automatic Resynchronization

When a version mismatch is detected, Mesh automatically handles resynchronization:

  1. The client detects the version mismatch
  2. The client requests a full resync from the server
  3. The server sends the complete current value and version
  4. The client updates its local state and continues from the new version

This happens transparently to your application code. In patch mode, your update handler will receive a full update instead of a patch:

let productData = {}; let currentVersion = version; client.subscribeRecord( "product:456", (update) => { if (update.patch) { // Normal update applyPatch(productData, update.patch); } else { // Resync update (full value) productData = update.full; console.log(`Resynced to version ${update.version}`); } currentVersion = update.version; }, { mode: "patch" } );

How Resync Works Under the Hood

When a client detects a version mismatch, here’s what happens:

  1. The client automatically unsubscribes from the record
  2. The client immediately resubscribes to the record
  3. The server sends the current full value and version
  4. The client’s update handler receives a full update
  5. Normal operation continues from the new version

This process is handled automatically by the Mesh client SDK.

Versioning in Full Mode

Even in full mode, version numbers are included with each update:

client.subscribeRecord("user:123", (update) => { console.log(`Received full update v${update.version}`); // update.full contains the complete record });

While version checking is less critical in full mode (since you always get the complete state), the version numbers are still useful for tracking the update history.

Server-Side Versioning

On the server, version numbers are stored in Redis alongside the record values. When you call publishRecordUpdate(), the server:

  1. Retrieves the current value and version from Redis
  2. Increments the version
  3. Stores the new value and version in Redis
  4. Computes a patch (if needed)
  5. Broadcasts the update with the new version
// Server-side await server.publishRecordUpdate("user:123", { name: "Alice", email: "alice@example.com", }); // Later... await server.publishRecordUpdate("user:123", { name: "Alice", email: "alice@updated.com", status: "active", });

Handling Reconnection

When a client reconnects after a disconnection, it needs to resubscribe to records:

// Track active record subscriptions const activeRecords = new Map(); // Subscribe to a record and track it const subscribeToRecord = async (recordId, callback, options) => { const result = await client.subscribeRecord(recordId, callback, options); if (result.success) { activeRecords.set(recordId, { callback, options }); } return result; }; // Restore subscriptions after reconnection client.onReconnect(async () => { console.log("Reconnected, restoring record subscriptions..."); for (const [recordId, { callback, options }] of activeRecords.entries()) { await client.subscribeRecord(recordId, callback, options); } });

When resubscribing, the client will receive the current value and version, effectively performing a resync.

Conflict Resolution

Mesh doesn’t include built-in conflict resolution for simultaneous updates. The last update wins. If you need more sophisticated conflict resolution:

  1. Use server-side logic to validate and merge updates
  2. Implement optimistic updates with rollback on the client
  3. Consider using a command-based approach for critical operations
// Server-side command with validation server.exposeCommand("update-user-field", async (ctx) => { const { userId, field, value, expectedVersion } = ctx.payload; // Get current record const { value: currentValue, version } = await server.recordManager.getRecord(`user:${userId}`); // Check version to detect conflicts if (expectedVersion !== version) { return { success: false, reason: "conflict" }; } // Update just one field const newValue = { ...currentValue, [field]: value }; // Publish update await server.publishRecordUpdate(`user:${userId}`, newValue); return { success: true, version: version + 1 }; });

Best Practices

  1. Trust the versioning system

    • Let Mesh handle version tracking and resync
    • Don’t try to implement your own versioning on top of Mesh
  2. Design for eventual consistency

    • Assume clients may temporarily have different views of the data
    • Use UI patterns that gracefully handle state transitions
  3. Consider update frequency

    • High-frequency updates may lead to more resyncs
    • Batch related changes into single updates when possible
  4. Handle reconnection properly

    • Track active subscriptions
    • Resubscribe after reconnection
  5. Monitor version jumps

    • Large version jumps may indicate network issues
    • Consider logging when significant resyncs occur
Last updated on
© 2025