Go Caching Guidelines for Redis (and Codis)
Introduction
This document provides guidelines for effectively using Redis (and Codis, where applicable as a Redis proxy) as a caching layer within Go applications. It emphasizes best practices to prevent misuse, ensure optimal performance, and avoid negative impacts on other services or systems. These guidelines cover key-value design, coding practices, considerations for Redis Cluster, and operational aspects. Following these recommendations will help ensure the stability and efficiency of your Go applications that rely on Redis caching.
Target Audience: Go developers working with Redis (or Codis) for caching.
Key Concepts & Terminology
- Redis: An in-memory data structure store, often used as a cache, message broker, and database.
- Codis: A distributed Redis solution (proxy) that provides scalability and high availability.
- Redis Cluster: Another distributed solution provided by Redis which shards data across multiple nodes.
- Key: The identifier used to store and retrieve data in Redis.
- Value: The data associated with a key.
- Expiration (TTL): The time after which a key is automatically deleted by Redis.
- Connection Pool: A cache of database connections maintained so that connections can be reused when future requests to the database are required. This helps to avoid the overhead of creating new connections for each operation.
- Pipeline: A technique where multiple Redis commands are sent to the server in a batch without waiting for the replies individually improving performance.
- Lua Scripting: Allows you to execute custom logic on the Redis server.
- Lazy Free (ASYNC DELETE / UNLINK): A mechanism in Redis to reclaim memory asynchronously, avoiding blocking the main thread.
- Persistence (RDB/AOF): Mechanisms to persist Redis data to disk for recovery after a restart.
- Hot Key: A key that is accessed very frequently, potentially causing performance bottlenecks.
Guidelines
1. Key-Value Design
Category | Rule | Description | Rationale/Example |
---|---|---|---|
Key Structure | MUST | Avoid special characters (space, TAB, control characters) in keys. Use alphanumeric characters, hyphens, or underscores. | Special characters can cause parsing issues or unexpected behavior. Example: user:123:profile is good; user 123 profile is bad. |
MUST | Set an expiration time (TTL) for every key. For cases where keys should persist indefinitely, carefully evaluate the scale and memory implications. Keys in the user dimension must have an expire time. | Prevents Redis from growing indefinitely and running out of memory. User-specific data often becomes stale over time. Example: SET user:123:profile value EX 3600 (expires in 1 hour). | |
RECOMMENDED | Keep keys short and readable. The maximum key size is 512MB, but performance degrades significantly beyond 1KB. Aim for keys significantly shorter than 1KB. Use a consistent naming convention. | Shorter keys save memory and improve readability. Longer keys can slow down lookup operations. Example: u:123:p (less readable than user:123:profile but shorter). Find the balance! | |
RECOMMENDED | Split hot keys to distribute the load. | A single heavily accessed key can overload a Redis instance. Consider sharding the key. Example: Instead of a single leaderboard key, use leaderboard:0 , leaderboard:1 , etc., based on a user ID hash. | |
Value Structure | RECOMMENDED | Restrict value sizes. Strings should generally be below 1KB. For HASH , LIST , SET , or ZSET types, limit the number of elements to 5000 or less. Consider pagination when retrieving large datasets. | Large values consume more memory and increase serialization/deserialization overhead. Large collections impact command execution time. |
RECOMMENDED | Choose the appropriate data type. Use HASH for dictionary-type data instead of serializing it into a String . Use SET for storing unique values, and ZSET for ordered data. | Using the correct data type allows you to leverage Redis's built-in functionalities and optimizations. HSET user:123 name "John" age 30 is better than storing {"name":"John", "age": 30} as a string. | |
Key Namespace | RECOMMENDED | Establish a consistent key namespace convention. Prefix keys with application or module names (e.g., myapp:user:123 ). | Avoids key collisions between different applications or modules. Improves organization and maintainability. |
2. Best Practices
Category | Rule | Description | Rationale/Example |
---|---|---|---|
Core Principle | MUST | Use Redis as a cache, not a primary database. Data persistence and consistency are not Redis's strengths. | Redis is designed for speed; durability should be handled by a primary database (e.g., PostgreSQL, MySQL). |
Expiration | MUST | Always include the expiration time in the SET command (e.g., SET key value EX 3600 ). Relying solely on EXPIRE can lead to orphaned keys if the server crashes after SET but before EXPIRE . | Ensures that keys are automatically removed, preventing memory leaks. |
Connections | MUST | Use a connection pool with an appropriately sized pool. Avoid short-lived connections. Configure timeouts for connection acquisition and operations. | Connection pools reduce the overhead of creating and destroying connections for each operation. Timeouts prevent applications from hanging indefinitely due to network issues. |
Data Retrieval | MUST | Avoid retrieving all elements from a key at once (e.g., ZRANGE key 0 -1 , LRANGE key 0 -1 , HGETALL , SMEMBERS ). Use pagination or incremental retrieval techniques. For instance, use HSCAN for HASH keys. | Retrieving all elements can block Redis and impact performance, especially for large datasets. Incremental retrieval allows you to process data in chunks. |
Expiration Strategy | MUST | Stagger key expirations to prevent large-scale cache invalidation. Use a random jitter (e.g., EX 3500 + rand(0,200) ) to slightly randomize the expiration time of each key. | Mass expiration events can cause spikes in CPU usage and impact Redis performance. Staggering spreads out the load. |
Error Handling | MUST | Limit the number of retries on Redis errors. Implement exponential backoff to avoid overwhelming Redis after a failure. Log all errors. | Retrying indefinitely can exacerbate the problem if Redis is overloaded or unavailable. Backoff strategies give Redis time to recover. |
Testing | MUST | Don't use production or pre-production clusters for stress testing. Use dedicated testing environments. Always expire stress test keys after the test. | Stress tests can overload Redis and impact production services. Cleaning up test data is essential. |
Optimization | RECOMMENDED | Analyze slow queries (commands exceeding 1ms). Avoid commands exceeding 10ms. Use redis-cli --slowlog-get 10 to retrieve the last 10 slow queries. | Identifying and optimizing slow queries is crucial for maintaining Redis performance. |
Batch Operations | RECOMMENDED | Avoid MGET and MSET if possible. If necessary, limit the number of keys requested to a small number (e.g., < 100). MGET can be a better choice for certain workloads than individual GET commands, but use them judiciously. | MGET and MSET can become inefficient for large numbers of keys, especially in a clustered environment. Consider the network overhead. |
Pipelining | RECOMMENDED | Use PIPELINE for batching multiple commands to improve efficiency. Be aware that pipelines are not atomic operations. Use transactions if atomicity is required, but with caution due to performance implications. | Pipelining reduces round trip times and improves throughput. |
Lua Scripting | RECOMMENDED | Minimize the use of Lua scripts, as they can be difficult to troubleshoot and debug. When using Lua, ensure that all operation keys reside on the same Redis instance (especially important for clustered environments). | Lua scripts execute on the Redis server, so errors can be harder to diagnose. In a cluster, all keys accessed by a Lua script must hash to the same slot to guarantee atomicity. |
Configuration | RECOMMENDED | Properly configure timeout and TCP-Keepalive parameters for Redis connections. | Timeouts prevent connections from hanging indefinitely. TCP-Keepalive helps to detect and close dead connections. |
Client Library | RECOMMENDED | Use go-redis as the default Redis client library unless there is a compelling reason to use another. | go-redis is a widely used and well-maintained Go Redis client. |
3. Redis Cluster Specific Considerations
Category | Rule | Description | Rationale/Example |
---|---|---|---|
Lazy Free | RECOMMENDED | For Redis v4.0 and above, enable the lazy free mechanism (lazyfree-lazy-eviction yes , lazyfree-lazy-expire yes , lazyfree-lazy-server-del yes ) in production environments in redis.conf . | Lazy free reduces the impact of deleting large keys or expiring many keys simultaneously which can cause latency spikes. It avoids blocking the main thread during these operations. Remember to restart the Redis server after changing redis.conf . |
Limitations | RECOMMENDED | Avoid using MGET and MSET in Redis Cluster due to potential cross-slot key issues and redirection overhead. Prefer fetching keys individually or using pipelining with careful slot affinity management. | In Redis Cluster, keys are distributed across multiple nodes based on hash slots. MGET and MSET may require redirection to different nodes, increasing latency. If keys must be accessed using MGET and MSET , ensure those keys belong to the same slot (e.g., using hash tags). |
Transactions | RECOMMENDED | Redis Cluster does not support multi-key transaction operations across different slots. If atomicity is crucial, consider alternative strategies like Lua scripting with keys in the same slot, or redesigning your data model. | Transactions in Redis (using MULTI , EXEC ) are only guaranteed to be atomic if all keys involved reside within the same hash slot. |
4. Operation and Maintenance
Category | Rule | Description | Rationale/Example |
---|---|---|---|
Data Sync | RECOMMENDED | Understand that master-slave/replica data synchronization is asynchronous and guarantees eventual consistency. Master-slave/replica switching can take up to 30 seconds (or more, depending on configuration and data size). | Be aware of potential data loss or inconsistencies during failover. Implement appropriate error handling and retry mechanisms in your application. |
Authentication | RECOMMENDED | Enable password authentication (requirepass ) for Redis instances. | Prevents unauthorized access to your Redis data. |
Isolation | RECOMMENDED | Deploy Redis instances separately for different business units or applications. | Prevents failures in one application from affecting other services. |
Persistence | RECOMMENDED | If data is not highly sensitive and can be tolerated to lose some data enable persistence selectively. Enabling persistence may cause delays, especially on non-SSD disks. Evaluate the tradeoffs between data durability and performance. | RDB snapshots are faster but can lead to data loss within the snapshot interval. AOF provides better durability but can impact write performance, especially with fsync always . |
Persistence Options | RECOMMENDED | Be aware that that RDB persistence may result in data loss within the configured storage period. AOF persistence guarantees at least 2 seconds of data protection but can be affected by disk blocking and other issues. | Choose the persistence method that aligns with your data loss tolerance and performance requirements. |
Monitoring | RECOMMENDED | Implement comprehensive monitoring of Redis instances, including CPU usage, memory usage, network traffic, slow queries, and connected clients. Use tools like redis-cli info , redis-stat , Prometheus, or Datadog. | Proactive monitoring allows you to identify and address performance issues before they impact your application. Setup alerts for critical metrics (e.g., high memory usage, slow command execution). |
Conclusion
By adhering to these guidelines, you can leverage Redis (and Codis) effectively as a caching layer in your Go applications, ensuring performance, stability, and maintainability. Remember to regularly review and update these guidelines as your application and infrastructure evolve. Good luck!