Loading content...
Understanding BASE conceptually is one thing. Having the confidence to use it in production—knowing exactly when eventual consistency is safe—is another entirely. Engineers often default to ACID 'just to be safe,' leaving performance and scalability on the table. Others embrace BASE too eagerly, creating subtle bugs that surface only under specific conditions.
This page is your practical guide to BASE adoption. By the end, you'll have clear heuristics for identifying BASE-appropriate scenarios, real-world examples from companies operating at massive scale, and the confidence to make informed decisions about consistency in your own systems.
By the end of this page, you will understand the domain characteristics that make BASE acceptable, concrete use cases where BASE excels, scenarios where BASE is dangerous and should be avoided, risk assessment frameworks for borderline cases, and real-world case studies from Amazon, Facebook, Netflix, and others.
Certain domains and problem types are naturally suited to eventual consistency. Recognizing these characteristics helps you identify BASE opportunities without deep analysis.
| Characteristic | BASE-Friendly | ACID Required |
|---|---|---|
| Value direction | Monotonic (only increases/decreases) | Arbitrary (can go up or down) |
| Update pattern | Set/replace | Increment/modify based on current |
| Conflict frequency | Rare (user-partitioned) | Common (shared resources) |
| User perception | Slight delay acceptable | Immediate visibility required |
| Error recovery | Simple retry | Complex rollback needed |
| Business impact | Cosmetic/UX | Financial/legal/safety |
A quick heuristic: 'If I show a user data that's 5 seconds old, what's the worst that happens?' If the answer is 'minor confusion' or 'they refresh,' BASE is probably fine. If the answer is 'they see wrong balance' or 'double-booking,' ACID is needed.
Let's examine specific use cases across different domains where BASE is not just acceptable but optimal.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
// Like counter using BASE consistency// Optimized for availability and speed, accepts eventual accuracy class LikeService { private redis: Redis; // Primary storage (fast, eventual) private postgres: PostgresDB; // Source of truth (eventual sync) private eventQueue: EventQueue; // For durability async likePost(userId: string, postId: string): Promise<LikeResult> { const likeId = `like:${postId}:${userId}`; // Step 1: Check if already liked (fast, possibly stale) const alreadyLiked = await this.redis.sismember( `post:${postId}:likers`, userId ); if (alreadyLiked) { // Idempotent - return success even if duplicate return { success: true, action: 'already_liked' }; } // Step 2: Optimistic update to Redis (immediate visibility) await Promise.all([ this.redis.sadd(`post:${postId}:likers`, userId), this.redis.incr(`post:${postId}:like_count`) ]); // Step 3: Queue durable write (eventual persistence) await this.eventQueue.publish('LikeCreated', { likeId, userId, postId, timestamp: Date.now() }); // Return immediately - don't wait for DB // Like count might be slightly off across replicas, but: // - User sees their own like immediately (read-your-writes via same Redis) // - Other users see eventually consistent count (acceptable) // - Durability is guaranteed via event queue return { success: true, action: 'liked', note: 'Count is eventually consistent across regions' }; } async getLikeCount(postId: string): Promise<number> { // Fast read from Redis - might be slightly behind const count = await this.redis.get(`post:${postId}:like_count`); return parseInt(count || '0'); }} // Background worker: Event → PostgreSQL (source of truth)class LikeEventProcessor { async processLikeCreated(event: LikeCreatedEvent) { // Idempotent upsert to PostgreSQL await this.postgres.query(` INSERT INTO likes (like_id, user_id, post_id, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (like_id) DO NOTHING `, [event.likeId, event.userId, event.postId, event.timestamp]); // Update materialized count (for consistency with Redis) await this.postgres.query(` INSERT INTO post_stats (post_id, like_count) VALUES ($1, 1) ON CONFLICT (post_id) DO UPDATE SET like_count = post_stats.like_count + 1 `, [event.postId]); }}Just as important as knowing when BASE is acceptable is knowing when it's dangerous. These scenarios require ACID consistency—eventual consistency will cause real problems.
A common mistake: using eventually consistent reads for inventory during checkout. 'We have 5 in stock' → user purchases → actually 0 in stock (other purchases not yet replicated). Result: Overselling, cancelled orders, angry customers. Always check inventory with strong consistency at the moment of purchase.
| Warning Sign | Why It's Dangerous | Solution |
|---|---|---|
| Value is money or credit | Incorrect balances = real loss | ACID for all money operations |
| Resource is exclusive | Multiple 'owners' = conflict | Distributed lock with ACID |
| Legal/compliance involved | Audit failures = penalties | ACID with audit trail |
| User safety affected | Wrong state = harm | ACID for safety-critical |
| Uniqueness required | Duplicates cause cascade failures | ACID with unique constraints |
| Conflicts are expensive | Compensation cost > availability benefit | ACID to prevent conflicts |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
// CORRECT: ACID for inventory at checkout// BASE was used for browsing (catalog), but checkout needs ACID class CheckoutService { private acidDatabase: PostgresDB; async checkout( cartItems: CartItem[], userId: string ): Promise<CheckoutResult> { // ACID transaction - all or nothing return await this.acidDatabase.transaction(async (tx) => { const reservations: InventoryReservation[] = []; for (const item of cartItems) { // Lock row for update - prevents concurrent modifications const inventory = await tx.query(` SELECT quantity_available, quantity_reserved FROM inventory WHERE product_id = $1 FOR UPDATE -- Critical: exclusive lock `, [item.productId]); if (inventory.quantity_available < item.quantity) { // Abort entire transaction throw new InsufficientInventoryError( `Only ${inventory.quantity_available} of ${item.productId} available` ); } // Reserve inventory atomically await tx.query(` UPDATE inventory SET quantity_available = quantity_available - $1, quantity_reserved = quantity_reserved + $1 WHERE product_id = $2 `, [item.quantity, item.productId]); reservations.push({ productId: item.productId, quantity: item.quantity }); } // Create order with reserved items const order = await tx.query(` INSERT INTO orders (user_id, status, items, created_at) VALUES ($1, 'PENDING', $2, NOW()) RETURNING id `, [userId, JSON.stringify(reservations)]); // All succeeded - commit transaction return { success: true, orderId: order.id, reservations }; }); // If anything fails, entire transaction rolls back // No partial reservations, no inconsistent state // This MUST be ACID - BASE would cause overselling }} // WRONG: This would cause oversellingclass BrokenCheckoutService { private eventuallyConsistentDB: CassandraDB; async brokenCheckout(cartItems: CartItem[]) { for (const item of cartItems) { // BAD: Eventually consistent read - might be stale! const inventory = await this.eventuallyConsistentDB.read( `inventory:${item.productId}` ); if (inventory.available >= item.quantity) { // BAD: No atomicity - race condition! await this.eventuallyConsistentDB.write( `inventory:${item.productId}`, { available: inventory.available - item.quantity } ); } } // Result: Multiple users can "see" available inventory // and all try to reserve it = overselling }}Many use cases fall in a gray area—not obviously BASE-friendly, not obviously requiring ACID. For these borderline cases, use a structured risk assessment.
The BASE Risk Assessment Checklist:
For each operation, answer these questions. Score 1 point for each 'Yes'. Higher scores favor ACID; lower scores favor BASE.
| Question | If Yes, Score | |
|---|---|---|
| 1 | Does inconsistency cause financial loss? | +3 |
| 2 | Does inconsistency cause legal/regulatory issues? | +3 |
| 3 | Does inconsistency affect user safety? | +3 |
| 4 | Does inconsistency cause permanent data corruption? | +2 |
| 5 | Does inconsistency require expensive compensation? | +2 |
| 6 | Is the operation non-idempotent? | +2 |
| 7 | Do users expect immediate visibility to others? | +1 |
| 8 | Is there high conflict potential (many writers)? | +1 |
| 9 | Does inconsistency affect trust/reputation? | +1 |
| 10 | Is compensation (rollback) complex? | +1 |
Interpreting Your Score:
| Score | Recommendation | Rationale |
|---|---|---|
| 0-3 | BASE is likely fine | Low risk, high availability benefit |
| 4-6 | Consider hybrid | Use ACID for write, BASE for read |
| 7-10 | Hybrid with caution | ACID for core, carefully designed BASE edges |
| 11+ | ACID required | Risk too high for eventual consistency |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
// Example risk assessments for borderline cases interface RiskAssessment { operation: string; scores: Record<string, number>; totalScore: number; recommendation: 'BASE' | 'HYBRID' | 'ACID'; rationale: string;} const assessments: RiskAssessment[] = [ { operation: "User profile bio update", scores: { financialLoss: 0, legalIssues: 0, userSafety: 0, dataCorruption: 0, expensiveCompensation: 0, nonIdempotent: 0, immediateVisibility: 1, // Users like seeing changes immediately highConflict: 0, // User owns their own profile trustImpact: 0, complexRollback: 0 }, totalScore: 1, recommendation: 'BASE', rationale: 'User-partitioned data, idempotent writes, low impact of delay' }, { operation: "Comment on post", scores: { financialLoss: 0, legalIssues: 0, userSafety: 0, dataCorruption: 0, expensiveCompensation: 0, nonIdempotent: 1, // Adding comment is not naturally idempotent immediateVisibility: 1, highConflict: 0, // Comments are append-only trustImpact: 0, complexRollback: 0 }, totalScore: 2, recommendation: 'BASE', rationale: 'With idempotency key, can be made safe. Visibility delay acceptable.' }, { operation: "Group membership change", scores: { financialLoss: 0, legalIssues: 1, // Access control has compliance implications userSafety: 0, dataCorruption: 0, expensiveCompensation: 1, nonIdempotent: 0, immediateVisibility: 1, // Security expects immediate effect highConflict: 1, // Admins might conflict on membership trustImpact: 1, // Security expectations complexRollback: 0 }, totalScore: 5, recommendation: 'HYBRID', rationale: 'Use ACID for membership write, cache for read. Invalidate on change.' }, { operation: "Subscription upgrade/downgrade", scores: { financialLoss: 3, // Wrong tier = wrong billing legalIssues: 2, // Billing compliance userSafety: 0, dataCorruption: 0, expensiveCompensation: 2, // Refund processing nonIdempotent: 2, // Upgrade is a state change immediateVisibility: 1, highConflict: 0, trustImpact: 1, complexRollback: 1 }, totalScore: 12, recommendation: 'ACID', rationale: 'Financial impact too high. Subscription state must be strongly consistent.' }];Let's examine how major companies have made BASE decisions for specific features.
Notice the pattern: These companies use BASE for user-facing features where availability and low latency matter most (carts, feeds, history, messages). They use ACID for business-critical operations where correctness is paramount (payments, subscriptions, account state). This hybrid approach gives the best of both worlds.
When you've decided BASE is appropriate, follow these guidelines to implement it correctly.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
// Template for implementing BASE-style operations interface BASEOperation<T> { // Unique key for idempotency idempotencyKey: string; // Timestamp for ordering/conflict resolution timestamp: number; // The actual data payload: T; // Consistency hints for clients consistency: { level: 'eventual' | 'read-your-writes' | 'monotonic'; maxStalenessMs?: number; };} class BASEServiceTemplate<T> { private primaryStore: DistributedStore; private idempotencyCache: IdempotencyCache; async write(operation: BASEOperation<T>): Promise<WriteResult> { // 1. Idempotency check const existing = await this.idempotencyCache.get(operation.idempotencyKey); if (existing) { console.log('Idempotent retry detected, returning cached result'); return existing.result; } // 2. Perform write with version const result = await this.primaryStore.write({ key: this.deriveKey(operation), value: operation.payload, timestamp: operation.timestamp, metadata: { idempotencyKey: operation.idempotencyKey, writtenAt: Date.now() } }); // 3. Cache result for idempotency (with TTL) await this.idempotencyCache.set( operation.idempotencyKey, { result, expiry: Date.now() + 24 * 60 * 60 * 1000 } ); // 4. Return with consistency metadata return { success: true, version: result.version, consistency: operation.consistency, propagationEstimate: this.estimatePropagationTime() }; } async read( key: string, options: ReadOptions = {} ): Promise<ReadResult<T>> { // 5. Read with freshness check const result = await this.primaryStore.read(key); if (!result) { return { found: false, consistency: { level: 'eventual' } }; } // 6. Check freshness for read-your-writes if (options.minVersion && result.version < options.minVersion) { // Data is stale relative to a write we know about if (options.waitForFreshness) { // Wait for propagation return await this.waitForVersion(key, options.minVersion, options.timeoutMs); } else { // Return with staleness warning return { found: true, value: result.value, version: result.version, stale: true, consistency: { level: 'eventual', staleness: Date.now() - result.timestamp } }; } } return { found: true, value: result.value, version: result.version, stale: false, consistency: { level: options.minVersion ? 'read-your-writes' : 'eventual' } }; } private estimatePropagationTime(): number { // Based on monitoring data return this.monitoringService.getP99PropagationTimeMs(); }}We've explored when BASE consistency is acceptable and how to implement it safely. Let's consolidate the key takeaways:
Module Complete:
Congratulations! You've completed the BASE Properties module. You now understand:
This knowledge is foundational for designing distributed systems that scale. In the next module, we'll explore Data Modeling Fundamentals—how to structure your data for both ACID and BASE systems.
You now have the knowledge and frameworks to confidently choose BASE consistency when appropriate—and recognize when it's not. This is a crucial skill for any engineer designing systems at scale. BASE isn't a compromise; it's a deliberate choice that enables availability, performance, and scalability for the right use cases.