What are Conflicts?
In real-time collaborative applications, conflicts occur when multiple users attempt to modify the same data simultaneously. Multisynq provides built-in conflict resolution mechanisms to ensure data consistency across all connected users.
Multisynq’s Approach to Conflict Resolution
Deterministic Execution
Multisynq prevents most conflicts by using a deterministic execution model:
- Single Source of Truth: All application logic runs in the Model on the reflector
- Event Ordering: Events are processed in a consistent order across all clients
- Synchronized State: All users see the exact same state at any given time
Built-in Conflict Prevention
1. Atomic Operations
class MyModel extends Multisynq.Model {
init(options) {
super.init(options);
this.counter = 0;
this.subscribe(this.sessionId, "increment", this.incrementCounter);
}
incrementCounter() {
// This operation is atomic across all users
this.counter += 1;
this.publish(this.sessionId, "counterUpdated", this.counter);
}
}
2. Event Sequencing
All events are processed in the order they arrive at the reflector, ensuring consistent state updates.
3. Snapshot Consistency
Regular snapshots ensure all users stay synchronized, automatically resolving any temporary inconsistencies.
Handling User Intent Conflicts
While Multisynq prevents data conflicts, user intent conflicts may still occur. Here are strategies to handle them:
1. Optimistic UI Updates
class MyView extends Multisynq.View {
constructor(model) {
super(model);
this.subscribe(this.sessionId, "textChanged", this.updateText);
}
handleTextInput(text) {
// Optimistically update UI immediately
this.updateDisplayText(text);
// Send change to model for synchronization
this.publish(this.sessionId, "changeText", {
userId: this.viewId,
text: text,
timestamp: this.now()
});
}
updateText(data) {
// Apply authoritative update from model
this.updateDisplayText(data.text);
}
}
2. Last-Writer-Wins Strategy
class DocumentModel extends Multisynq.Model {
init(options) {
super.init(options);
this.document = { content: "", lastModified: 0, lastEditor: null };
this.subscribe(this.sessionId, "updateDocument", this.updateDocument);
}
updateDocument(data) {
// Simple last-writer-wins approach
if (data.timestamp > this.document.lastModified) {
this.document.content = data.content;
this.document.lastModified = data.timestamp;
this.document.lastEditor = data.userId;
this.publish(this.sessionId, "documentUpdated", this.document);
}
}
}
3. Collaborative Text Editing
For complex text editing, consider operational transformation:
class CollaborativeTextModel extends Multisynq.Model {
init(options) {
super.init(options);
this.content = "";
this.operations = [];
this.subscribe(this.sessionId, "textOperation", this.applyOperation);
}
applyOperation(operation) {
// Transform operation against concurrent operations
const transformedOp = this.transformOperation(operation, this.operations);
// Apply the transformed operation
this.content = this.applyToText(this.content, transformedOp);
this.operations.push(transformedOp);
// Notify all views of the change
this.publish(this.sessionId, "textUpdated", {
content: this.content,
operation: transformedOp
});
}
transformOperation(newOp, existingOps) {
// Implement operational transformation logic
// This is a simplified example
return newOp;
}
}
Advanced Conflict Resolution Patterns
1. Version Vectors
Track changes per user to handle complex merge scenarios:
class VersionedModel extends Multisynq.Model {
init(options) {
super.init(options);
this.data = {};
this.versions = {}; // Track version per user
this.subscribe(this.sessionId, "dataUpdate", this.handleUpdate);
}
handleUpdate(update) {
const { userId, data, version } = update;
// Check if this update is newer than what we have
if (!this.versions[userId] || version > this.versions[userId]) {
this.mergeData(data, userId);
this.versions[userId] = version;
this.publish(this.sessionId, "dataChanged", this.data);
}
}
mergeData(newData, userId) {
// Implement application-specific merge logic
Object.assign(this.data, newData);
}
}
2. Conflict-Free Replicated Data Types (CRDTs)
Use CRDT patterns for automatic conflict resolution:
class CRDTSetModel extends Multisynq.Model {
init(options) {
super.init(options);
this.set = new Map(); // userId -> Set of items
this.subscribe(this.sessionId, "addItem", this.addItem);
this.subscribe(this.sessionId, "removeItem", this.removeItem);
}
addItem(data) {
const { userId, item } = data;
if (!this.set.has(userId)) {
this.set.set(userId, new Set());
}
this.set.get(userId).add(item);
this.publishSetUpdate();
}
removeItem(data) {
const { userId, item } = data;
if (this.set.has(userId)) {
this.set.get(userId).delete(item);
}
this.publishSetUpdate();
}
getUnionSet() {
// Merge all user sets automatically
const union = new Set();
for (const userSet of this.set.values()) {
for (const item of userSet) {
union.add(item);
}
}
return union;
}
publishSetUpdate() {
this.publish(this.sessionId, "setUpdated", Array.from(this.getUnionSet()));
}
}
Best Practices
1. Design for Collaboration
- Plan your data structures to minimize conflicts
- Use fine-grained operations instead of bulk updates
- Consider user workflow when designing interactions
2. Provide User Feedback
class ConflictAwareView extends Multisynq.View {
handleConflict(conflictData) {
// Show user-friendly conflict resolution UI
this.showNotification(`Your change conflicted with ${conflictData.otherUser}'s edit`);
this.highlightConflictedArea(conflictData.location);
}
showMergeDialog(options) {
// Allow user to choose resolution strategy
this.displayMergeOptions(options);
}
}
3. Monitor and Log Conflicts
class ConflictMonitoringModel extends Multisynq.Model {
handlePotentialConflict(data) {
// Log conflict for analysis
console.log("Conflict detected:", {
users: data.conflictingUsers,
operation: data.operation,
timestamp: this.now()
});
// Resolve using your chosen strategy
this.resolveConflict(data);
}
}
Testing Conflict Scenarios
1. Simulated Concurrent Actions
// Test setup for conflict scenarios
class ConflictTestModel extends Multisynq.Model {
simulateConflict() {
// Simulate two users editing simultaneously
this.handleEdit({ userId: "user1", text: "Hello", position: 0 });
this.handleEdit({ userId: "user2", text: "Hi", position: 0 });
}
}
2. Network Partition Testing
Test how your application handles temporary disconnections and reconnections.
Summary
Multisynq’s deterministic execution model prevents most data conflicts automatically. For user intent conflicts:
- Use optimistic UI updates for responsiveness
- Implement appropriate conflict resolution strategies (last-writer-wins, operational transformation, CRDTs)
- Provide clear user feedback when conflicts occur
- Design data structures to minimize conflicts
- Test thoroughly with concurrent user scenarios
By following these patterns, you can build collaborative applications that handle conflicts gracefully while maintaining data consistency across all users.
Responses are generated using AI and may contain mistakes.