What are write-through and write-behind caching?

Caching is a crucial strategy to improve the speed and performance of databases and applications, storing frequently used information in an easy-to-access location in memory. But with multiple caching strategies available, which is the best one for your situation?

Below, we’ll discuss the definitions of write-through caching and write-behind caching, as well as how write-through caching and write-behind caching work.

Both strategies describe what happens on a write. The read path is a separate decision, covered in read-through caching, and either write strategy can be paired with it. The alternative to both is cache-aside, where the application writes to the database directly and invalidates the cached entry afterwards.

What is write-through caching?

Write-through caching is a caching strategy in which the cache and database are updated almost simultaneously. When we want to update the information in the cache, we first update the cache itself, and then propagate the same update to the underlying database. Changes to the cache that have not yet been sent to the database are referred to as “dirty.”

The use of write-through caching helps guarantee that your data is consistent between the cache and the database. Write-through caching is best when you expect to perform update operations relatively infrequently. If you perform updates too many times while using write-through caching, then this reduces the benefits of having a cache in the first place, since you will need to access the database anyway.

Write-through caching is especially helpful when you want to guard against system failures, such as power outages or crashes. If the system goes down while using write-through caching, it’s very likely that the data in the cache and the database will be identical once the system recovers, since updates are made almost instantaneously.

What is write-behind caching?

Write-behind caching (also called write-back caching) is a caching strategy in which the cache is updated first, and then the database is updated after a set period of time. 

The use of write-behind caching is more convenient when you expect to have a write-heavy workload, i.e. you expect to perform many cache updates. Write-behind caching improves system performance because the user does not (usually) have to wait for changes to be made to the database.

As long as the time limit is not too long, write-behind caching may still deliver acceptable protection from system failures. However, the risk of data loss is greater when using write-behind caching. If the system goes down, any changes to the cache data may not have yet been sent to the database.

How do write-through and write-behind caching work?

In general, write-through caching is easier to implement than write-behind caching for several reasons.

First, in order to successfully implement write-behind caching, all parts of the system must first make changes to the cache before changing the database, as well as check the cache before accessing the database. Otherwise, the system could miss the “dirty” records stored in the cache that have not yet been sent to the database.

Second, write-behind implementations batch their writes to cut the number of round trips to the database. Some go further and coalesce repeated writes to the same key, so a value changed from 1 to 2 and then from 2 to 3 reaches the database only once. Coalescing is not universal, and where it exists it is usually bounded to a single batch rather than to an arbitrary window — so confirm what your implementation actually guarantees rather than assuming it consolidates everything.

Write-through and write-behind caching in Redis

Redis is an open-source, in-memory data structure store that is frequently used to build NoSQL key-value databases, caches, and message brokers. However, one drawback of Redis is that it isn’t automatically compatible with programming languages such as Java out of the box. To lower the Redis learning curve, many Java developers install a third-party Redis Java client such as Redisson.

Redisson implements many different Java objects, collections, and constructs, making it easy for Java developers to get started using Redis. Both write strategies are configured the same way: you supply a MapWriter that knows how to persist entries, then choose a write mode on the options you pass to RMap.

MapWriter<String, Product> writer = new MapWriter<>() {
    @Override
    public void write(Map<String, Product> entries) {
        productRepository.saveAll(entries.values());   // one call per batch
    }

    @Override
    public void delete(Collection<String> keys) {
        productRepository.deleteAllById(keys);
    }
};

// Write-through: the database write happens as part of the map write
MapOptions<String, Product> writeThrough = MapOptions.<String, Product>name("products")
        .writer(writer)
        .writeMode(WriteMode.WRITE_THROUGH);

// Write-behind: the map write returns first, the database write is batched
MapOptions<String, Product> writeBehind = MapOptions.<String, Product>name("products")
        .writer(writer)
        .writeMode(WriteMode.WRITE_BEHIND)
        .writeBehindDelay(5000)        // milliseconds between batches
        .writeBehindBatchSize(100);    // operations per batch

RMap<String, Product> products = redisson.getMap(writeBehind);

products.put("sku-123", product);   // returns without waiting for the database

Without an explicit mode you get write-through: put() does not return until the MapWriter has persisted the entry, so a database failure surfaces as an exception at the call site. Under WRITE_BEHIND the call returns as soon as the cache is updated and the operation is queued instead, and a timer drains up to writeBehindBatchSize queued operations into a single write() or delete() call every writeBehindDelay milliseconds.

Pending writes in a batch are accumulated into a map keyed by cache key, so two writes to the same key inside one batch collapse to the later value — but writes landing in different batches each reach the database. That is batch-local, not the unbounded consolidation sometimes described as conflation. writeRetryAttempts() and writeRetryInterval() control what happens when a batch fails against a database that is temporarily unavailable.

One version note worth checking against any example you find online: MapOptions above is org.redisson.api.options.MapOptions, used with the single-argument getMap(options). The older org.redisson.api.MapOptions, paired with getMap(name, options), still compiles but is deprecated.

The same options apply to RMapCache through MapCacheOptions if you also need per-entry TTL, to the local-cached map variants, and to MapWriterAsync if your persistence layer is non-blocking. For the full configuration surface, see Map persistence in the Redisson documentation.

Similar terms