Consistency Models

In a single-server database (like SQLite), consistency is easy. If you write X=5, the next read sees X=5.

In a distributed system like DynamoDB, data is replicated across three Availability Zones (AZs) for durability. This introduces a delay—Replication Lag.

You must choose between Speed (Eventual Consistency) and Correctness (Strong Consistency).

1. The CAP Theorem in Practice

DynamoDB is an AP system by default (Available & Partition Tolerant), but can act as CP (Consistent & Partition Tolerant) on a per-request basis.

1. Eventual Consistency (The Default)

  • Behavior: When you write, DynamoDB acknowledges success once the Leader node writes the data. Replication to other AZs happens asynchronously (~10-20ms later).
  • Risk: If you read from a replica immediately after writing, you might see old data.
  • Benefit: Fastest latency, lowest cost (0.5 RCU per 4KB), highest availability.
  • Use Case: Social media feeds, comments, likes.

2. Strong Consistency

  • Behavior: DynamoDB ensures the read reflects all successful writes. It usually consults the Leader node.
  • Risk: Higher latency. If the Leader is unavailable (rare), the read fails (Availability drops).
  • Cost: 2x more expensive (1.0 RCU per 4KB).
  • Use Case: Financial transactions, inventory counts, “Check if username taken”.

2. Interactive: Replication Simulator

Visualize how data propagates and why “Eventual Consistency” can return stale data.

Replication Lag Visualizer

Leader (AZ1)
v1
Replica (AZ2)
v1
Replica (AZ3)
v1
System ready. All nodes at v1.

3. Code Implementation: Consistent Reads

To enforce Strong Consistency, simply set the ConsistentRead flag to true.

Java Implementation

import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.util.Map;

public class GetItemStronglyConsistent {
    public static void main(String[] args) {
        DynamoDbClient ddb = DynamoDbClient.create();

        GetItemRequest request = GetItemRequest.builder()
            .tableName("Orders")
            .key(Map.of("OrderId", AttributeValue.builder().s("101").build()))
            .consistentRead(true) // <--- The Toggle
            .build();

        GetItemResponse response = ddb.getItem(request);
        System.out.println("Item: " + response.item());
    }
}

Go Implementation

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
	cfg, _ := config.LoadDefaultConfig(context.TODO())
	svc := dynamodb.NewFromConfig(cfg)

	input := &dynamodb.GetItemInput{
		TableName: aws.String("Orders"),
		Key: map[string]types.AttributeValue{
			"OrderId": &types.AttributeValueMemberS{Value: "101"},
		},
		ConsistentRead: aws.Bool(true), // <--- The Toggle
	}

	result, err := svc.GetItem(context.TODO(), input)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	fmt.Println("Item:", result.Item)
}