Choosing the Right MySQL‑Redis Cache Consistency Strategy
This article explains why caching is essential for high‑traffic MySQL applications, outlines the consistency challenges of using Redis as a cache, and compares four practical synchronization solutions—TTL‑only, write‑through, message‑queue‑based, and binlog‑driven—highlighting their advantages, drawbacks, and selection guidelines.
Background
Database caching is a common pattern in modern applications. While caching improves read performance, keeping the cache consistent with the underlying MySQL data is a frequent interview topic. This summary presents four practical consistency strategies for a Redis‑MySQL cache pair and guidance on selecting the appropriate one.
What Is a Cache?
A cache stores the result of a slow‑access data source (e.g., MySQL) in a faster storage layer (e.g., Redis) for a limited period. The cache sits above the primary store in the storage hierarchy, acting as a temporary replica of hot data.
Why Use a Cache?
MySQL provides full ACID guarantees, but its durability and locking mechanisms limit throughput under high concurrency. According to the 80/20 rule, roughly 80 % of requests target 20 % of the data. Placing a read‑heavy, write‑light workload behind Redis reduces MySQL load, improves latency, and increases overall system robustness.
Problem Statement
When MySQL rows are updated, the corresponding Redis entries become stale. The acceptable staleness window depends on business requirements, but most systems aim for eventual consistency.
Redis as a MySQL Cache
Typical architecture:
MySQL ←‑ write‑through / async update → Redis (cache)The challenge is synchronizing Redis after every MySQL modification without incurring prohibitive latency or complexity.
Solution Options
Option 1 – TTL‑Only (Cache‑Aside)
Set an expiration time (TTL) on each Redis key. MySQL updates do not touch Redis; the stale entry is automatically evicted when the TTL expires.
Pros: Minimal development effort; no additional connections; low operational risk.
Cons: Staleness duration equals the TTL. Short TTL → frequent cache misses; long TTL → prolonged inconsistency.
Typical TTL configuration (seconds):
redis> CONFIG SET maxmemory-policy allkeys-lru
redis> SETEX user:123 300 "{...}" # 5‑minute TTLOption 2 – Write‑Through with TTL Backup
When the application updates MySQL, it also writes the new value to Redis. The TTL remains as a safety net in case the write‑through fails.
Pros: Reduced staleness compared with pure TTL; fallback TTL prevents permanent stale entries.
Cons: If the Redis write fails after MySQL succeeds, the system degrades to Option 1. Requires simultaneous MySQL and Redis connections, increasing connection‑pool pressure.
Example transaction (pseudo‑code):
BEGIN;
UPDATE users SET name='Alice' WHERE id=123;
redis.set('user:123', newValue, ex=300);
COMMIT;Option 3 – Asynchronous Update via Message Queue
Introduce a durable message queue (e.g., Kafka) between MySQL and Redis. After a successful MySQL write, the service publishes an update event. A separate consumer reads the event and updates Redis.
Pros: Decouples business logic from cache updates; the queue can batch messages and provide local buffering, reducing connection spikes; at‑least‑once delivery guarantees consistency after manual commit.
Cons: Ordering is not guaranteed when multiple producers update the same row; additional consumer service and queue infrastructure increase operational cost.
Sample Kafka producer (Go):
msg := kafka.Message{Key: []byte(strconv.Itoa(userID)), Value: []byte(newJSON)}
producer.WriteMessages(context.Background(), msg)Sample consumer handling ordering with version check:
for msg := range consumer.Messages() {
var ev UpdateEvent
json.Unmarshal(msg.Value, &ev)
// optimistic lock: compare version before write
if ev.Version > cacheVersion {
redis.Set(ev.Key, ev.Value)
}
consumer.CommitMessages(msg)
}Option 4 – Binlog Subscription (Change Data Capture)
Deploy a dedicated sync service that acts as a MySQL replica, reads the binary log (binlog), parses row‑level events, and updates Redis accordingly. This approach provides near‑real‑time propagation and preserves the original commit order.
Pros: Low latency when MySQL load is moderate; completely decoupled from application code; natural ordering guarantees because events are processed in the same order MySQL applied them.
Cons: Requires building and maintaining a binlog parser (e.g., using Maxwell, Debezium, or custom Go/Python client); added infrastructure complexity.
Typical binlog consumer snippet (Java with Debezium):
Properties props = new Properties();
props.setProperty("connector.class", "io.debezium.connector.mysql.MySqlConnector");
props.setProperty("database.hostname", "mysql-host");
props.setProperty("database.port", "3306");
props.setProperty("database.user", "debezium");
props.setProperty("database.password", "pwd");
props.setProperty("database.server.id", "184054");
props.setProperty("database.server.name", "my-app");
props.setProperty("database.history.kafka.bootstrap.servers", "kafka:9092");
props.setProperty("database.history.kafka.topic", "schema-changes.mydb");
Engine engine = Engine.create(props, (records, committer) -> {
for (ChangeEvent event : records) {
// translate to Redis command
redis.set(event.key(), event.value());
}
committer.markBatchFinished();
});
engine.run();Selection Guidelines
Assess latency tolerance and write frequency. If the application cannot tolerate any stale reads, consider eliminating the cache.
For read‑heavy, write‑light workloads where occasional staleness is acceptable, Option 1 (TTL‑only) is sufficient and incurs zero development cost.
If the business requires fresher data after writes but can accept occasional miss, adopt Option 2. Implement simple retry logic if desired.
When strict latency and reliability are needed, evaluate Option 3 or Option 4. Option 4 provides stronger ordering guarantees and eliminates the need for a separate message‑queue service, at the expense of higher implementation complexity.
Conclusion
In most practical scenarios, a simple TTL‑only cache (Option 1) meets performance goals while keeping operational overhead low. For systems with tighter consistency or latency requirements, a binlog‑driven synchronizer (Option 4) offers the best trade‑off between freshness, ordering, and performance, provided the team is willing to invest in the necessary CDC infrastructure.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
