Data Structures
TopGun provides two primary CRDT-based data structures: LWW-Map (Last-Write-Wins Map) and OR-Map (Observed-Remove Map). Understanding the difference between them is critical for preventing data loss in distributed applications.
LWW-Map (Last-Write-Wins)
The LWW-Map is the default map structure in TopGun. As the name suggests, it resolves conflicts by accepting the update with the highest timestamp (Last-Write-Wins).
Best for: Key-Value stores where keys represent unique entities or fields that are overwritten (e.g., User Profile, Configuration, Document Fields).
How it works
- Each key holds a single value and a timestamp.
- When two nodes update the same key, the one with the later timestamp wins.
- If you use it for a list/collection by generating random keys, you might encounter issues if multiple users try to “add” to the list simultaneously and happen to generate colliding keys (rare) or if you try to manage the list as a single value (overwrites).
Usage
// Get an LWW-Map
const profile = client.getMap('user:123');
// Set a value (overwrites existing)
profile.set('name', 'Alice');
profile.set('status', 'online');
// Remove a key
profile.remove('status'); OR-Map (Observed-Remove)
The OR-Map is designed for Sets and Collections. It allows you to add multiple items to a key without them overwriting each other, even if they have the same value (depending on implementation) or if they are added concurrently. In TopGun, the OR-Map is implemented as a map where values are “observed” and can be removed without race conditions affecting re-additions.
Best for: Collections, Lists, Sets, and scenarios where multiple users might add/remove items concurrently (e.g., Todo Lists, Chat Messages in a channel, Tags).
Why LWW is bad for Sets
Imagine two users adding a “Todo Item” to a list. If you used LWW-Map and stored the list as a JSON array under a single key todos, the user who saves last would overwrite the other user’s addition.
With OR-Map, each addition is treated as a unique operation. Even if two users add the same value, or different values, both are preserved (unless one is explicitly removed).
Usage
// Get an OR-Map
const todos = client.getORMap('todos:project-a');
// Add items (treated as a Set/Collection)
// .add() returns an ORMapRecord with a unique 'tag' and timestamp
const record1 = todos.add('active', { title: 'Write docs' });
const record2 = todos.add('active', { title: 'Fix bugs' });
// Removing an item
// In OR-Map, you remove by key AND exact value
// This marks all observed instances of this value as removed
todos.remove('active', { title: 'Write docs' }); Tombstones & Deletion
When you delete data in TopGun, the record isn’t immediately removed from storage. Instead, a tombstone is created. This is essential for proper synchronization with offline clients.
LWW-Map Tombstones
A tombstone is simply a record with value: null:
{ value: null, timestamp: {...} }OR-Map Tombstones
Removed tags are stored in a tombstone set:
tombstones: Set<tag>
Garbage Collection: Tombstones are periodically cleaned up via GC. The server coordinates this process to ensure all replicas have seen the deletion before permanently removing the tombstone. This prevents “resurrection” of deleted data.
Summary
| Feature | LWW-Map | OR-Map |
|---|---|---|
| Primary Use Case | Key-Value properties (Profile, Config) | Collections, Sets, Lists (Todos, Comments) |
| Conflict Resolution | Last Write Wins (Overwrites) | Union of Adds (Merge), Observed Remove |
| Deletion | Tombstone (value: null) | Tag added to tombstone set |
| API Method | client.getMap() | client.getORMap() |