pattern approximation
**Intentionally store approximate values to reduce write load when exact real-time counts are not required.** High-frequency counters (page views, trending scores, social media counters) that increment by +1 per event can create expensive per-event writes. The approximation pattern batches these increments, trading staleness for dramatically lower write volume.
Overview
Approximation Pattern
Intentionally store approximate values to reduce write load when exact real-time counts are not required. High-frequency counters (page views, trending scores, social media counters) that increment by +1 per event can create expensive per-event writes. The approximation pattern batches these increments, trading staleness for dramatically lower write volume.
Incorrect (write to database on every event):
// Page view counter - writes to MongoDB on every single view
function recordPageView(articleId) {
db.articles.updateOne(
{ _id: articleId },
{
$inc: { viewCount: 1 },
$set: { lastViewedAt: new Date() }
}
)
}
// 1M page views/day = 1M database writes/day
// High write load for a counter that doesn't need real-time accuracy
Correct (batch writes with threshold):
The document stores an approximate count plus a sync timestamp. The application tracks counts in local memory (e.g. a Map keyed by article ID) and writes to the database only when the local counter crosses a threshold (e.g. every 100 views). At threshold=100 this yields ~100× fewer database writes.
The document includes viewCount (approximate — may lag by up to one threshold) and lastSyncedAt. When the local counter reaches the threshold, the application issues a single $inc by the threshold amount and updates lastSyncedAt. Unsynced local increments are lost on application restart.
Tradeoffs:
| Concern | Impact |
|---|---|
| Write reduction | ~100x fewer DB writes (at threshold=100) |
| Staleness | Up to threshold events behind |
| Accuracy | Approximate — never exact real-time |
| Crash safety | Unsynced local increments lost on restart |
Difference from Computed Pattern:
- Computed Pattern: pre-computes expensive aggregations, stores exact results
- Approximation Pattern: intentionally stores inexact values to reduce write frequency
Use Approximation when staleness is acceptable. Use Computed when exact values are needed but recalculating each time is too expensive.
When NOT to use this pattern:
- Financial amounts, inventory counts: Exact values required — approximation is unacceptable.
- Low-frequency updates: If counter changes rarely, approximation adds complexity without benefit.
- Regulatory/audit requirements: When exact counts are mandated.
Verify with
// Check write frequency on counter fields
db.setProfilingLevel(1, { slowms: 0 })
db.system.profile.find({
"command.update": "articles",
"command.updates.u.$inc.viewCount": { $exists: true }
}).count()
// High count relative to read count suggests approximation would help
// Compare counter staleness
db.articles.aggregate([
{ $project: {
title: 1,
viewCount: 1,
lastSyncedAt: 1,
staleness: { $subtract: ["$$NOW", "$lastSyncedAt"] }
}},
{ $sort: { staleness: -1 } },
{ $limit: 10 }
])
// Verify staleness is within acceptable bounds for your use case
Reference: Use the Approximation Pattern