Implementing Eventual Consistency in Go
This challenge focuses on building a system that demonstrates eventual consistency. Eventual consistency is a consistency model that guarantees, if no new updates are made to a given data item, eventually all accesses to that item will return the last updated value. This is crucial for distributed systems where immediate consistency is often impractical or impossible due to network latency and partitions.
Problem Description
Your task is to design and implement a simplified system that simulates eventual consistency for a shared data item (e.g., a counter or a string). You will have multiple "clients" or "nodes" that can read and update this data. Updates made by one client should propagate to others over time, ensuring that eventually, all clients will see the same, most recent value.
Key Requirements:
- Shared Data: A single piece of data (e.g., an integer counter) that can be accessed and modified by multiple concurrent processes.
- Update Propagation: Implement a mechanism for updates to propagate from one process to others. This propagation should not be instantaneous.
- Read Operations: Clients should be able to read the current value of the data.
- Eventual Convergence: Design the system such that if no further updates occur, all clients will eventually read the same, latest value.
- Concurrency Safety: The system must handle concurrent reads and writes safely, preventing race conditions within individual nodes.
- Simulated Network Delay/Failure (Optional but Recommended): Introduce delays or simulated message loss to better mimic real-world distributed systems.
Expected Behavior:
- A client performs an update.
- Other clients continue to read their potentially stale values for a period.
- Over time, the update propagates, and eventually, all clients will read the new value.
- If multiple updates occur rapidly, the system should eventually converge on the last updated value.
Edge Cases to Consider:
- What happens if a client is offline when an update occurs? How does it catch up?
- How do you handle concurrent updates from different clients? (For this challenge, focus on eventual convergence to the latest value, not necessarily a specific ordering of concurrent updates if they are extremely close).
- What if a client reads during a propagation phase?
Examples
Example 1: Simple Update Propagation
Imagine a shared counter initialized to 0.
- Client A updates the counter to
1. - Client B reads the counter and currently sees
0. - Client C reads the counter and currently sees
0. - After some time, the update from Client A propagates.
- Now, if Client B reads again, it should see
1. - If Client C reads again, it should see
1.
Example 2: Multiple Rapid Updates
Imagine a shared counter initialized to 0.
- Client A updates the counter to
1. - Client B updates the counter to
2. - Client C updates the counter to
3. - Initially, other clients might see
0or1or2. - However, eventually, all clients should converge to seeing
3, the last updated value.
Example 3: Handling a Stale Read and Catching Up
Imagine a shared counter initialized to 0.
- Client A updates the counter to
5. - Client B reads and sees
0(stale). - Client C reads and sees
0(stale). - Meanwhile, another update from Client D changes the value to
10. - Eventually, Client B receives the update from Client A and now sees
5. - Then, Client B receives the update from Client D and now sees
10. - Eventually, Client C also receives the update from Client D and sees
10.
Constraints
- The system will simulate at least 3 concurrent clients/nodes.
- Updates should be propagated asynchronously, meaning a direct, synchronous call to update all other nodes is not permitted.
- The core data structure representing the shared state should be accessible and updatable.
- Focus on the logic of eventual consistency rather than high-performance distributed consensus algorithms (e.g., Paxos, Raft).
- Your solution should be executable and demonstrate the eventual convergence.
Notes
- You can use Go's concurrency primitives like goroutines and channels to simulate concurrent clients and message passing.
- Consider using a simple data structure (like a
structholding a value and a version number/timestamp) to represent the shared state. The version number can help in determining the "latest" value. - For simulating propagation, you might use channels to send "update notifications" or the updated data itself between goroutines.
- Think about how a client would reconcile incoming updates with its current known state.
- You can use
time.Sleepto simulate network delays.