Add an in-memory LRU cache

Created on 25 June 2011, over 13 years ago
Updated 26 November 2024, about 2 months ago

Problem/Motivation

Long running processes - migrations and any big drush command can run into memory issues, especially with entity static caching.

Proposed resolution

#375494: Restrict the number of nodes held in the node_load() static cache β†’ added a memory cache - putting the 'static' cache into a service that can be swapped out, and cleared independently from entity persistent caching. This will allow drush's memory reclaim logic to work the same way that it did in Drupal 7 (when it cleared drupal_static).

However we can go a step or two further, and add an LRU implementation of the memory cache API, which holds a maximum number of items and cycles out once it goes over. This would allow drush or migrate to replace the service, and just load entities as much as it likes without requiring manual memory reclamation.

Remaining tasks

User interface changes

API changes

Data model changes

#375494: Restrict the number of nodes held in the node_load() static cache β†’ adds an LRU cache for the entity cache, I wanted to see if we could make a generic in-memory LRU cache that could maybe be used elsewhere.

Turns out to be reasonably simple to write. I haven't benchmarked this or anything yet, just wanted to get something that works first. It does not yet allow for adding multiple items to the cache at once, that may be a useful feature to add for the entity case.

Patch only adds the class and a unit test, but marking CNR for the bot and hopefully feedback.

πŸ“Œ Task
Status

Needs work

Version

11.0 πŸ”₯

Component

base system

Created by

πŸ‡¬πŸ‡§United Kingdom catch

Live updates comments and jobs are added and updated live.
  • Performance

    It affects performance. It is often combined with the Needs profiling tag.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • First commit to issue fork.
  • Merge request !10336#1199866 Add an in-memory LRU cache β†’ (Open) created by gapple
  • Pipeline finished with Failed
    about 2 months ago
    Total: 160s
    #350102
  • Pipeline finished with Failed
    about 2 months ago
    Total: 159s
    #350122
  • Pipeline finished with Failed
    about 2 months ago
    Total: 154s
    #350123
  • πŸ‡¨πŸ‡¦Canada gapple

    Updated the patch from #105 in a MR

    - Move allowedSlots property declaration to constructor; replaces mismatched default value on property (#106)
    - Added missing early return in set() (#113)
    - Override setMultiple() to only count() extra items once before removal (#108)
    - Code style fixes

  • Pipeline finished with Failed
    about 2 months ago
    Total: 615s
    #350139
  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    Just cross-referencing with another old issue: https://www.drupal.org/project/drupal/issues/1961934#comment-7274730 β†’

    Back when I raised that I was seeing a pattern of entity cache problems, and observed that a LRU approach was similarly problematic in the circumstances in question ("With LRU and limited memory, you can easily throw out everything you want to keep"), so I proposed a way of providing hints to the cache to enable it to be smarter.

    I haven't tried to catch up with what's currently happening in this issue, so apologies in advance if it's not relevant any more; but seeing as how there's current activity on this LRU caching issue, I thought I'd add a link in case it was still a going concern.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    @jweowu we're talking about static memory caches here so what other users are doing is not really relevant. A static memory cache is empty at the start of every request.

  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    Very true, "other users" are only relevant to the persistent caches (I was considering both in the other issue).

    I don't believe that renders the notion irrelevant, though. After all, no cache-expiry scheme has any advantage over any other if the cache doesn't fill up. We're only concerned about what happens when, for whatever reason, the cache fills up, and my suggestion is that giving the programmer some control over what should be flushed and what should be kept in those situations would be an improvement, and that a simple LRU approach isn't always going to be the best approach.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Would also add the original issue described for Drupal 7 was fixed for Drupal 8+ in this issue #1596472: Replace hard coded static cache of entities with cache backends β†’ - which is what allows this one to happen cleanly e.g. the entity static cache can already be reset independently of the persistent cache.

    Adding a reference to πŸ“Œ Add an entity iterator to load entities in chunks Needs work too which would help when iterating over a very large number of entities.

  • Pipeline finished with Failed
    15 days ago
    Total: 510s
    #388659
  • Pipeline finished with Success
    15 days ago
    Total: 407s
    #388762
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Pushing on this one again. The entity static cache filling up on long running jobs is an issue I run into quite often work around and then when I hit it again I get sad that we're get to fix this.

  • Pipeline finished with Success
    15 days ago
    Total: 411s
    #388796
  • Pipeline finished with Canceled
    14 days ago
    Total: 2295s
    #388815
  • Pipeline finished with Canceled
    14 days ago
    Total: 263s
    #388864
  • Pipeline finished with Canceled
    14 days ago
    Total: 125s
    #388867
  • Pipeline finished with Success
    14 days ago
    Total: 343s
    #388870
  • Pipeline finished with Canceled
    14 days ago
    Total: 425s
    #388894
  • Pipeline finished with Failed
    14 days ago
    Total: 444s
    #388897
  • Pipeline finished with Canceled
    14 days ago
    Total: 48936s
    #389068
  • Pipeline finished with Failed
    14 days ago
    Total: 452s
    #389617
  • πŸ‡¬πŸ‡§United Kingdom catch

    I opened this issue, but the last time I worked on the actual code here was 7 years ago, so I think I can RTBC it.

  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    I'll have a quick look myself to see if I can +1 this.

  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    Okay so the code will work, I really like the thorough test coverage and the fact that we uncovered MemoryCache itself didn't have a test extending GenericCacheBackendUnitTestBase yet. Only remark I have in general is that some methods are dedicated to numeric keys while other methods cover both string and numeric keys in one go. But don't let that detract from the overall excellent coverage.

    I found a tiny nit regarding the word pidgin and think the test could benefit from renaming the assertion and an extra comment or two. This is why I'm setting back to NW.

    The comment about moving the shared logic I'll leave up to you to decide, as MemoryBackend also seems to copy-paste things across methods occasionally. So I wouldn't be too bothered if we copy-paste a few lines of code here too. So feel free to RTBC without addressing this.

    Now that this introduces another memory cache, would it be prudent to add a line to MemoryCache and LruMemoryCache's docblocks to tell people they should declare these bins using the cache.bin.memory tag?

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I fixed 3/4 of the comments on the MR. There was one I didn;'t think needed doing.

    Re the cache.bin.memory tag - interestingly the entity.memory_cache does not use this tag and it's not really managed like other cache bins or memory cache bins. I think we should consider adding documentation about somewhere but I don't think it should be in this issue. I also think we could consider defaulting to a LRU cache for all memory caches.

  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    One final nit, but can be accepted upon commit.

    I agree that we should create a follow-up for checking if we have sufficient documentation re cache.bin.memory and why entity storages don't use it like that.

  • Pipeline finished with Canceled
    14 days ago
    Total: 438s
    #389850
  • Pipeline finished with Failed
    14 days ago
    Total: 394s
    #389868
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I tested this code on a long running batch process from the Entity Usage module - to build the entity_usage table from a large site 30,000+ nodes and 200000+ paragraphs using a module I made with this code - see https://www.drupal.org/project/entity_lru_cache β†’ - and it makes a massive difference. Memory no longer spirals to 90% of VM I'm running the job in and everything is a bit quicker too.

  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    Oh wow, that's great. We should consider making it the default then, no?

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    @kristiaanvandeneynde see πŸ“Œ Use LRU Cache for static entity cache Postponed

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    I'm running a migration that usually takes 40 hours ish.
    As a control I first am running it with attemptMemoryReclaim commented out on Drupal 11.1.0

    I will update periodically.
    It's several migrations chained
    11280/81350 8min 43s/1hr 2min 24MiB

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Discussed sizing a bit @catch and we agreed to remove the default as any code that uses a LRU cache should consider the number of slots for the use-case.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    Since my last comment I ran the migration on production with and without the LRU cache. Both took 39 hours.

    @alexpott mentioned this is a positive result since it's the same, this update will either improve or do nothing, my migration helps prove if it's not needed it does not make the situation worse.

  • πŸ‡·πŸ‡ΊRussia Chi

    Wonder if we could go with LFU cache instead. Will the implementation be more complex?

  • πŸ‡¬πŸ‡§United Kingdom catch

    LFU would be much more complex yes, you have to keep a count of how often an item has been accessed and then evict the least frequently accessed item. And the current implementation is being careful to add as little CPU overhead as possible.

    In general on a normal web request, we've set the limits high enough that items should never be evicted, so it will only kick in for migrations and similar tasks, or pages that load an extreme number of entities for some reason.

  • The Needs Review Queue Bot β†’ tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Fixed the PHPStan issue.

Production build 0.71.5 2024