Overview
Consistency models establish the rules that govern how and when updates made by one process become visible to other processes in a distributed system. Each model represents a different set of trade-offs between data consistency, system availability, and performance. The choice of consistency model fundamentally shapes application behavior, data integrity guarantees, and operational characteristics.
In a single-node system, consistency is straightforward: operations execute in program order, and all reads see the most recent write. Distributed systems complicate this picture. Multiple nodes maintain copies of data, network partitions can isolate nodes, and operations may execute concurrently across different locations. Consistency models formalize what guarantees the system provides despite these challenges.
The spectrum of consistency models ranges from strong consistency, where all nodes see the same data at the same time, to weak consistency, where different nodes may temporarily diverge. Stronger models provide simpler programming semantics but typically sacrifice availability and performance. Weaker models enable higher availability and better performance but require applications to handle data conflicts and eventual convergence.
# Strong consistency example: All readers see the write immediately
database.write(key: "user:123", value: {name: "Alice"})
database.read(key: "user:123")
# => Always returns {name: "Alice"} across all nodes
# Eventual consistency example: Readers may see stale data temporarily
cache.set("session:456", {logged_in: true})
cache.get("session:456")
# => May return nil briefly on different nodes before propagation
Understanding consistency models requires distinguishing between the logical ordering of operations and their physical execution timing. A consistency model doesn't dictate when operations actually execute; it defines what ordering guarantees the system must maintain from the client's perspective.
Key Principles
Consistency models build on several foundational concepts that define their behavior and guarantees.
Operation Visibility determines when writes become observable to read operations. In a strongly consistent system, a write must be visible to all subsequent reads immediately. In a weakly consistent system, there may be a window of time during which some reads return old values while the update propagates.
Operation Ordering specifies the sequence in which operations appear to execute. Sequential consistency requires all operations to appear in some global order consistent with the program order of each individual process. Causal consistency only preserves the order of causally related operations, allowing concurrent operations to be observed in different orders by different processes.
Linearizability represents the strongest single-object consistency guarantee. A linearizable system ensures that operations appear to execute atomically at some point between their invocation and response, with all operations respecting a single global order. This creates the illusion of a single, non-replicated data store.
# Linearizable operation semantics
class LinearizableRegister
def write(value)
# Write must take effect at some instant during this method
timestamp = Time.now.to_f
@value = {data: value, timestamp: timestamp}
end
def read
# Read must return value from some instant during this method
@value[:data]
end
end
Serializability applies to transactions involving multiple objects. A serializable execution of concurrent transactions produces the same result as some serial execution of those transactions. This guarantee applies to the outcome but doesn't specify the actual order or timing.
Causal Consistency maintains the ordering of operations that have a cause-and-effect relationship. If operation A happens before operation B in a way that B could observe A's effects, all processes must see A before B. Concurrent operations that don't have a causal relationship can be observed in any order.
Eventual Consistency guarantees that if no new updates occur, all replicas will eventually converge to the same value. This model provides no bounds on convergence time and no ordering guarantees during the convergence period. The system may temporarily violate consistency constraints.
# Causal consistency example with version vectors
class CausalStore
def initialize
@vector_clock = Hash.new(0)
@data = {}
end
def write(key, value, observed_version)
# Increment local clock
@vector_clock[node_id] += 1
# Store value with version that includes causal history
@data[key] = {
value: value,
version: @vector_clock.dup,
depends_on: observed_version
}
end
def read(key)
entry = @data[key]
{value: entry[:value], version: entry[:version]}
end
end
Monotonic Reads ensure that if a process reads a value v1, subsequent reads by that process never return older values. This prevents time from appearing to flow backward from a client's perspective.
Monotonic Writes guarantee that write operations from a single process are applied in the order they were issued. If a process writes v1 then v2, all processes that observe these writes see them in that order.
Read-Your-Writes consistency ensures that a process always sees its own previous writes. After writing a value, subsequent reads by that process reflect the written value, even if other processes might not yet see the update.
Design Considerations
Selecting an appropriate consistency model requires analyzing application requirements against system constraints and trade-offs.
CAP Theorem Implications fundamentally constrain distributed system design. The theorem states that in the presence of network partitions, a system must choose between consistency and availability. Strong consistency models sacrifice availability during partitions, while weak consistency models sacrifice immediate consistency to maintain availability. The practical question becomes: how does the application behave when nodes cannot communicate?
Applications that require correctness guarantees often mandate strong consistency despite availability costs. Financial transactions, inventory management, and seat reservations cannot tolerate conflicting states. The cost of inconsistency exceeds the cost of occasional unavailability.
# Strong consistency for inventory management
class InventorySystem
def reserve_item(item_id, quantity)
# Must use distributed lock or consensus
transaction do |tx|
current = tx.read(item_id, isolation: :serializable)
if current.quantity >= quantity
tx.write(item_id, {quantity: current.quantity - quantity})
:success
else
:insufficient_inventory
end
end
end
end
Applications that prioritize availability often accept eventual consistency. Social media feeds, content caching, and metrics collection can tolerate temporary inconsistency. Users accept that likes and follower counts may briefly differ across views.
Application-Level Conflict Resolution becomes necessary with weak consistency models. When concurrent updates create conflicts, the application must define merge semantics. Last-write-wins strategies are simple but can lose data. Custom merge functions preserve all updates but increase complexity.
# Eventual consistency with conflict resolution
class EventuallyConsistentCounter
def increment(amount)
# Increment is commutative - conflicts resolve automatically
@replicas.each do |replica|
replica.merge_update(node_id, amount)
end
end
def value
# Sum all increments from all replicas
@replicas.sum { |r| r.local_value }
end
end
Session Guarantees provide middle-ground consistency appropriate for many applications. Read-your-writes, monotonic reads, and monotonic writes can be implemented without full strong consistency, giving clients coherent views without global coordination.
Geographic Distribution amplifies consistency trade-offs. Synchronous replication across continents adds hundreds of milliseconds to every write. Asynchronous replication provides low latency but creates windows of inconsistency. Multi-datacenter applications often partition data by geographic region, accepting weaker cross-region consistency for stronger within-region guarantees.
Write Patterns influence model selection. Write-heavy workloads benefit from weak consistency because strong consistency requires coordinating every write. Read-heavy workloads often tolerate eventual consistency since reads outnumber writes, and cached values remain valid for reasonable periods.
Data Relationships complicate consistency requirements. Operations spanning multiple objects require transaction semantics or application-level coordination. A weak consistency model may suffice for independent objects but prove inadequate when maintaining invariants across objects.
Implementation Approaches
Different mechanisms achieve various consistency guarantees in distributed systems.
Primary-Backup Replication designates one replica as primary, handling all writes. The primary synchronously or asynchronously propagates updates to backup replicas. Synchronous propagation provides strong consistency at the cost of write latency and availability during primary failure. Asynchronous propagation reduces latency but creates consistency windows.
class PrimaryBackupStore
def initialize(primary:, backups:)
@primary = primary
@backups = backups
end
def write(key, value)
# Write to primary first
@primary.set(key, value)
# Synchronously replicate to quorum of backups
required_acks = (@backups.size / 2) + 1
acks = @backups.count { |backup| backup.replicate(key, value) }
raise ReplicationError unless acks >= required_acks
end
def read(key)
# Can read from any replica
(@backups + [@primary]).sample.get(key)
end
end
Quorum Systems achieve consistency without a single primary. Each operation must be acknowledged by a majority of replicas. For N replicas, writes require W acknowledgments and reads require R acknowledgments where W + R > N. This overlap ensures reads see the most recent write.
Read quorums and write quorums can be adjusted to optimize for different access patterns. Higher write quorums (W = N) provide read-one-write-all semantics. Lower write quorums (W = 1, R = N) optimize for write latency. The key constraint is W + R > N for consistency.
class QuorumStore
def initialize(replicas:, write_quorum:, read_quorum:)
@replicas = replicas
@write_quorum = write_quorum
@read_quorum = read_quorum
end
def write(key, value)
version = generate_version
writes = @replicas.map { |r|
Thread.new { r.write(key, value, version) }
}
successes = writes.count { |t| t.value == :success }
raise QuorumError unless successes >= @write_quorum
end
def read(key)
reads = @replicas.map { |r|
Thread.new { r.read(key) }
}
responses = reads.map(&:value).compact
raise QuorumError unless responses.size >= @read_quorum
# Return value with highest version
responses.max_by { |r| r[:version] }[:value]
end
end
Vector Clocks track causality in distributed systems. Each node maintains a counter for every other node, creating a vector of logical timestamps. When a node performs an operation, it increments its own counter. When nodes exchange messages, they merge vector clocks, taking the maximum of each counter. This allows detecting concurrent operations and preserving causal ordering.
Vector clocks grow with the number of nodes, creating scaling challenges. Dotted version vectors optimize this by tracking only active writers. Version vectors trade space overhead for precise conflict detection.
class VectorClock
def initialize(node_id)
@node_id = node_id
@clocks = Hash.new(0)
end
def increment
@clocks[@node_id] += 1
end
def merge(other_clock)
@clocks.merge!(other_clock.clocks) { |_, v1, v2| [v1, v2].max }
end
def happens_before?(other)
@clocks.all? { |node, count| count <= other.clocks[node] } &&
@clocks != other.clocks
end
def concurrent?(other)
!happens_before?(other) && !other.happens_before?(self)
end
attr_reader :clocks
end
Consensus Algorithms like Raft and Paxos provide strong consistency by ensuring all replicas agree on operation order. Consensus protocols elect a leader that sequences operations. Followers replicate the leader's log. The system maintains consistency despite failures as long as a majority of nodes remain available.
Consensus protocols pay latency costs for coordination. Each operation requires at least one round-trip to a majority of nodes. This makes them unsuitable for geographically distributed systems spanning continents but appropriate for regional clusters.
Conflict-Free Replicated Data Types (CRDTs) provide eventual consistency without coordination. CRDTs define merge operations that are commutative, associative, and idempotent. Replicas can accept updates independently and merge states later, guaranteed to converge to the same value.
Different CRDT designs suit different data types. G-Counters only increment. PN-Counters support both increment and decrement. OR-Sets handle add and remove operations. The constraint is that merge operations must resolve conflicts deterministically without coordination.
class GCounter
def initialize(node_id)
@node_id = node_id
@counts = Hash.new(0)
end
def increment(amount = 1)
@counts[@node_id] += amount
end
def value
@counts.values.sum
end
def merge(other)
@counts.merge!(other.counts) { |_, v1, v2| [v1, v2].max }
end
attr_reader :counts
end
Ruby Implementation
Ruby applications interact with consistency models primarily through database clients and distributed data stores.
ActiveRecord with PostgreSQL provides serializable isolation through snapshot isolation. PostgreSQL's serializable snapshot isolation detects conflicts by tracking read and write dependencies between concurrent transactions. When conflicts occur, one transaction aborts with a serialization failure.
class Account < ActiveRecord::Base
def transfer_to(other_account, amount)
Account.transaction(isolation: :serializable) do
current_balance = balance
raise InsufficientFunds if current_balance < amount
update!(balance: current_balance - amount)
other_account.update!(balance: other_account.balance + amount)
end
rescue ActiveRecord::SerializationFailure
# Retry transaction on conflict
retry
end
end
Redis offers eventual consistency by default with optional strong consistency through Redis Sentinel or Redis Cluster. Single Redis instances provide linearizable operations within that instance. Replication between primary and replicas is asynchronous, creating brief consistency windows.
require 'redis'
class SessionStore
def initialize
@redis = Redis.new(
cluster: ['redis://node1:6379', 'redis://node2:6379'],
reconnect_attempts: 3
)
end
def store_session(session_id, data)
# Eventual consistency - writes may not be immediately visible
@redis.setex("session:#{session_id}", 3600, data.to_json)
end
def get_session(session_id)
json = @redis.get("session:#{session_id}")
json ? JSON.parse(json) : nil
end
end
Elasticsearch provides eventual consistency with tunable replication. The write consistency parameter controls how many replicas must acknowledge a write. Setting it to "quorum" requires a majority of replicas, providing stronger consistency at the cost of write availability.
require 'elasticsearch'
class SearchIndex
def initialize
@client = Elasticsearch::Client.new(
hosts: ['es1:9200', 'es2:9200', 'es3:9200']
)
end
def index_document(id, document)
@client.index(
index: 'documents',
id: id,
body: document,
consistency: 'quorum', # Require majority ack
refresh: false # Don't wait for refresh
)
end
def search(query)
@client.search(
index: 'documents',
body: {query: {match: {content: query}}}
)
end
end
Cassandra through the cassandra-driver gem exposes consistency levels explicitly. Applications specify consistency requirements per operation. ONE requires only one replica to acknowledge, QUORUM requires a majority, and ALL requires all replicas. LOCAL_QUORUM operates within a single datacenter.
require 'cassandra'
class DistributedCounter
def initialize
@cluster = Cassandra.cluster(
hosts: ['cassandra1', 'cassandra2', 'cassandra3']
)
@session = @cluster.connect('analytics')
end
def increment_counter(metric_id, amount)
@session.execute(
"UPDATE counters SET count = count + ? WHERE metric_id = ?",
arguments: [amount, metric_id],
consistency: :quorum # Require majority for consistency
)
end
def read_counter(metric_id)
rows = @session.execute(
"SELECT count FROM counters WHERE metric_id = ?",
arguments: [metric_id],
consistency: :one # Accept eventual consistency for reads
)
rows.first['count']
end
end
DynamoDB through the aws-sdk gem defaults to eventual consistency with optional strong consistency for reads. Applications specify consistent_read when strong consistency is required. DynamoDB uses conditional updates to implement optimistic concurrency control.
require 'aws-sdk-dynamodb'
class DynamoStore
def initialize
@client = Aws::DynamoDB::Client.new(region: 'us-east-1')
@table = 'user_data'
end
def get_item_strongly_consistent(key)
@client.get_item(
table_name: @table,
key: {'user_id' => key},
consistent_read: true # Strong consistency
).item
end
def get_item_eventually_consistent(key)
@client.get_item(
table_name: @table,
key: {'user_id' => key},
consistent_read: false # Default eventual consistency
).item
end
def conditional_update(key, expected_version, new_data)
@client.update_item(
table_name: @table,
key: {'user_id' => key},
update_expression: 'SET #data = :data, #version = :version',
expression_attribute_names: {
'#data' => 'data',
'#version' => 'version'
},
expression_attribute_values: {
':data' => new_data,
':version' => expected_version + 1,
':expected' => expected_version
},
condition_expression: '#version = :expected'
)
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
:conflict
end
end
Implementing Session Guarantees in Ruby applications requires tracking state on the client side. Monotonic reads need version tracking to reject stale responses. Read-your-writes requires routing reads to replicas that have received the client's writes.
class MonotonicReadClient
def initialize(replicas)
@replicas = replicas
@last_version = 0
end
def read(key)
# Try replicas until finding one with sufficient version
@replicas.shuffle.each do |replica|
result = replica.read(key)
if result[:version] >= @last_version
@last_version = result[:version]
return result[:value]
end
end
raise StaleReadError
end
end
Performance Considerations
Consistency models directly impact system performance, latency, and throughput characteristics.
Strong Consistency Costs manifest primarily as increased latency and reduced availability. Each write operation must propagate to multiple replicas synchronously before acknowledging success. This serialization point becomes a bottleneck. Network latency between replicas adds directly to operation latency. Geographic distribution amplifies this effect—synchronous replication across continents adds 100-300ms per operation.
Linearizability requires coordination that prevents concurrent operations from proceeding independently. This serialization limits throughput to the rate at which a single coordination point can process operations. Distributed consensus protocols like Raft require at least one round trip to a majority of nodes for each operation.
# Performance comparison: local vs distributed
require 'benchmark'
# Local strong consistency (single PostgreSQL server)
local_time = Benchmark.measure do
1000.times do |i|
User.transaction { User.create(name: "user_#{i}") }
end
end
# Distributed strong consistency (multi-region replicas)
distributed_time = Benchmark.measure do
1000.times do |i|
User.transaction(isolation: :serializable) do
User.create(name: "user_#{i}")
# Waits for replication to all regions
end
end
end
# local_time.real: ~2.5 seconds
# distributed_time.real: ~150 seconds (60x slower)
Eventual Consistency Performance enables higher throughput and lower latency by removing synchronization points. Writes complete after updating only the local replica. Asynchronous replication propagates updates in the background without blocking the client. This allows operations to proceed at local speeds regardless of replica count or geographic distribution.
The cost of eventual consistency appears as increased complexity rather than latency. Applications must handle conflicts, stale reads, and temporary inconsistency. Conflict resolution overhead occurs when merges require application logic. Large numbers of concurrent updates to the same object increase conflict frequency.
Read Optimization differs significantly across consistency models. Eventual consistency allows reads from any replica, enabling geographic read optimization by directing clients to nearby replicas. Strong consistency often requires reading from the primary or a quorum of replicas, preventing geographic optimization.
class GeographicallyOptimizedCache
def initialize(local_replicas, remote_replicas)
@local = local_replicas
@remote = remote_replicas
end
def get(key, strong_consistency: false)
if strong_consistency
# Must read from quorum - includes remote replicas
read_quorum(key, @local + @remote) # 150ms avg
else
# Can read from local replica only
@local.sample.get(key) # 2ms avg
end
end
end
Write Amplification occurs when replication multiplies the cost of each write. Strong consistency requires synchronously writing to multiple replicas. A write to a system with 5 replicas performs 5x the I/O of a single-node write. Network bandwidth consumption scales similarly. Quorum systems allow tuning this trade-off by varying quorum size.
Conflict Resolution Overhead impacts eventually consistent systems. When concurrent updates create conflicts, merging states adds computation cost. Simple strategies like last-write-wins have minimal overhead. Custom merge functions for complex data structures increase CPU usage. CRDTs trade merge computation for coordination elimination.
Caching Effectiveness varies with consistency guarantees. Strong consistency invalidates caches on every write, reducing cache hit rates. Eventual consistency allows longer cache TTLs since stale data is acceptable. Applications must balance staleness tolerance against cache performance.
class ConsistencyAwareCache
def initialize(consistency_model:)
@cache = {}
@ttl = consistency_model == :strong ? 0 : 60
end
def get(key)
cached = @cache[key]
if cached && Time.now - cached[:timestamp] < @ttl
cached[:value] # Cache hit
else
value = fetch_from_source(key)
@cache[key] = {value: value, timestamp: Time.now}
value # Cache miss
end
end
end
Transaction Abort Rates significantly impact performance in systems using optimistic concurrency control. High contention on hot keys causes frequent transaction aborts and retries. Each retry multiplies the effective cost of operations. Serializable isolation in PostgreSQL shows this effect clearly under contention.
Network Partition Handling affects performance during failures. Strong consistency systems become unavailable when partitions prevent reaching a quorum. Operations block until partition heals. Eventual consistency systems remain available during partitions but accumulate divergence requiring later reconciliation.
Practical Examples
Real-world scenarios demonstrate consistency model selection and implementation.
Social Media News Feed tolerates eventual consistency. Posts propagating to follower feeds can lag by seconds without harming user experience. Users accept that like counts and comments may briefly differ across views. The scale benefits of eventual consistency—handling millions of concurrent updates—outweigh consistency costs.
class NewsFeedService
def initialize
@redis = Redis.new
@fanout_queue = Sidekiq::Queue.new('feed_fanout')
end
def post_update(user_id, content)
# Write to user's timeline immediately
post_id = SecureRandom.uuid
post = {id: post_id, user_id: user_id, content: content}
@redis.zadd("timeline:#{user_id}", Time.now.to_i, post.to_json)
# Asynchronously fanout to followers (eventual consistency)
FanoutWorker.perform_async(post_id, user_id)
post_id
end
def get_feed(user_id, count: 50)
# Read from local cache - may see slightly stale data
posts = @redis.zrevrange("feed:#{user_id}", 0, count - 1)
posts.map { |p| JSON.parse(p) }
end
end
class FanoutWorker
include Sidekiq::Worker
def perform(post_id, author_id)
post = fetch_post(post_id)
followers = fetch_followers(author_id)
# Write to each follower's feed asynchronously
followers.each do |follower_id|
Redis.current.zadd(
"feed:#{follower_id}",
post[:timestamp],
post.to_json
)
end
end
end
E-commerce Inventory requires strong consistency to prevent overselling. When stock quantity reaches zero, the system must prevent further purchases across all concurrent sessions. Eventual consistency would allow race conditions where multiple customers purchase the last item.
class InventoryManager
def initialize
@db = ActiveRecord::Base.connection
end
def purchase_item(item_id, quantity)
# Use serializable transaction for strong consistency
ActiveRecord::Base.transaction(isolation: :serializable) do
item = Item.lock.find(item_id)
if item.stock >= quantity
item.update!(stock: item.stock - quantity)
create_order(item_id, quantity)
:success
else
:insufficient_stock
end
end
rescue ActiveRecord::SerializationFailure
# Retry on concurrent modification
retry
end
private
def create_order(item_id, quantity)
Order.create!(
item_id: item_id,
quantity: quantity,
timestamp: Time.now
)
end
end
Distributed Session Storage balances consistency and availability using session-specific guarantees. Read-your-writes ensures users see their own actions immediately. Other sessions can tolerate brief staleness for session data like preferences and recently viewed items.
class SessionStore
def initialize(redis_cluster)
@redis = redis_cluster
@local_cache = {}
end
def write(session_id, key, value)
# Write to primary for read-your-writes consistency
@redis.set("session:#{session_id}:#{key}", value.to_json)
# Update local cache for immediate read
@local_cache["#{session_id}:#{key}"] = value
# Asynchronously replicate to other regions
ReplicationWorker.perform_async(session_id, key, value)
end
def read(session_id, key, strict: false)
cache_key = "#{session_id}:#{key}"
# Check local cache first (read-your-writes)
return @local_cache[cache_key] if @local_cache.key?(cache_key)
if strict
# Strong consistency - read from primary
json = @redis.get("session:#{session_id}:#{key}")
else
# Eventual consistency - read from any replica
json = @redis.get("session:#{session_id}:#{key}", replicate: false)
end
json ? JSON.parse(json) : nil
end
end
Real-Time Collaboration uses causal consistency to preserve operation ordering while allowing concurrent edits. Operational transformation or CRDTs merge concurrent changes without coordination. Users see their own changes immediately while other users' changes arrive asynchronously.
class CollaborativeDocument
def initialize(document_id)
@document_id = document_id
@vector_clock = VectorClock.new(node_id)
@pending_ops = []
end
def insert_text(position, text)
# Create operation with causal context
@vector_clock.increment
operation = {
type: :insert,
position: position,
text: text,
clock: @vector_clock.dup
}
# Apply locally immediately
apply_operation(operation)
# Broadcast to other clients asynchronously
broadcast_operation(operation)
end
def receive_remote_operation(operation)
# Check causal dependencies
if satisfies_dependencies?(operation)
apply_operation(operation)
@vector_clock.merge(operation[:clock])
else
# Buffer until dependencies satisfied
@pending_ops << operation
end
# Process any buffered operations now satisfied
process_pending_operations
end
private
def satisfies_dependencies?(operation)
operation[:clock].happens_before?(@vector_clock) ||
operation[:clock].concurrent?(@vector_clock)
end
end
Metrics Collection System uses eventual consistency for high-throughput ingestion. Individual data points can arrive out of order or with slight delays. Aggregated metrics converge to correct values as all data arrives. Accepting eventual consistency enables horizontal scaling of ingestion nodes.
class MetricsCollector
def initialize
@cassandra = Cassandra.cluster.connect('metrics')
end
def record_metric(metric_name, value, timestamp = Time.now)
# Write with eventual consistency for maximum throughput
@cassandra.execute_async(
"INSERT INTO metrics (name, timestamp, value) VALUES (?, ?, ?)",
arguments: [metric_name, timestamp, value],
consistency: :one # Single replica for speed
)
end
def get_aggregated_metric(metric_name, window_minutes)
# Read aggregates with quorum for reasonable accuracy
rows = @cassandra.execute(
"SELECT sum(value) as total FROM metrics
WHERE name = ? AND timestamp > ?",
arguments: [metric_name, window_minutes.minutes.ago],
consistency: :quorum
)
rows.first['total']
end
end
Reference
Consistency Model Comparison
| Model | Guarantees | Use Cases | Availability | Performance |
|---|---|---|---|---|
| Linearizability | Operations appear atomic in real-time order | Financial transactions, seat reservations | Low during partitions | High latency, low throughput |
| Sequential Consistency | Operations appear in some total order | Shared memory, distributed locks | Low during partitions | Medium latency |
| Causal Consistency | Causally related operations ordered | Collaborative editing, social feeds | Medium | Medium latency |
| Eventual Consistency | All replicas converge eventually | Content distribution, caching | High | Low latency, high throughput |
| Read-Your-Writes | Clients see own writes | User sessions, preferences | Medium | Medium latency |
| Monotonic Reads | No reading old values after new | News feeds, notifications | Medium | Medium latency |
| Monotonic Writes | Writes applied in order | Logging, append-only data | Medium | Medium latency |
Database Consistency Guarantees
| Database | Default Model | Available Options | Configuration |
|---|---|---|---|
| PostgreSQL | Serializable | Read Uncommitted, Read Committed, Repeatable Read, Serializable | Transaction isolation level |
| MySQL | Repeatable Read | Read Uncommitted, Read Committed, Repeatable Read, Serializable | Transaction isolation level |
| MongoDB | Eventual | Eventual, Linearizable | Read concern and write concern |
| Cassandra | Eventual | ONE, QUORUM, ALL, LOCAL_QUORUM | Consistency level per operation |
| Redis | Linearizable (single instance) | Eventual (with replication) | Replication configuration |
| DynamoDB | Eventual | Eventual, Strong | consistent_read parameter |
| Elasticsearch | Eventual | Eventual, Quorum | Write consistency parameter |
Ruby Client Consistency Configuration
| Client | Consistency Parameter | Values | Example |
|---|---|---|---|
| ActiveRecord | isolation | read_uncommitted, read_committed, repeatable_read, serializable | transaction(isolation: :serializable) |
| cassandra-driver | consistency | one, quorum, all, local_quorum | execute(query, consistency: :quorum) |
| redis-rb | wait | replicas, timeout | wait(replicas: 2, timeout: 1000) |
| aws-sdk-dynamodb | consistent_read | true, false | get_item(consistent_read: true) |
| mongo | read_concern | local, majority, linearizable | find(read: {mode: :majority}) |
CAP Theorem Trade-offs
| Choice | Optimizes For | Sacrifices | Example Systems |
|---|---|---|---|
| CP (Consistency + Partition Tolerance) | Data correctness during partitions | Availability when partitioned | HBase, MongoDB (strong), Redis (Sentinel) |
| AP (Availability + Partition Tolerance) | Service availability during partitions | Consistency during partitions | Cassandra, DynamoDB, Riak |
| CA (Consistency + Availability) | Consistency and availability | Partition tolerance (single site only) | PostgreSQL (single instance), MySQL (single instance) |
Quorum Configuration Formulas
| Configuration | Write Quorum (W) | Read Quorum (R) | Consistency Guarantee | Use Case |
|---|---|---|---|---|
| Strong Consistency | Any | Any where W + R > N | Latest write visible | Financial transactions |
| Read Optimized | N | 1 | Eventual | Read-heavy workloads |
| Write Optimized | 1 | N | Eventual | Write-heavy logging |
| Balanced | (N/2) + 1 | (N/2) + 1 | Strong | General purpose |
| Sloppy Quorum | W < (N/2) + 1 | R < (N/2) + 1 | Eventual | High availability priority |
Common Consistency Patterns
| Pattern | Description | Implementation | Trade-off |
|---|---|---|---|
| Primary-Backup | Single primary handles writes, backups replicate | Synchronous or asynchronous replication | Simplicity vs consistency strength |
| Multi-Master | Multiple nodes accept writes | Conflict detection and resolution | Availability vs complexity |
| Quorum Replication | Majority agreement required | Overlapping read/write sets | Consistency vs latency |
| CRDT | Conflict-free merge operations | Commutative merge functions | No coordination vs limited operations |
| Consensus | Distributed agreement protocol | Raft, Paxos, ZAB | Strong consistency vs latency |
| Vector Clocks | Track causality with version vectors | Increment and merge clocks | Causal ordering vs space overhead |
Version Vector Operations
| Operation | Description | Complexity | Use Case |
|---|---|---|---|
| Increment | Advance local clock | O(1) | Record local event |
| Merge | Take maximum of each component | O(N) where N is node count | Incorporate remote state |
| Compare | Determine happens-before relationship | O(N) | Detect conflicts |
| Prune | Remove inactive nodes | O(N) | Control space growth |
Performance Characteristics
| Operation | Strong Consistency | Causal Consistency | Eventual Consistency |
|---|---|---|---|
| Write Latency | Network RTT × replicas | Local write + async replication | Local write only |
| Read Latency | Network RTT to quorum | Local read | Local read |
| Throughput | Limited by coordination | Medium | High |
| Conflict Frequency | Zero (prevented) | Low (causally ordered) | High (concurrent updates) |
| Network Overhead | High (synchronous) | Medium | Low (asynchronous) |
| Availability During Partition | Low | Medium | High |