DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Eventual Consistency: 3 Decision-Making Criteria for Side Projects

Side projects are, for me, a space to try new things on one hand, and to solve a problem in my head on the other. Generally, in these projects, we want everything to be immediate and perfect. But both our time and our money are limited. This is exactly where Eventual Consistency becomes a lifesaver for me. Not everything needs to be consistent instantly, all the time. Sometimes, being able to say, "it'll be fine," provides critical flexibility to bring projects to life.

In this post, I will explain when I prefer the Eventual Consistency approach for my own side projects, the 3 core criteria I consider when making this decision, and the experiences I've gained in this process. This isn't just a technical choice; it's also part of a philosophy on how I manage my personal resources.

Eventual Consistency: An Art of Balance in Life and Software

Eventual Consistency is a model that assumes data in a system will become consistent after a certain period, not instantly. This means when data is updated, that update might not propagate to all copies immediately; but eventually, they all reach the same state. While in enterprise projects this is often associated with complex distributed system architectures, for me in side projects, this concept has a much more personal meaning.

In life, not everything has to be perfect instantly. Sometimes, allowing something to mature over time, rather than rushing to finish it, yields better results. It's no different in software. In a financial calculator or a task management app that I'm developing as my own side product, it's not essential for every user to see every piece of data within milliseconds. What matters is reaching the correct result eventually. This flexibility is one of the biggest factors that allows projects to launch, especially for developers like me with limited time.

Criterion 1: Data Value and My Tolerance for Latency

The first thing I consider when deciding on Eventual Consistency is how critical the relevant data is and how much latency it can tolerate. Every piece of data has a different "value," and this value directly affects the need for consistency. For example, for instant balance information in a bank's internal platform, strong consistency is a must; even a 50-millisecond delay can cause serious problems. But in a spam blocker app I'm developing on the Android side, updating the blocked numbers list every 5 minutes wouldn't bother anyone.

In my own side projects, I generally evaluate data with questions like: "How much of a problem would it create if this information was updated 10 seconds late?" or "Would a 1-minute delay in updating this information disrupt the user's workflow?". If the answer is "it wouldn't be much of a problem," then Eventual Consistency is a good candidate for me. For instance, on a page showing historical transaction records for a financial calculator on my own website, it's acceptable for the most recently added transaction to appear 1-2 seconds later. However, if it needs to start processing a value the user just entered immediately, then I'd want a state close to strong consistency.

đź’ˇ Pragmatic Data Evaluation

When determining the criticality level of a piece of data, thinking in terms of the "most common scenario" rather than the "worst-case scenario" yields more realistic results for side projects. Always considering the absolute worst-case scenario often leads to unnecessary complexity.

In a production ERP system, it's critical for an order appearing on operator screens in the production planning flow to be visible instantly. In a project like that recently, when an operator finished the previous order and pressed the "complete" button, the next order had to appear on the screen immediately. There was no room for eventual consistency here, as a 5-second delay could halt the production line. But for the "weekly production reports" section of the same ERP, a 30-minute data delay wouldn't be an issue. Making this distinction forms the basis of my Eventual Consistency decisions, both in enterprise and side projects.

Criterion 2: Cost and Operational Overhead: My Pocket Money and My Sleep's Value

The biggest constraints in side projects are usually budget and my personal time. Ensuring strong consistency often requires more expensive and complex infrastructure. Things like replication synchronizations, distributed locking mechanisms, and two-phase commit protocols both extend development time and increase server costs. For a solo developer like me, this burden can mean the project never gets finished.

Eventual Consistency lightens this load. For example, in a microservice architecture running on my own VPS, instead of ensuring instant data consistency between services, I use asynchronous communication via a message queue (like Redis Streams or a simple PostgreSQL table). This makes the services independent of each other and prevents the entire system from crashing in case of an error. Recently, in the backend of one of my side products, I used this method to transfer data from a service processing user data to a reporting service. The operation took 2 seconds instead of 100 milliseconds, but that was an acceptable trade-off for me.

# Simple message queue simulation (can also be done with PostgreSQL or Redis)
# This represents an "outbox" pattern for Eventual Consistency.

from collections import deque
import time

class MessageQueue:
    def __init__(self):
        self.queue = deque()

    def publish(self, message):
        self.queue.append(message)
        print(f"[{time.time():.2f}] Message published: {message}")

    def consume(self):
        if self.queue:
            message = self.queue.popleft()
            print(f"[{time.time():.2f}] Message consumed: {message}")
            return message
        return None

# Usage example
queue = MessageQueue()

# Publish from Service A
queue.publish({"user_id": 1, "action": "profile_update"})
time.sleep(0.1) # Simulated delay

# Consume from Service B
queue.consume()

# Output (approximate):
# [1716902400.00] Message published: {'user_id': 1, 'action': 'profile_update'}
# [1716902400.10] Message consumed: {'user_id': 1, 'action': 'profile_update'}
Enter fullscreen mode Exit fullscreen mode

This type of approach allows me to use fewer server resources (a simple background job requiring less CPU/RAM) and simplifies my development and debugging processes. Ultimately, when I don't have a primary goal like making money from side projects or reaching a large audience, the value of my sleep and the money I spend outweighs the need for instant consistency.

Criterion 3: User Experience and My Expectations

The third criterion relates to the user experience the project aims for and my own expectations. To what extent can the users of an application (which is often me, initially) tolerate a slight delay? In which situations do they expect immediate feedback? It's important to strike a good balance here.

For example, when I block a number in my own Android spam app, the blocking is expected to take effect instantly. There's no room for eventual consistency here. However, it's acceptable for the app to update the list of new spam numbers in the background with a 5-10 minute delay. Users expect this kind of operation "in the background"; they don't expect immediate feedback.

Another example: Consider a user creating a new order in an ERP system for a manufacturing company. A message confirming that the order has been successfully saved to the database should appear instantly. However, the processing of this order in the backend inventory system or the shipment planning module might be delayed by 30 seconds. As long as the user is guaranteed that the order has been received, having backend processes run with eventual consistency won't cause problems.

ℹ️ Managing Expectations with Eventual Consistency

Clearly informing the user about how long they might wait or that a process is continuing in the background is key to managing the perceptual delays caused by eventual consistency. Messages like "Your request has been queued and will be completed shortly" increase user satisfaction.

When I publish a blog post on my own website, it's important for it to appear "published" instantly when I save it. But it's fine if search engine indexing or the RSS feed updates 5 minutes later. This is how I manage my own expectations as a user (and also as a developer). If I need to see the result of an operation immediately, I prefer strong consistency. But if the operation is a "notification" or a "report," then eventual consistency is a reasonable option for me.

What I've Learned While Implementing Eventual Consistency in Side Projects

Although Eventual Consistency has attractive advantages, I've also faced some challenges while implementing this approach in my side projects. One of the biggest issues was determining when and how the "final state" would be guaranteed. Once, in a task management application I developed myself, I noticed that synchronization to my other devices was very slow when I completed a task. It sometimes took longer than 1 minute, leading to the dilemma of "Did I complete it or not?".

To solve this, I added a simple "last_updated" timestamp mechanism and ensured that each device checked the server for this timestamp at regular intervals (e.g., every 15 seconds). If the timestamp on the server was newer than the one on my device, I would pull the data. This significantly improved the user experience while preserving the system's eventual consistency model. In a previous post I wrote about [related: mobile synchronization issues], I discussed such problems in more detail.

Another important lesson was planning in advance how Eventual Consistency would behave in error situations. What happens if a process in a message queue fails? Should the message be retried? Or should it go to a "dead-letter queue"? Answering these questions upfront prevented me from waking up at midnight wondering, "Why wasn't this data updated?". In one of my side products, I wrote a simple Python script to retry messages that couldn't be processed on a queue I was using on Redis after a certain period, and if still unsuccessful, log them to a separate file. This provided me with operational ease and minimized the risk of data loss.

# Simple retry mechanism (pseudo-code)
def process_message_with_retry(message, max_retries=3):
    retries = 0
    while retries < max_retries:
        try:
            # Message processing logic
            print(f"Processing message: {message}")
            # If processing fails, raise an exception
            # if random.random() < 0.3: # Simulate 30% error rate
            #    raise ValueError("Processing error!")
            print(f"Message processed successfully: {message}")
            return True
        except Exception as e:
            retries += 1
            print(f"Error processing message ({e}). Retrying {retries}/{max_retries}...")
            time.sleep(2 ** retries) # Exponential backoff
    print(f"Message failed after {max_retries} retries: {message}. Sending to dead-letter queue.")
    return False

# Usage:
# process_message_with_retry({"data": "critical info"})
Enter fullscreen mode Exit fullscreen mode

Practical solutions like these increase the applicability of Eventual Consistency in side projects. The key is to understand the risks and establish simple yet effective mechanisms to manage them.

Conclusion: Letting Go with the Flow and Holding Tight Where It Matters

My approach to Eventual Consistency in side projects is more than just a technical choice; it's a reflection of a broader life philosophy. Instead of expecting everything to be perfect and instant, identifying what is truly critical and allowing flexibility for the rest ensures project progress and saves me from unnecessary stress. Data value, cost, and user expectations serve as my compass in establishing this balance.

My clear stance is this: if a piece of data or an operation can fulfill its basic functionality without instant consistency and doesn't significantly negatively impact user experience, then Eventual Consistency is my default option. This provides me with faster prototyping, lower operational costs, and fewer headaches. Just like in life, in software, instead of trying to control everything all the time, sometimes it's better to let things flow and only hold tight where it truly matters. In the next post, we can discuss [related: time management and software projects].

Top comments (0)