Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Draft
yhayase wants to merge4 commits intolaravel:12.x
base:12.x
Choose a base branch
Loading
fromyhayase:fix-cache-flexible-redis-cluster

Conversation

@yhayase
Copy link

@yhayaseyhayase commentedNov 3, 2025
edited
Loading

Description

This PR fixesCache::flexible() to work with AWS ElastiCache Serverless (Valkey) and similar environments that havecluster_enabled: 1 but provide a single endpoint.

Problem

AWS ElastiCache Serverless (Valkey) has a unique configuration:

  • Server-side:cluster_enabled: 1 (cluster mode enabled)
  • Connection: Single endpoint

This creates a problem whereneither connection mode works:

phpredis driver

Single-node connection mode (default):

TypeError: array_map(): Argument #2 ($array) must be of type array, bool givenFile: vendor/illuminate/redis/Connections/PhpRedisConnection.php:67Redis last error: CROSSSLOT Keys in request don't hash to the same slot

Theflexible() method stores two keys:

  • Value key:illuminate:cache:{key}
  • Timestamp 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:

RedisClusterException: Error processing response from Redis node!

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\NotSupportedException: Cannot use 'MGET' with redis-cluster.

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:

$hashKey =substr(md5($key),0,4);// 16-bit hash$valueKey ="{{$hashKey}}:{$key}";$createdKey ="{{$hashKey}}:illuminate:cache:flexible:created:{$key}";$lockKey ="{{$hashKey}}:illuminate:cache:flexible:lock:{$key}";

How it works

  • Redis Cluster uses the content within{...} to determine the slot
  • All three keys share the same hash tag{$hashKey}, ensuring they're in the same slot
  • The 4-character MD5 hash (16 bits) provides sufficient uniqueness for slot distribution
  • The original$key is preserved outside the hash tag for uniqueness
  • Works with standalone Redis (hash tags are ignored)

Why this fixes the issue

For phpredis + default (single-node mode):

  • Keys now hash to the same slot
  • CROSSSLOT error no longer occurs
  • Works with Valkey Serverless ✅

For predis + default (single-node mode):

  • Keys now hash to the same slot
  • CROSSSLOT error no longer occurs ✅

For predis + clusters (cluster mode):

  • predis checks that all keys have the same hash tag{$hashKey}
  • Allows MGET to proceed since keys are in the same slot
  • Works with Valkey Serverless ✅

Testing

Verified on AWS ElastiCache Serverless (Valkey) withcluster_enabled: 1:

  • ✅ phpredis + default (single-node connection):SUCCESS (0.145s)
  • ✅ predis + default (single-node connection):SUCCESS (0.008s)
  • ✅ predis + clusters (cluster mode connection):SUCCESS (0.095s)

Complete reproduction case:https://github.com/yhayase/laravel-cache-flexible-valkey-serverless-issue

Compatibility

  • ✅ Backward compatible with standalone Redis (hash tags are ignored)
  • ✅ Works with phpredis and predis drivers
  • ✅ Works with all cache backends (hash tags are added to all drivers, but only meaningful for Redis)
  • ⚠️Breaking change: Cache keys will change after upgrading
    • Affects all cache drivers (Redis, Memcached, Database, File, DynamoDB, etc.)
    • Existingflexible() cache entries will be missed on first access after upgrade
    • Callbacks will be executed to regenerate values
    • Old cache entries will be automatically deleted when their TTL expires
    • No manual cache clearing required, but temporary storage increase during TTL period

Related Issue

Fixes CROSSSLOT errors on AWS ElastiCache Serverless (Valkey) and similar environments where the server has cluster mode enabled but provides a single endpoint.

hayase_yasuhiro added2 commitsNovember 3, 2025 22:24
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.
@yhayaseyhayase changed the titleFix Cache::flexible() to work with Redis ClusterFix Cache::flexible() to work with AWS ElastiCache Serverless (Valkey)Nov 3, 2025
@yhayaseyhayase marked this pull request as draftNovember 3, 2025 14:00
@vadimonus
Copy link
Contributor

vadimonus commentedNov 3, 2025
edited
Loading

@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.

"illuminate:cache:flexible:created:{$key}" -> "illuminate:cache:flexible:created:\{$key\}""illuminate:cache:flexible:lock:{$key}" -> "illuminate:cache:flexible:lock:\{$key\}"

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.
You will need CACHE_STORE=redis and REDIS_CACHE_CONNECTION=default for tests with redis cluster

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>
@yhayaseyhayaseforce-pushed thefix-cache-flexible-redis-cluster branch from9fb8741 to1eadeb1CompareNovember 5, 2025 02:48
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 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>
@yhayaseyhayaseforce-pushed thefix-cache-flexible-redis-cluster branch from60baaa4 toc798cdeCompareNovember 5, 2025 04:15
@yhayase
Copy link
Author

@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 approach

I initially considered your suggestion to use escaped braces:

"illuminate:cache:flexible:created:{{$key}}"

This would produce a string likeilluminate:cache:flexible:created:{mykey} when$key = "mykey", which looks correct for Redis hash tags.

However, I found a critical issue with this approach. When a user provides a key like"a}keyname", the result would be:

illuminate:cache:flexible:created:{a}keyname}

Redis Cluster would only use the content between the first{ and} (i.e.,a) for slot calculation. This means:

  • User keya}keyname → slot based on full stringa}keyname
  • Created keyilluminate:cache:flexible:created:{a}keyname} → slot based ona only

These would hash todifferent slots, causing the same CROSSSLOT error we're trying to fix.

Alternative approach: Configuration-based sequential operations

I've explored a different approach that maintains backward compatibility while fixing the Redis Cluster issue:

Add aflexible_cluster_mode configuration option that switchesflexible() to use sequential operations instead of bulk operations (MGET/MSET):

Configuration (config/cache.php):

'stores' => ['redis' => ['driver' =>'redis','flexible_cluster_mode' =>true,    ],],

Implementation:

  • Whenflexible_cluster_mode is enabled,flexible() uses individualget()/put() calls instead ofmany()/putMany()
  • This avoids CROSSSLOT errors entirely since each operation targets a single key
  • Default isfalse, maintaining existing behavior
  • No key name changes, so fully backward compatible
  • Works consistently withCache::get(),Cache::forget(), andCache::tags()

Trade-offs:

  • 2 network round-trips instead of 1 (minimal performance impact since callback execution typically dominates)
  • Only needed for environments like AWS ElastiCache Serverless where the server has cluster mode enabled but provides a single endpoint
  • For proper Redis Cluster connections (predis clusters mode), this configuration is not needed as putMany() already handles it correctly

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!

@vadimonus
Copy link
Contributor

@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 likea}keyname can appear only in rare specific cases. In such cases you should think about some sort of escaping of keys. They can containt { as well, leading to unpredictable behaviour in many cases. Such cases rare cases can be eliminated on application and not on framework side. Please note, Redis Cluster itself does not provide any way to escape curly braces, or other way to specify, what part of key to use as hash key is. So with any other framework or without it you will experience some difficulties if working with Redis Cluster and keys with curly braces.
In this situation, moving solution of such rare cases from framework to application level is good tradeoff between framework simplicity, backward compatibility and making it workable in most of cases.

If you choose use tho puts instead of one putMany, you do not need special config settings. It's enough to check something like$this->store->connection instanceof PhpRedisCluster. See similar branches in RedisBroadcaster class.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@yhayase@vadimonus

[8]ページ先頭

©2009-2025 Movatter.jp