Loading content...
ACID and BASE represent two fundamentally different philosophies for managing data in computer systems. ACID prioritizes correctness, giving you absolute guarantees about data integrity at the cost of availability and scalability. BASE prioritizes availability, keeping your system running even during failures at the cost of temporary inconsistency.
Neither is universally 'better.' They're different tools for different problems. A bank processing wire transfers has different needs than a social media platform counting likes. A healthcare system storing patient records has different constraints than a gaming leaderboard.
The art of system design lies in understanding these trade-offs deeply enough to make the right choice for each part of your system—and increasingly, mixing both approaches within the same architecture.
By the end of this page, you will understand the fundamental differences between ACID and BASE, the technical and business factors that drive the choice, when each model is appropriate, hybrid architectures that combine both approaches, and a decision framework for choosing consistency models in your own systems.
Let's begin with a comprehensive comparison of the two models across multiple dimensions:
| Dimension | ACID | BASE |
|---|---|---|
| Philosophy | Pessimistic: Assume conflicts, prevent them | Optimistic: Assume success, handle conflicts later |
| Consistency | Strong: All readers see same data immediately | Eventual: Readers may temporarily see different data |
| Availability | May sacrifice for consistency | Prioritized over consistency |
| Durability | Guaranteed: Committed data survives failures | Varies: May be relaxed for performance |
| Isolation | Transactions are independent | Operations may interleave; conflicts resolved later |
| Scaling | Vertical focus; horizontal is complex | Horizontal native; designed for distribution |
| Latency | Higher: Coordination required | Lower: Local operations, async replication |
| Complexity | Simpler programming model | More complex application logic required |
| Failure mode | Reject operations to preserve consistency | Accept operations, reconcile later |
The ACID vs BASE choice isn't binary. Most real systems use a combination—ACID for critical financial transactions, BASE for user activity feeds. Modern databases like CockroachDB and Spanner even offer ACID guarantees across distributed nodes, though with availability and latency trade-offs.
The ACID vs BASE trade-off is fundamentally tied to the CAP theorem. Understanding this connection helps clarify when each model is appropriate.
CAP Theorem Refresher:
In a distributed system, you can only guarantee two of three properties:
Since network partitions are inevitable in distributed systems, the real choice is:
| Choice | Behavior During Partition | Normal Operation | Example Systems |
|---|---|---|---|
| CP (ACID-aligned) | Reject writes to maintain consistency | Strong consistency, full availability | PostgreSQL, MySQL, Spanner, etcd |
| AP (BASE-aligned) | Accept writes, resolve conflicts later | Eventual consistency, full availability | Cassandra, DynamoDB, CouchDB |
The Partition Question:
The key question is: What happens when nodes can't communicate?
ACID/CP Answer: Stop accepting writes to the partitioned nodes. Users might see errors or timeouts, but data integrity is preserved. When the partition heals, no reconciliation is needed—data was never diverged.
BASE/AP Answer: Continue accepting writes on all available nodes. Users continue working, but different users might see different data. When the partition heals, the system must reconcile conflicting writes.
Neither answer is wrong—they're appropriate for different contexts.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
// Simulating CP vs AP behavior during network partition interface PartitionScenario { partitionedNodes: string[]; reachableNodes: string[];} class CPDatabase { // Consistency-prioritizing (ACID-style) async write(key: string, value: any, scenario: PartitionScenario) { const requiredNodes = this.getQuorum(); // Majority needed const availableNodes = scenario.reachableNodes; if (availableNodes.length < requiredNodes) { // Cannot guarantee consistency - reject write throw new UnavailableError( 'Cannot write: insufficient nodes for quorum. ' + 'Rejecting to preserve ACID consistency.' ); } // Proceed with synchronous replication to quorum await this.replicateToQuorum(key, value, availableNodes); return { success: true, consistency: 'strong' }; }} class APDatabase { // Availability-prioritizing (BASE-style) async write(key: string, value: any, scenario: PartitionScenario) { const availableNodes = scenario.reachableNodes; if (availableNodes.length === 0) { throw new Error('No nodes available at all'); } // Accept write on any available node const timestamp = Date.now(); await this.writeToAvailable(key, value, timestamp, availableNodes); // Queue for async replication when partition heals this.queueForReplication(key, value, timestamp, scenario.partitionedNodes); return { success: true, consistency: 'eventual', warning: 'Write accepted but not yet replicated to all nodes' }; } async handlePartitionHeal(scenario: PartitionScenario) { // Resolve conflicts using last-writer-wins or merge for (const node of scenario.partitionedNodes) { const conflicts = await this.detectConflicts(node); for (const conflict of conflicts) { await this.resolveConflict(conflict); } } }} // Example during partition:// CP: User sees "Service temporarily unavailable, please retry"// AP: User's write succeeds, might see stale data from other writesLet's dive deeper into the technical implications of choosing ACID vs BASE. Understanding these helps you anticipate challenges and design appropriate solutions.
BASE's apparent simplicity (just add more nodes!) hides significant application-level complexity. Every developer must understand eventual consistency, handle conflicts, and design for idempotency. The database is simpler, but the system as a whole may not be. Consider your team's expertise when choosing.
| Complexity Type | ACID | BASE |
|---|---|---|
| Transaction management | Database handles | Application handles (or avoided) |
| Conflict resolution | Database prevents via locks | Application resolves after the fact |
| Failure recovery | Automatic rollback | Compensating transactions |
| Consistency verification | Guaranteed by database | Application must check or accept risk |
| Distributed operations | 2PC/Paxos (complex) | Async replication (simpler infra) |
| Testing | Standard transaction tests | Must test for eventual consistency edge cases |
Technical trade-offs matter, but ultimately the ACID vs BASE decision is driven by business requirements. Let's map common business needs to appropriate consistency models.
| Business Requirement | Recommended Model | Rationale |
|---|---|---|
| Financial transactions | ACID | Legal/regulatory requirements; double-spending prevention |
| Inventory management | ACID (or hybrid) | Overselling is costly; exact counts matter |
| User authentication | ACID | Security requires immediate consistency |
| Social media feeds | BASE | Slight delays acceptable; availability critical |
| Real-time analytics | BASE | Approximate counts fine; speed matters |
| Shopping carts | BASE | Availability critical; can reconcile later |
| Medical records | ACID | Regulatory requirements; patient safety |
| Gaming leaderboards | BASE | Eventual accuracy fine; low latency critical |
| Booking/reservations | ACID | Double-booking unacceptable |
| Content delivery | BASE | Slightly stale content acceptable; availability critical |
The Key Questions:
When deciding between ACID and BASE, ask these questions:
What's the cost of inconsistency?
What's the cost of unavailability?
Can you design for eventual consistency?
What are your scale requirements?
Amazon famously uses BASE for shopping carts—they'd rather let you add items during an outage and reconcile later than show you an error page. But they use ACID for payment processing—charging you twice is unacceptable. This 'split personality' approach is common in large systems: BASE for user-facing features, ACID for financial core.
Most production systems don't choose exclusively ACID or BASE—they combine both approaches strategically. Here are common hybrid patterns:
Pattern Deep Dive: CQRS (Command Query Responsibility Segregation)
CQRS separates the write model (commands) from the read model (queries). This naturally maps to hybrid consistency:
This gives you:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
// CQRS: ACID writes + BASE reads // ===== WRITE SIDE (ACID) =====class OrderService { private acidDatabase: PostgresDB; private eventPublisher: EventPublisher; async createOrder(command: CreateOrderCommand): Promise<Order> { // ACID transaction for order creation return await this.acidDatabase.transaction(async (tx) => { // Validate inventory (ACID read) const inventory = await tx.query( 'SELECT quantity FROM inventory WHERE product_id = $1 FOR UPDATE', [command.productId] ); if (inventory.quantity < command.quantity) { throw new InsufficientInventoryError(); } // Create order (ACID write) const order = await tx.query( 'INSERT INTO orders (product_id, quantity, user_id, status) VALUES ($1, $2, $3, $4) RETURNING *', [command.productId, command.quantity, command.userId, 'PENDING'] ); // Reserve inventory (ACID write) await tx.query( 'UPDATE inventory SET quantity = quantity - $1, reserved = reserved + $1 WHERE product_id = $2', [command.quantity, command.productId] ); // Publish event (will be consumed by read side) await this.eventPublisher.publish('OrderCreated', { orderId: order.id, productId: command.productId, quantity: command.quantity, userId: command.userId, timestamp: new Date() }); return order; }); }} // ===== READ SIDE (BASE) =====class OrderReadModelUpdater { private readDatabase: CassandraDB; // Optimized for reads // Eventually consistent - processes events async async handleOrderCreated(event: OrderCreatedEvent) { // Update denormalized read models // User's order history (eventually consistent) await this.readDatabase.execute( 'INSERT INTO user_orders (user_id, order_id, product_name, quantity, created_at) VALUES (?, ?, ?, ?, ?)', [event.userId, event.orderId, await this.getProductName(event.productId), event.quantity, event.timestamp] ); // Product order count (eventually consistent) await this.readDatabase.execute( 'UPDATE product_stats SET order_count = order_count + 1 WHERE product_id = ?', [event.productId] ); // Real-time dashboard (eventually consistent) await this.readDatabase.execute( 'INSERT INTO recent_orders (hour, order_id, total) VALUES (?, ?, ?) USING TTL 86400', [this.getHourBucket(), event.orderId, event.quantity] ); }} class OrderQueryService { private readDatabase: CassandraDB; // Fast reads from eventually consistent store async getUserOrderHistory(userId: string): Promise<OrderSummary[]> { // Reads from denormalized, eventually consistent read model // Super fast, might be slightly behind writes return await this.readDatabase.execute( 'SELECT * FROM user_orders WHERE user_id = ? LIMIT 100', [userId] ); }}The key to hybrid architectures is clear consistency boundaries. Define which operations need ACID (usually writes, critical reads) and which can be BASE (usually reads, analytics, caches). Document these boundaries; they're part of your system's contract.
Let's synthesize everything into a practical decision framework you can use when designing systems.
Step 1: Classify Your Operations
For each operation in your system, categorize it:
| Category | Description | Examples |
|---|---|---|
| Critical Write | Must never be lost or duplicated | Payments, reservations, account creation |
| Critical Read | Must be current and accurate | Balance checks, inventory for purchase |
| Important Write | Should succeed, minor inconsistency OK | Profile updates, preferences |
| Important Read | Should be recent, can be slightly stale | User dashboard, order history |
| Best-Effort | Nice to have, can be delayed/lost | Analytics, recommendations, activity feeds |
Step 2: Apply the Decision Matrix
| Operation Category | Consistency Model | Implementation |
|---|---|---|
| Critical Write | ACID | Transactional database, synchronous confirmation |
| Critical Read | ACID or Strong Read | Read from leader, or read-your-writes |
| Important Write | ACID or Hybrid | ACID preferred, async replication acceptable |
| Important Read | BASE acceptable | Read replicas, eventual consistency OK |
| Best-Effort | BASE | Async processing, caching, eventual consistency |
Step 3: Consider Constraints
Before finalizing, check constraints:
✅ Scale Requirements
100K writes/second: BASE patterns become more attractive
✅ Team Expertise
✅ Regulatory Requirements
✅ Existing Infrastructure
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
// Example: E-commerce system consistency decisions interface SystemDesignDecisions { operation: string; category: 'critical-write' | 'critical-read' | 'important-write' | 'important-read' | 'best-effort'; consistency: 'ACID' | 'BASE' | 'hybrid'; implementation: string;} const ecommerceDecisions: SystemDesignDecisions[] = [ // Critical { operation: 'Process payment', category: 'critical-write', consistency: 'ACID', implementation: 'PostgreSQL with 2PC to payment processor' }, { operation: 'Check inventory before purchase', category: 'critical-read', consistency: 'ACID', implementation: 'Read from PostgreSQL with SELECT FOR UPDATE' }, { operation: 'Create order', category: 'critical-write', consistency: 'ACID', implementation: 'PostgreSQL transaction: order + inventory + payment' }, // Important { operation: 'Update user profile', category: 'important-write', consistency: 'hybrid', implementation: 'ACID write to PostgreSQL, async to read replicas' }, { operation: 'View order history', category: 'important-read', consistency: 'BASE', implementation: 'Read from eventually consistent read model' }, // Best-effort { operation: 'Product recommendations', category: 'best-effort', consistency: 'BASE', implementation: 'Pre-computed, cached, updated hourly' }, { operation: 'Recently viewed products', category: 'best-effort', consistency: 'BASE', implementation: 'Redis with TTL, async updates' }, { operation: 'Real-time analytics', category: 'best-effort', consistency: 'BASE', implementation: 'Event streaming to Kafka, processed by ClickHouse' }]; // Result: Core commerce in PostgreSQL (ACID)// Read scaling via replicas and Redis (BASE)// Analytics via streaming and OLAP (BASE)We've explored the trade-offs between ACID and BASE consistency models in depth. Let's consolidate the key takeaways:
What's Next:
Now that we understand the trade-offs between ACID and BASE, we'll conclude this module by exploring When BASE is Acceptable. This final page provides concrete scenarios, heuristics, and real-world examples to help you confidently choose BASE when it's appropriate—and recognize when it's not.
You now understand the fundamental trade-offs between ACID and BASE consistency models. Neither is universally better—they're tools for different problems. The art of system design lies in choosing the right consistency model for each operation based on business requirements, technical constraints, and team capabilities. Next, we'll explore specific scenarios where BASE is the right choice.