Cache Raw Invalidate

Created on 21 November 2024, 6 months ago

>

Problem/Motivation

Action = Cache Raw: invalidate does not appear to invalidate provided cache tag.

Steps to reproduce

Create model (example attached) with:
- Event: Update Content Entity
- Action: Cache Raw Invalidate. Cache backend = render, Cache Key = entity_view (have also tried using "node" and other applicable keys), Cache tags = token based pattern.
Provided Cache key is not invalidated.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

πŸ› Bug report
Status

Active

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States eswiderski

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @eswiderski
  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    This looks more like the field "Cache tags" doesn't support tokens.

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

    @jurgenhaas I've actually tried with a fixed tag (without tokens, e.g. "node:77695") too, still no effect. Is there something else I'm doing wrong?

  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    I've done some debugging, and it looks as if something with the cache handling is broken in general. But I'm not a caching expert, and we need some more general research here.

    This particular issue is broken because the cache tags get processed through $tags = array_values(DataTransferObject::buildArrayFromUserInput($this->configuration['tags'])); and that build array from user inputs interprets the given tags as yaml which means that we lose everything before the colon. So, the cache tag being invalidated is just the ID, not the node: part.

    But there is more to it: if tags are given, then this plugin calls Cache::invalidateTags($tags); which goes through all invalidators and all bins to invalidate the given tags. I wonder why we then need the cache backend and cache key in the config? Those aren't used in this context.

    This whole set of raw cache handling (read, write, invalidate) seems to be not correct. Can we find someone who could review the situation and refactor the set of action plugins?

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

    @jurgenhaas I was wondering about the key and backend too, agree they seem unnecessary.

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

    @jurgenhaas as always, appreciate your tips, which helped to hack something up!
    I stripped out all of the references to cache backend and keys in CacheActionBase.php and CacheInvalidate.php. So they basically only operate with tags now. Then I switched the tags processor to a string:
    from:

      protected function getCacheTags(): array {
        $tags = [];
        if ($this->configuration['tags'] !== '') {
          $tags = array_values(DataTransferObject::buildArrayFromUserInput($this->configuration['tags']));
        }
        return $tags;
      }
    

    to:

      protected function getCacheTags(): ?string {
        $tags = ($this->configuration['tags']);
        $tags = trim($this->tokenService->replaceClear($this->configuration['tags'] ?? ''));
        return $tags;
      }
    

    I'm sure there's probably a better practice approach, but it's working for me. Thanks again!

  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    I think that Jurgen is correct in #4. When invoking the Cache Tags Invalidator Service, that's a high level API that does not need to know / care about which backend is used.

    All that needs to be passed to the service is an array of strings like node:## and the API should be able to do the rest.

  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    I've now created an MR which removes the back-end and key properties from the raw cache invalidate action plugin. It also introduced token support for the tags and explodes the tags that may be concatenated by commas.

    Please give it a try.

  • Pipeline finished with Canceled
    5 months ago
    Total: 284s
    #352932
  • Pipeline finished with Success
    5 months ago
    Total: 452s
    #352938
  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    @eswiderski, @luke.leber any chance for a review on this? I'd like to merge this before we publish the stable 2.1.0 release soon.

  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    I'll put it on my list to do tomorrow.

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

    Same here, will try to test asap. Thanks again @jurgenhaas

  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    I was able to successfully test the invalidation action.

    uuid: 46349853-db2b-4cfc-822c-9ac6680f2ff1
    langcode: en
    status: true
    dependencies:
      module:
        - eca_base
        - eca_cache
    id: process_jfh9njk
    modeller: bpmn_io
    label: noname
    version: ''
    weight: 0
    events:
      Event_1bq8hvb:
        plugin: 'eca_base:eca_custom'
        label: Event_1bq8hvb
        configuration:
          event_id: test
        successors:
          -
            id: Activity_087tmd6
            condition: ''
    conditions: {  }
    gateways: {  }
    actions:
      Activity_087tmd6:
        plugin: eca_raw_cache_invalidate
        label: Activity_087tmd6
        configuration:
          tags: test_tag
          backend: entity
          key: 'values:node:1'
        successors: {  }
    

    # Set a cache entry to persist for 5 minutes with a tag of "test_tag".
    vendor/bin/drush eval "\Drupal::cache()->set('test', 'value', time() + 300, ['test_tag']);" && echo 'Set a cache entry'

    # Confirm the cache entry is valid.
    vendor/bin/drush eval "echo 'The cache entry is...' . (\Drupal::cache()->get('test') ? 'valid' : 'invalid');"

    # Invalidate the tag via an ECA action.
    vendor/bin/drush eca:trigger:custom_event test && echo 'ECA invalidated the tag'

    # Confirm the cache entry is no longer valid.
    vendor/bin/drush eval "echo 'The cache entry is...' . (\Drupal::cache()->get('test') ? 'valid' : 'invalid');"

    Output should be:

    Set a cache entry
    The cache entry is...valid
    ECA invalidated the tag
    The cache entry is...invalid
    

    My only concern here is one of performance. If token substitution happens, but yields an empty string, does that mean that this kicks in?

    When empty, then the whole cache is being invalidated.

    For larger sites, full cache invalidations can lead to some pretty significant performance degradation, and in some cases denial of service if the ECA workflow runs often enough.

  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    My only concern here is one of performance. If token substitution happens, but yields an empty string, does that mean that this kicks in?

    When empty, then the whole cache is being invalidated.

    For larger sites, full cache invalidations can lead to some pretty significant performance degradation, and in some cases denial of service if the ECA workflow runs often enough.

    That's correct, but that's by design, I guess. Just like with other means that allow you to flush all caches. There are options in the UI as well as e.g. drush that deliberately provide that feature. So does ECA.

    With great power comes responsibility, doesn't it. ECA models should be created with care, so that flushing all caches doesn't happen unintentionally. I would expect that enough eyeballs are looking at such ECA models, like they would review custom code which could cause the same issue, especially on those large sites, where this really makes a difference.

    @luke.leber thank you so much for the detailed testing.

    Now wondering, if we should merge this bug fix or not. The fact that ECA could wipe all caches has been there from day 1.

  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    However! when creating a brand new action (rather than starting with an old one, then patching), I get [warning] Undefined array key "backend" CacheActionBase.php:80 printed out.

    I'm setting this back to NW. It looks like Drupal\eca_cache\Plugin\Action\CacheActionBase::getCacheBackend may need an update to cope with derivative classes that do not operate on a specific backend.

    As a result, I'm pretty sure that Drupal\eca_cache\Plugin\Action::execute needs to be refactored to do a full cache flush and not exit early via

        if (!($cache = $this->getCacheBackend())) {
          return;
        }
    

    That said, I'm not 100% sure what the right API calls should be in if tags are empty.

        if (empty($tags)) {
          $cache->invalidateAll(); <-- definitely not this, but I'm not sure what to replace it with!
        }
    
  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    Good catch. It looks like the access and execute callbacks need to be overridden in this action plugin. By doing so, I've also added a loop to go through all bins to invalidate all their tags, if they're empty.

  • Pipeline finished with Success
    5 months ago
    Total: 544s
    #363418
  • Pipeline finished with Success
    5 months ago
    Total: 635s
    #364272
  • Pipeline finished with Failed
    5 months ago
    Total: 766s
    #364279
  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    This all looks good to me! I re-ran

    # Set a cache entry to persist for 5 minutes with a tag of "test_tag".
    vendor/bin/drush eval "\Drupal::cache()->set('test', 'value', time() + 300, ['test_tag']);" && echo 'Set a cache entry'
    
    # Confirm the cache entry is valid.
    vendor/bin/drush eval "echo 'The cache entry is...' . (\Drupal::cache()->get('test') ? 'valid' : 'invalid');"
    
    # Invalidate the tag via an ECA action.
    vendor/bin/drush eca:trigger:custom_event test && echo 'ECA invalidated the tag'
    
    # Confirm the cache entry is no longer valid.
    vendor/bin/drush eval "echo 'The cache entry is...' . (\Drupal::cache()->get('test') ? 'valid' : 'invalid');"
    

    with...

    1. A single hard-coded tag
    2. A single tag that was resolved by a token
    3. No tags (invalidate everything)

    I think this one's good to go! Great stuff!

  • Pipeline finished with Skipped
    5 months ago
    #364423
  • Pipeline finished with Skipped
    5 months ago
    #364424
  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    Thank you so much for all the careful testing and the great feedback, really much appreciated.

  • πŸ‡©πŸ‡ͺGermany jurgenhaas Gottmadingen

    Merged and back ported to 2.0.x as well.

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

    Looks great on my end too! Boom!

  • Automatically closed - issue fixed for 2 weeks with no activity.

  • Pipeline finished with Failed
    3 months ago
    Total: 239s
    #416586
  • Pipeline finished with Failed
    3 months ago
    Total: 752s
    #416655
  • Pipeline finished with Failed
    3 months ago
    Total: 189s
    #416668
  • Pipeline finished with Failed
    3 months ago
    Total: 222s
    #416671
  • Pipeline finished with Success
    3 months ago
    Total: 1924s
    #416676
  • Pipeline finished with Success
    3 months ago
    Total: 336s
    #419754
Production build 0.71.5 2024