- Notifications
You must be signed in to change notification settings - Fork11.7k
Fix Cache::flexible() to work with AWS ElastiCache Serverless (Valkey)#57651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:12.x
Are you sure you want to change the base?
Conversation
The flexible() method was generating keys that could hash to differentslots in Redis Cluster, causing CROSSSLOT errors. This fix adds Redishash tags to ensure all related keys (value, created timestamp, and lock)are stored in the same slot.Changes:- Add a 4-character MD5 hash prefix as a hash tag for slot allocation- Wrap hash in {} to ensure consistent slot placement in Redis Cluster- Maintain original key structure for non-cluster Redis instances- Works with both phpredis and predis driversThis resolves issues with AWS ElastiCache Serverless (Valkey) and otherRedis Cluster environments while remaining backward compatible withstandalone Redis instances.vadimonus commentedNov 3, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@yhayase , your PR is backward incompatible, as will change all logic of setting and retreiving values from cache. It's very dangerous. Also you do not need to use md5 hash for key, as redis already calculates slot with more quick crc16 hash (seehttps://redis.io/docs/latest/operate/oss_and_stack/management/scaling/#redis-cluster-data-sharding). So, all you need are only changes in \Illuminate\Cache\Repository::flexible method. Maybe it was just a mistake not to escape curly braces. This will lead that slot for key 'xxx' and 'illuminate:cache:flexible:created:{xxx}' would be the same (see how redis calculate slots). There are integration tests with Redis Cluster in .github/workflows/queues.yml. You may add run of tests/Integration/Cache/RepositoryTest.php there to show if it fails before fix and not fails after it. |
This adds a new configuration option 'flexible_cluster_mode' that enablessequential operations in Cache::flexible() for Redis Cluster compatibility.Implementation:- When 'flexible_cluster_mode' is enabled, flexible() uses sequential get/put operations instead of bulk many()/putMany() operations- This avoids CROSSSLOT errors in Redis Cluster environments where keys hash to different slots- Default is false to maintain existing behaviorConfiguration (config/cache.php):```php'stores' => [ 'redis' => [ 'driver' => 'redis', 'flexible_cluster_mode' => env('CACHE_FLEXIBLE_CLUSTER_MODE', false), ],],```Trade-offs:- Sequential mode: 2 network round-trips instead of 1- Performance impact is minimal since flexible() callback execution dominates- This approach already exists for putMany() in cluster connectionsAll tests pass (75 tests, 302 assertions).🤖 Generated with [Claude Code](https://claude.com/claude-code)Co-Authored-By: Claude <noreply@anthropic.com>9fb8741 to1eadeb1CompareRepository::flexible() references the flexible_cluster_mode config,but CacheManager::repository() was using Arr::only($config, ['store']),which prevented flexible_cluster_mode from being passed to Repository.Changes:- Modified Arr::only($config, ['store', 'flexible_cluster_mode'])- This ensures flexible_cluster_mode config is properly passed to RepositoryTest results:- Local: 75 tests, 302 assertions all passed- Valkey Serverless: 3/3 patterns all succeeded - phpredis + default: SUCCESS - predis + default: SUCCESS - predis + clusters: SUCCESS🤖 Generated with [Claude Code](https://claude.com/claude-code)Co-Authored-By: Claude <noreply@anthropic.com>
60baaa4 toc798cdeCompare@vadimonus Thank you so much for your detailed review and valuable suggestions! I really appreciate you taking the time to point out the backward compatibility issue and propose a simpler solution. You're absolutely right that my original approach (adding hash tags to all keys) was backward incompatible and overly complex. I've been reconsidering the implementation based on your feedback. Issue with the hash tag escape approachI initially considered your suggestion to use escaped braces: "illuminate:cache:flexible:created:{{$key}}"This would produce a string like However, I found a critical issue with this approach. When a user provides a key like Redis Cluster would only use the content between the first
These would hash todifferent slots, causing the same CROSSSLOT error we're trying to fix. Alternative approach: Configuration-based sequential operationsI've explored a different approach that maintains backward compatibility while fixing the Redis Cluster issue: Add a Configuration (config/cache.php): 'stores' => ['redis' => ['driver' =>'redis','flexible_cluster_mode' =>true, ],], Implementation:
Trade-offs:
I've tested this approach successfully on AWS ElastiCache Serverless (Valkey) with all patterns passing. I've already updated the branch to implement this new approach. I'll update the PR description once we reach a consensus on the direction. What do you think about this approach? I'm open to any suggestions or alternative solutions you might have. Thanks again for your guidance! |
@yhayase You give such a polite and detailed answer that I don't always understand whether I'm communicating with you or with a gpt bot. Cache keys like If you choose use tho puts instead of one putMany, you do not need special config settings. It's enough to check something like |
Uh oh!
There was an error while loading.Please reload this page.
Description
This PR fixes
Cache::flexible()to work with AWS ElastiCache Serverless (Valkey) and similar environments that havecluster_enabled: 1but provide a single endpoint.Problem
AWS ElastiCache Serverless (Valkey) has a unique configuration:
cluster_enabled: 1(cluster mode enabled)This creates a problem whereneither connection mode works:
phpredis driver
Single-node connection mode (default):
The
flexible()method stores two keys:illuminate:cache:{key}illuminate:cache:flexible:created:{key}These keys hash todifferent slots in Redis Cluster. Since the server has cluster mode enabled, MGET returns a CROSSSLOT error.
Cluster connection mode:
phpredis RedisCluster fails to process responses from Valkey Serverless (Valkey Serverless-specific issue).
predis driver
Single-node connection mode (default):
Same CROSSSLOT error as phpredis.
Cluster connection mode:
predis checks hash tags before executing MGET. Without consistent hash tags, it rejects the operation.
Solution
Add Redis hash tags to ensure all related keys are stored in the same slot:
How it works
{...}to determine the slot{$hashKey}, ensuring they're in the same slot$keyis preserved outside the hash tag for uniquenessWhy this fixes the issue
For phpredis + default (single-node mode):
For predis + default (single-node mode):
For predis + clusters (cluster mode):
{$hashKey}Testing
Verified on AWS ElastiCache Serverless (Valkey) with
cluster_enabled: 1:Complete reproduction case:https://github.com/yhayase/laravel-cache-flexible-valkey-serverless-issue
Compatibility
flexible()cache entries will be missed on first access after upgradeRelated Issue
Fixes CROSSSLOT errors on AWS ElastiCache Serverless (Valkey) and similar environments where the server has cluster mode enabled but provides a single endpoint.