Database Caching With Redis and Java
Explore database caching with Redis and Java and see some of the different caching strategies.
Join the DZone community and get the full member experience.
Join For FreeWhy Is Database Caching so Important?
The more information you have in a database, the slower it will become over time. Even database management systems that are well-designed to support many concurrent requests will eventually hit their limit.
Database caching is one of the most common strategies for dealing with these performance issues. Caching involves saving the results of database queries in a location that is faster and easier to access. When done correctly, caching will slash query response times, decrease the load on your databases, and cut costs.
However, caches also need to be handled with care because they essentially make another copy of your information in a separate location. Keeping both the database and the cache synchronized and up-to-date can be a trickier challenge than you anticipated. In the next section, we'll discuss some of the most common database caching strategies.
What Are the Different Caching Strategies?
Manual caching (also known as a cache-aside strategy) involves direct management of both the database and the cache. Your application inspects the cache before launching a database query, and it updates the cache after any changes to the database.
While effective if implemented correctly, manual caching can be extremely tedious, especially if you need to query more than one database. For these reasons, developers have invented a number of alternative caching strategies.
Read-Through Caching Strategy
In read-through caching, the application first queries the cache to see if the information it needs is inside. If not, it retrieves the information from the database and uses it to update the cache. The cache provider or cache library is responsible for the detailed logic of querying and updating the cache.
The read-through strategy works best for read-heavy workloads when the application requests the same data repeatedly: for example, a news website that loads the same articles over and over.
One downside of the read-through strategy is that the first query to the cache will always result in a miss because the requested information is guaranteed not to be inside. To deal with this issue, developers often "warm" the cache ahead of time with information that users are likely to request.
Write-Through Caching Strategy
In write-through caching, updates are made to the cache first and to the database second. There is a direct line from the application to the cache and from the cache to the database. When combined with read-through caching, a write-through strategy guarantees that your data will be consistent, removing the need for manual cache invalidation.
Write-Behind Caching Strategy
In write-behind caching (also known as write-back caching), the application first writes data to the cache. After a set period of delay, the cache writes this information to the database as well. Write-behind caches are best for write-heavy workloads and can perform well even with some failures and downtime.
Java-Based Redis Caching With Redisson
Redis is one of the most popular options for NoSQL databases, using a key-value system to store data. Redisson, a client library for Redis in the Java programming language, makes it easy to access Redis features using all the familiar Java collections.
Redisson allows you to place data in Maps in external storage. You can use this functionality to implement caching for databases, web services, or any other data source.
Read-Through Caching in Redis
Below is a Java example of how to use read-through caching in Redis with Redisson.
If the requested entry doesn't exist in the cache, it will be loaded by MapLoader object:
MapLoader<String, String> mapLoader = new MapLoader<String, String>() {
@Override
public Iterable<String> loadAllKeys() {
List<String> list = new ArrayList<String>();
Statement statement = conn.createStatement();
try {
ResultSet result = statement.executeQuery("SELECT id FROM student");
while (result.next()) {
list.add(result.getString(1));
}
} finally {
statement.close();
}
return list;
}
@Override
public String load(String key) {
PreparedStatement preparedStatement = conn.prepareStatement("SELECT name FROM student where id = ?");
try {
preparedStatement.setString(1, key);
ResultSet result = preparedStatement.executeQuery();
if (result.next()) {
return result.getString(1);
}
return null;
} finally {
preparedStatement.close();
}
}
};
Configuration example:
MapOptions<K, V> options = MapOptions.<K, V>defaults()
.loader(mapLoader);
RMap<K, V> map = redisson.getMap("test", options);
// or
RMapCache<K, V> map = redisson.getMapCache("test", options);
// or with boost up to 45x times
RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options);
// or with boost up to 45x times
RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
Write-Through Caching in Redis
Below is a Java example of how to use write-through caching in Redis in Redis with Redisson.
Cache update method will not return until both the cache and the database have been updated by MapWriter object:
MapWriter<String, String> mapWriter = new MapWriter<String, String>() {
@Override
public void write(Map<String, String> map) {
PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO student (id, name) values (?, ?)");
try {
for (Entry<String, String> entry : map.entrySet()) {
preparedStatement.setString(1, entry.getKey());
preparedStatement.setString(2, entry.getValue());
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
} finally {
preparedStatement.close();
}
}
@Override
public void delete(Collection<String> keys) {
PreparedStatement preparedStatement = conn.prepareStatement("DELETE FROM student where id = ?");
try {
for (String key : keys) {
preparedStatement.setString(1, key);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
} finally {
preparedStatement.close();
}
}
};
Configuration example:
MapOptions<K, V> options = MapOptions.<K, V>defaults()
.writer(mapWriter)
.writeMode(WriteMode.WRITE_THROUGH);
RMap<K, V> map = redisson.getMap("test", options);
// or
RMapCache<K, V> map = redisson.getMapCache("test", options);
// or with boost up to 45x times
RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options);
// or with boost up to 45x times
RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
Write-Behind Caching in Redis
The MapWriter interface is also used to asynchronously commit updates to the Map object (cache) and the external storage (database). All Map updates are accumulated in batches and asynchronously written with defined delay.
writeBehindDelay — delay of batched write or delete operation. The default value is 1000 milliseconds.
writeBehindBatchSize — size of batch. Each batch contains Map Entry write or delete commands. The default value is 50.
Below, we see a Java example of the configuration for a Redis-based write-behind caching implementation in Redisson:
MapOptions<K, V> options = MapOptions.<K, V>defaults()
.writer(mapWriter)
.writeMode(WriteMode.WRITE_BEHIND)
.writeBehindDelay(5000)
.writeBehindBatchSize(100);
RMap<K, V> map = redisson.getMap("test", options);
// or
RMapCache<K, V> map = redisson.getMapCache("test", options);
// or with boost up to 45x times
RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options);
// or with boost up to 45x times
RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
All discussed strategies are available for the RMap, RMapCache, RLocalCachedMap, and RLocalCachedMapCache objects in Redisson. Using these latter two objects can make read operations in Redis up to 45 times faster.
Opinions expressed by DZone contributors are their own.
Comments