This is a sample post to check how content renders. It uses most of the formatting a real post would: headings, code blocks, callouts, tables, and lists.
Table of contents
Open Table of contents
The problem
Every time someone opens a post, we want to count a view. Writing to SQL on every request works, but it’s slow and puts pressure on the database when traffic spikes.
The idea is simple:
- Increment a counter in Redis (fast, in memory).
- Every ~60 seconds, flush the pending counts to SQL.
- SQL stays the source of truth.
Redis here is a buffer, not the database. If Redis loses data, we lose at most one minute of views.
Counting a view
In .NET, incrementing a counter with StackExchange.Redis is one line:
public class ViewCounter(IConnectionMultiplexer redis)
{
public Task<long> IncrementAsync(string slug)
{
var db = redis.GetDatabase();
return db.StringIncrementAsync($"views:{slug}");
}
}
Flushing to SQL
The flush must be safe to re-run. Here’s the change that makes it so:
var pending = (long)await db.StringGetAsync(key);
await AddViewsToSqlAsync(slug, pending);
await db.StringSetAsync(key, 0);
await db.StringDecrementAsync(key, pending);
Setting the key to 0 loses any views that arrived between the read and the write. Subtracting only what was flushed keeps them.
Comparing the options
| Approach | Writes per 1,000 views | Risk if cache dies |
|---|---|---|
| SQL on every request | 1,000 | None |
| Redis + flush every 60s | ~1 per post per minute | Up to 60s of views |
Wrapping up
That’s the whole pattern: increment fast, flush safely, and keep SQL as the truth. Part 2 would cover likes, which go straight to SQL.