-
Notifications
You must be signed in to change notification settings - Fork 11.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 our terms of service and privacy 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 different
slots in Redis Cluster, causing CROSSSLOT errors. This fix adds Redis
hash 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 drivers
This resolves issues with AWS ElastiCache Serverless (Valkey) and other
Redis Cluster environments while remaining backward compatible with
standalone Redis instances.
|
@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 (see https://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 enables
sequential 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 behavior
Configuration (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 connections
All tests pass (75 tests, 302 assertions).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
9fb8741 to
1eadeb1
Compare
Repository::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 Repository Test 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 to
c798cde
Compare
|
@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 to different 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 |
|
Hi @vadimonus, Thank you so much for your thoughtful feedback on this PR! Your suggestion about using After our discussion, I realized that instead of trying to make keys hash to the same slot or adding configuration options, the right approach was to:
I've implemented this approach and tested it successfully on AWS ElastiCache Valkey Serverless. The test-first development process is documented here:
I'll be submitting a new PR to laravel/framework soon as a follow-up to PR #53940.
Haha, you're actually half right! Since English isn't my native language, I work together with Claude Code (Anthropic's AI coding assistant) when writing PRs and replies. Claude helps me organize technical details and express them clearly in English, though it sometimes makes my messages longer and perhaps more formal than necessary. That's partly the AI's influence, but honestly also just my communication style - I tend to include a lot of detail and try to be thorough (maybe too much so!), especially since I'm still getting used to PR discussions. Thanks again for pushing me in the right direction. Your feedback was invaluable! |
|
You're referencing #53940, where problem of multiple put with Redis was already solved. Your problem occures on call to $this->many? In such case you should rollback all you have done, and just repeat solution from #53940 for RedisStore::many, and do nothing more. I said nothing about calling INFO. It's used in test just to wait for all cluster nodes to be up and healthy, without it tests fails randomly. Your do not need to call cluster INFO, to decide, if it's cluster or not connection. Check of instanceof is enough. Framework not expected to work correctly, if you defined cluster connection, but attach to single node redis, or vice versa. Also it would be good, if you find a way to add cache integration tests on redis cluster environment, same as for queues, to prevent other developers adding new code that will work incorrectly with cluster. When i found some problems with batches in Redis Cluster, i firstly bring integration tests, showing that problem exists. |
|
Hi @vadimonus, Thank you for the detailed feedback! Correction on Your main point: Why this doesn't work: phpredis cluster mode fails on Valkey Serverless. We tested this and documented it here: https://github.com/yhayase/laravel-cache-flexible-valkey-serverless-issue Pattern 2 (phpredis + clusters): 'clusters' => [
'default' => [
['host' => 'xxxxx.serverless.apne1.cache.amazonaws.com', 'port' => 6379]
]
]Result:
Why this likely happens: According to phpredis documentation, when connecting in cluster mode, it runs
This is why users must use regular connection mode, but then |
|
The problem you're talking is not Laravel problem. Redis Cluster and Valkey as its successor do not operate with domain names. It stores only ip address for nodes. When some slot is not found on some node, this node returns redirect answer, which contains IP Address of node, containing slot. |
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 where neither 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 to different 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.