Skip to content

Fix stale reads in Cache/Backend/Redis preload_keys - #41132

Open
EclipseEternal wants to merge 2 commits into
magento:2.4-developfrom
EclipseEternal:fix-redis-preload-keys-stale-read
Open

Fix stale reads in Cache/Backend/Redis preload_keys#41132
EclipseEternal wants to merge 2 commits into
magento:2.4-developfrom
EclipseEternal:fix-redis-preload-keys-stale-read

Conversation

@EclipseEternal

Copy link
Copy Markdown

Description (*)

Magento\Framework\Cache\Backend\Redis::load() has two defects in its preload_keys handling.

1. The preload pipeline re-fires on every load() after a total miss.

The guard is:

if (!empty($this->preloadKeys) && empty($this->preloadedData)) {

$this->preloadedData is built with array_filter(array_combine($this->preloadKeys, $redisResponse)), which strips every key that missed. If every preloaded key misses in a given batch, $preloadedData ends up empty - indistinguishable from "the pipeline has never run". The full N-key pipeline then re-fires on every subsequent load() call for the lifetime of the object, instead of running once.

2. save() and remove() never invalidate the preloaded snapshot.

Both methods only wrap parent::save() / parent::remove() in a try/catch; neither touches $preloadedData. If an id was preloaded and is then written or removed within the same request, load() keeps returning the stale pre-write value from $preloadedData for the rest of that request, because isset($this->preloadedData[$id]) is still true.

Fix: a private bool $preloaded flag now drives the load() guard (set once the pipeline has actually executed, independent of hit/miss), and save()/remove() unconditionally unset($this->preloadedData[$id]) before delegating to the parent implementation.

Adds RedisTest.php, which does not currently exist for this backend (only DatabaseTest, MongoDbTest, and RemoteSynchronizedCacheTest do). It instantiates the real class with a fake minimal Redis client (Credis_Client connects lazily, so no live server is needed) and covers: the pipeline firing exactly once across repeated total-miss load() calls, a preloaded hit being reused without a second pipeline, save() dropping the stale entry so the next load() re-reads, and remove() doing the same.

Related Pull Requests

None.

Fixed Issues (if relevant)

Not previously reported. A related but distinct edge case in the same method (array_combine() raising when exec() returns false) was already fixed via #37510; this PR does not touch that path.

Manual testing scenarios (*)

  1. Configure preload_keys on a default frontend pointed at an empty/flushed Redis namespace (so every preloaded key misses).
  2. Issue two requests (two load() calls). Before this fix, each request re-runs the full preload pipeline; after, only the first does (observable via MONITOR or a call counter on the Redis client).
  3. Preload an id, then within the same request save() a new value for that id and load() it again. Before this fix, the stale pre-save value is returned; after, the new value is returned.
  4. Same as (3) but with remove() instead of save(): before this fix the removed id still resolves via the stale snapshot; after, it does not.
  5. vendor/bin/phpunit lib/internal/Magento/Framework/Cache/Test/Unit/Backend/RedisTest.php covers all four scenarios above in isolation.

Two defects in the preload_keys pipeline in Cache/Backend/Redis:

1. load() guards the preload pipeline on `empty($this->preloadedData)`.
   array_filter() strips missed keys from that array, so a batch where
   every key misses is indistinguishable from "never preloaded", and
   the full pipeline re-fires on every subsequent load() instead of
   running once. A private bool $preloaded flag now drives the guard
   instead, set once the pipeline has actually run regardless of hit
   or miss.

2. save() and remove() never invalidate the preloaded snapshot, so an
   id that was preloaded and then written or removed within the same
   request keeps serving its pre-write value from $preloadedData for
   the rest of that request. Both methods now unset the entry
   unconditionally before delegating to the parent implementation, so
   the next load() re-reads Redis.

Adds RedisTest.php, which did not previously exist for this backend.
@m2-assistant

m2-assistant Bot commented Aug 19, 2026

Copy link
Copy Markdown

Hi @EclipseEternal. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

@EclipseEternal

EclipseEternal commented Aug 19, 2026

Copy link
Copy Markdown
Author

@magento run all tests

- Move the fake Credis_Client stand-in into its own file
  (RedisTestFakeClient.php): the coding standard requires one class
  per file.
- Add missing @var docblocks on two of its properties.
- Drop the unused $field parameter from its hGet() - the fake never
  needed it, since results are keyed by id alone.
@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Unit Tests, Static Tests

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run all tests

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Functional Tests EE, Functional Tests CE

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Unit Tests, Sample Data Tests CE, Sample Data Tests EE, Sample Data Tests B2B

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Sample Data Tests CE, Sample Data Tests EE, Sample Data Tests B2B

@magento-automated-testing

Copy link
Copy Markdown

Failed to run the builds. Please try to re-run them later.

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Sample Data Tests CE, Sample Data Tests EE, Sample Data Tests B2B

@magento-automated-testing

Copy link
Copy Markdown

Failed to run the builds. Please try to re-run them later.

@EclipseEternal

Copy link
Copy Markdown
Author

@magento run Unit Tests

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant