Showing posts with label Redis. Show all posts
Showing posts with label Redis. Show all posts

Friday, 14 March 2025

Efficient Redis Cache Invalidation Using Async Events

Caching is an integral part of improving application performance, but ensuring cache consistency can be a challenge. I recently designed a Redis cache invalidation mechanism that efficiently removes outdated cache entries using asynchronous events. This ensures that any modification to a cached object triggers an automatic cache cleanup, maintaining data integrity.

Approach

I used Redisson Buckets for caching objects and implemented an event-driven approach for cache invalidation. Whenever an update occurs on a cached object, an asynchronous event is triggered to delete the associated cache bucket.

Cache Example

To efficiently retrieve user details, we cache user profile information using Redis. If the data is not present in the cache, we fetch it from the database, validate the status, and store it in the cache for future use.

public EntityDetails findEntityDetails(String userId) {
String cacheKey = "CACHE_ENTITY_DETAILS_" + userId;
RBucket<EntityDetails> bucket = cacheClient.getBucket(cacheKey, JsonJacksonCodec.INSTANCE);
EntityDetails entityDetails = bucket.get();

if (entityDetails == null) {
entityDetails = entityRepository.findByUserId(userId);
if (entityDetails == null) {
throw new ApplicationException(ErrorCodes.ENTITY_NOT_FOUND.getErrorCode());
}
if ("INACTIVE".equalsIgnoreCase(entityDetails.getStatus())) {
throw new ApplicationException(ErrorCodes.ENTITY_NOT_FOUND_OR_INACTIVE.getErrorCode());
}
bucket.set(entityDetails, cacheExpiryTime, TimeUnit.SECONDS);
} else {
if ("INACTIVE".equalsIgnoreCase(entityDetails.getStatus())) {
throw new ApplicationException(ErrorCodes.ENTITY_NOT_FOUND_OR_INACTIVE.getErrorCode());
}
}
return entityDetails;
}

Event-Driven Cache Invalidation

The event listener processes incoming events and checks if they require cache invalidation. If an event of type INVALIDATE_CACHE_EVENT is received, the corresponding userId is extracted from the payload, and cache deletion logic is executed.

public void onEvent(CacheEvent event) {
final Map<Object, Object> payload = (Map<Object, Object>) event.getPayload();
final String eventType = event.getType();
LOGGER.info("Received event. Type: {}, Payload: {}", eventType, payload);

if (CacheService.INVALIDATE_CACHE_EVENT.equals(eventType)) {
String userId = (String) payload.get("userId");
if (StringUtils.isNotBlank(userId)) {
batchDeleteCacheKeys(userId);
} else {
LOGGER.warn("UserId is blank or missing in the event payload.");
}
}
}

Batch Deletion of Cache Keys

The core cache invalidation logic revolves around dynamically constructing cache keys based on user details and associated identifiers. These keys are then deleted in bulk using Redisson’s key deletion API.

private void batchDeleteCacheKeys(String userId) {
try {
List<String> cacheKeys = new ArrayList<>();

// Load configured bucket prefixes for deletion
String[] bucketNames = configuration.getProperty("bucket.for.deletion", "").split(",\\s*");
for (String bucketPrefix : bucketNames) {
cacheKeys.add(bucketPrefix + "_" + userId);
}

// Fetch entity-related cache keys
var entityDetails = cacheService.findEntityDetails(userId);
if (entityDetails != null) {
cacheKeys.add("ENTITY_" + entityDetails.getEntityId());
}

// Fetch associated identifiers and related cache entries
var associatedIdentifiers = cacheService.findAssociatedIdentifiers(userId);
if (associatedIdentifiers != null) {
cacheKeys.addAll(associatedIdentifiers.stream()
.map(identifier -> "CACHEKEY_" + identifier.getValue())
.collect(Collectors.toList()));
}

// Perform bulk deletion
if (!cacheKeys.isEmpty()) {
LOGGER.info("Deleting {} cache keys for userId {}", cacheKeys.size(), userId);
cacheClient.getKeys().delete(cacheKeys.toArray(new String[0]));
} else {
LOGGER.info("No cache keys found for userId {}", userId);
}
} catch (Exception ex) {
LOGGER.error("Error occurred while deleting cache for userId '{}': {}", userId, ex.getMessage(), ex);
}
}

Key Takeaways

  • Event-Driven Invalidations: Async events ensure that cache cleanup happens seamlessly without blocking application flow.
  • Efficient Bulk Deletion: Constructing and deleting cache keys in bulk prevents unnecessary Redis calls.
  • Scalability & Performance: Using Redisson Buckets provides a structured and scalable approach for managing cache invalidation.

By leveraging this approach, we can maintain a consistent and optimized cache without the risk of serving stale data. This ensures high availability and responsiveness in applications relying on Redis for caching.

Wednesday, 24 May 2023

Memcache vs. Redis: Choosing the Right In-Memory Data Store





When it comes to caching solutions in the world of software development, Redis and Memcached are two of the most popular choices. Both are powerful, in-memory data stores that can significantly enhance application performance. However, they come with distinct features and trade-offs. This article dives into the differences between Redis and Memcached, providing key statistics and references to help you make an informed decision.

What is Redis?

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store used as a database, cache, and message broker. It supports various data structures such as strings, hashes, lists, sets, and more.

Key Features of Redis:

  • Data Persistence: Redis supports data persistence, allowing data to be saved to disk and reloaded after a restart.
  • Advanced Data Structures: Supports complex data types like sorted sets, hyperloglogs, bitmaps, and streams.
  • Replication and High Availability: Redis supports master-slave replication and Redis Sentinel for high availability.
  • Pub/Sub Messaging: Provides publish/subscribe messaging functionality.
  • Lua Scripting: Supports server-side scripting using Lua.

What is Memcached?

Memcached is a high-performance, distributed memory caching system designed to speed up dynamic web applications by alleviating database load. It is simple yet powerful, primarily used for caching database query results, session data, and more.

Key Features of Memcached:

  • Simplicity and Speed: Memcached is designed for simplicity and speed, making it extremely fast for simple caching use cases.
  • Distributed Caching: Easily distributed across multiple servers, making it scalable.
  • Least Recently Used (LRU) Eviction: Uses LRU eviction policy to manage cache memory.
  • Multi-threaded Design: Supports multithreaded operations, enhancing performance on multi-core systems.

Comparative Analysis

Let’s compare Redis and Memcached based on various parameters:

Performance

  • Read/Write Speed: Memcached generally performs better for simple read/write operations due to its simplicity.
  • Memory Efficiency: Memcached is more memory-efficient for simple key-value storage.
  • Complex Operations: Redis excels in scenarios requiring complex operations and data structures.

Benchmark Stats

Latency:

  • Redis: ~0.25ms
  • Memcached: ~0.20ms

Throughput:

  • Redis: ~500,000 ops/sec (read-heavy workload)
  • Memcached: ~700,000 ops/sec (read-heavy workload)

Memory Usage:

  • Redis: Higher due to advanced data structures and persistence features.
  • Memcached: Lower for simple key-value pairs.

When to Use Redis?

  • Persistent Cache: When you need your cached data to survive server restarts.
  • Advanced Data Handling: When your application requires more than simple key-value storage.
  • Message Queuing: For pub/sub messaging systems.
  • Atomic Operations: When you need atomic operations on complex data structures.

When to Use Memcached?

  • Simple Caching: For straightforward key-value storage with high-speed requirements.
  • Memory Efficiency: When memory usage is a critical concern.
  • Distributed Environment: For large-scale web applications needing distributed caching without complex data handling.

When Not to Use Memcached:

Need for Data Persistence:

  • Why: Memcached stores data only in memory and does not support data persistence. If the server restarts or fails, all cached data is lost.
  • Alternative: Redis offers data persistence through RDB (snapshotting) and AOF (Append Only File) mechanisms.

Complex Data Structures:

  • Why: Memcached is limited to simple key-value pairs and does not support advanced data structures.
  • Alternative: Redis supports complex data types like lists, sets, sorted sets, hashes, bitmaps, and hyperloglogs.

High Availability and Replication:

  • Why: Memcached does not natively support replication or high availability features.
  • Alternative: Redis supports master-slave replication and Redis Sentinel for high availability.

Need for Atomic Operations:

  • Why: Memcached lacks support for atomic operations on complex data types, which can be crucial for certain applications.
  • Alternative: Redis supports atomic operations on complex data structures.

Large Key Size and Value Size:

  • Why: Memcached has limitations on key size (up to 250 bytes) and value size (up to 1 MB).
  • Alternative: Redis supports larger key and value sizes (up to 512 MB).

Pub/Sub Messaging:

  • Why: Memcached does not support publish/subscribe messaging patterns.
  • Alternative: Redis includes built-in pub/sub messaging functionality.

Lua Scripting:

  • Why: Memcached does not support server-side scripting.
  • Alternative: Redis allows server-side scripting using Lua, enabling complex operations to be executed on the server.

Geospatial Indexing:

  • Why: Memcached lacks support for geospatial indexing and queries.
  • Alternative: Redis provides geospatial indexing and query capabilities.

Conclusion

Choosing between Redis and Memcached depends on your specific use case. Redis offers more features and flexibility at the cost of higher memory usage, while Memcached provides blazing-fast performance for simple caching tasks.

References

  1. Redis Official Documentation
  2. Memcached Official Documentation
  3. Redis vs Memcached: Detailed Comparison
  4. Benchmarking Redis vs Memcached

By understanding the strengths and weaknesses of each, you can select the best tool for your application’s needs. Happy coding!

Tuesday, 26 April 2022

Optimizing Redis: Best Practices for Enhanced Performance and Tuning

TCP-KeepAlive

TCP-KeepAlive Graph
vim /etc/redis/redis.conf# Update the value to 0
tcp-keepalive 0

Pipelining

How to Use Pipelining
Pipelining Graph

Max-Connection

sudo vim /etc/rc.local

# make sure this line is just before of exit 0.
sysctl -w net.core.somaxconn=65365

Overcommit Memory

echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf

RDB Persistence and Append Only File

sudo vim /etc/redis/redis.conf

# Comment out these lines
save 900 1
save 300 10
save 60 10000

rdbcompression no
rdbchecksum no

appendonly no

Transparent Huge Page(THP)

sudo vim /etc/rc.local

# Add this line before exit 0
echo never > /sys/kernel/mm/transparent_hugepage/enabled








Efficient Redis Cache Invalidation Using Async Events

Caching is an integral part of improving application performance, but ensuring cache consistency can be a challenge. I recently designed a R...