Patternkit blocks cause unintentional page cache invalidations on edit and save

Created on 3 February 2023, almost 2 years ago
Updated 15 March 2023, almost 2 years ago

Problem/Motivation

When a Patternkit block is rendered on a page, it attaches cache tags for the associated block entity and the pattern entity being referenced. This is intentional and correct since it describes the content being rendered and allows for it to be targeted when changes affecting it should invalidate associated cache entries. The issue comes in instead with these related entities being saved at the wrong time causing a premature invalidation of all associated cache entries.

The current implementation of the Patternkit blocks saves these associated entities whenever the block form is submitted. This save triggers Drupal's default entity behaviors to invalidate the cache tags for those entities immediately. When used in Layout Builder, and likely other scenarios, this can result in the cache tags for a pattern and/or block being invalidated during the layout editing process before the changes to the layout are ever published. Since the pattern entity is also updated, any other pages that may render different blocks based on the same pattern entity would have their cache entries invalidated as well despite the specific revision they reference not being affected.

As an example, suppose two Nodes exist. Each is using Layout Builder and has an overridden layout set for it containing the Patternkit Example pattern. At this point the rendered cache entries for each node contain cache tags for the separate Patternkit Block entities and the same Pattern entity.

Assume now an editor edits the layout for Node 1, edits the content for the Patternkit Example block, and submits the form. They have not saved the layout yet, so no changes to the rendered content on the published Node would be visible yet. However, once the block form was submitted, a new revision of the Patternkit Block entity was created and saved which triggered invalidation of the rendered output of Node 1. The Patternkit Example Pattern entity was also saved despite there being no changes to reflect which results in the cache entries for both the rendered content of Node 1 and Node 2 being invalidated since both contain a block using that pattern.

Ideally, in this scenario, no rendered output caches should have been invalidated until the complete layout was saved. At this point, only the rendered content for Node 1 should have been invalidated since there were no changes to the Pattern entity that should cause the content for Node 2 to change.

Steps to reproduce

These steps are intended to test the initial setup for the test scenarios and confirm cache tags are represented as expected after initial content creation.

  • Install the following modules: Patternkit, Patternkit Example, Layout Builder
  • Enable cache header output
  • Enable Layout Builder with page-specific overrides on a content type
  • Create and save a new page (Page 1)
  • Edit the layout for this page and place a new "[Patternkit] Example" block
  • Save the layout
  • In an incognito window, navigate to Page 1 and view the cache headers:
    • Expect to see a cache miss and the following cache tags included:
      • "pattern:@patternkit/atoms/example/src/example"
      • "patternkit_block:1" (The number may vary if this is not a fresh installation.)
      • "patternkit_pattern:1" (The number may vary if this is not a fresh installation.)
  • In the original window, create and save a new page (Page 2)
  • Edit the layout for this page and place a new "[Patternkit] Example" block, saving the layout
  • In an incognito window, navigate to Page 2 and inspect the cache headers:
    • Expect to see a cache miss and the following cache tags included:
      • "pattern:@patternkit/atoms/example/src/example"
      • "patternkit_block:2" (The number may vary if this is not a fresh installation.)
      • "patternkit_pattern:1" (The number may vary if this is not a fresh installation.)

These steps are intended to verify the cache invalidation behavior during typical block editing processes.

  • Edit the layout for Page 1
  • Edit the example block already on the page
  • Change some field content and submit the block form (Do not save the layout yet)
    • At this point, the changed content should be visible on the layout preview screen.
  • In an incognito window, view the published version of Page 1:
    • Expect to see the unedited block content and a cache hit for the page
  • In the original tab, save the updated block layout.
    • Expect to see the changed content on the published view of Page 1
    • Expect to see a cache miss for the page
  • In an incognito window, view the published view of Page 2:
    • Expect to see a cache hit for the page

These steps should cover validation of caching behaviors when the underlying Patterns for these blocks are updated. Unfortunately, triggering the update process for a Pattern within a block isn't possible through the UI, so testing this only through the browser isn't currently possible. The hash for the pattern may be edited directly in the database which would enable the update process through the UI if this is an option in the testing environment. The following steps assume this or a similar change has been implemented to trigger the detection of an available pattern update for the "[Patternkit] Example" pattern.

  • As an editor, edit the layout for Page 1
  • Edit the example block placed on the page previously
  • Expect to see a prompt to update the block to the latest version
  • In a separate tab, edit the layout and block for Page 2
  • Expect to see a prompt to update the block to the latest version
  • In the original tab for Page 1, click the "Update pattern" button
  • Submit the block form
  • In a separate tab, view Page 1
  • Expect to see a cache hit
  • In the same tab, view Page 2
  • Expect to see a cache hit
  • In the original tab, save the layout for Page 1
  • Expect to see a cache miss for Page 1
  • View the published view of Page 2
  • Expect to see a cache hit
  • Edit the layout and example block for Page 2
  • Expect to see a prompt to update the pattern

Proposed resolution

Mimic the update and save solution used by Inline Blocks provided by the Layout Builder module to serialize content updates in blocks and defer entity updates (and related cache invalidations) until the layout and entity are saved.

Remaining tasks

User interface changes

None.

API changes

  • Patternkit Block config schema now includes a block_serialized string value to capture unsaved content changes to blocks
  • Pattern name cache tags (like pattern:@patternkit/atoms/example/src/example) are only invalidated now when an existing pattern is updated and marked as the default revision
  • Updates to patternkit blocks in Layout Builder are saved in the new serialized_block value until a layout is saved instead of being saved immediately when a block form is submitted
  • New entity insert and update hooks use the new PatternkitBlockEntityOperations service to save patternkit block and pattern entity updates when an entity using a layout containing them is saved
  • When a Pattern entity is updated through the block edit form, a new revision of it is saved and referenced from the updated block configuration. However, this new revision is not marked as the default revision until the layout is saved at which time the PatternkitBlockEntityOperations service resaves the Pattern entity as the default revision triggering invalidation of the related Pattern entity cache tags.

Data model changes

  • Patternkit Block config schema now includes a block_serialized string value to capture unsaved content changes to blocks
🐛 Bug report
Status

Fixed

Version

9.1

Component

Module Core

Created by

🇺🇸United States slucero Arkansas

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

Comments & Activities

Not all content is available!

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

  • Issue created by @slucero
  • @slucero opened merge request.
  • Issue was unassigned.
  • Status changed to Needs review almost 2 years ago
  • 🇺🇸United States slucero Arkansas
  • 🇺🇸United States slucero Arkansas

    It got rather complex trying to separate out the different types of cache tags and what their desired behavior should be, but this is what I settled on (obviously open to feedback).

    Types of cache tags

    • Block data entities (contains pattern configuration values): patternkit_block:123
    • Pattern data entities (contains versions of the pattern schema, template, etc.): patternkit_pattern:456
      • These target the specific entity ID of the pattern entity, and I've made efforts to move everything toward only one pattern entity per pattern definition moving forward, but I think there's some historical behavior that'll have multiple entities for the same pattern in the database for existing sites. This should get resolved over time with the new work allowing a clean-up in the future.
    • Pattern type (the pattern identifier itself that should cover anywhere the pattern is used even if multiple entities of it exist in the database): pattern:@patternkit/example/src/example
      • I kept these in to help with allowing invalidations targeting anywhere the assets (js/css) for a given pattern may have been changed and need to be updated.

    Intended behavior

    With the different types of cache tags and their intentions clarified, we can tackle the invalidation behavior a bit more clearly.

    • Whenever a patternkit block is created or updated, the cache tag for that specific entity ID (patternkit_block:123) should be invalidated, but only once the layout itself is actually saved. Unless a block is marked as reusable and placed on multiple pages, this shouldn't cause invalidations anywhere else.
    • Whenever a new pattern is used for the first time, an entity for that pattern has to be created when the block is first saved. This shouldn't trigger either until the layout is saved, but it will trigger invalidations for both the pattern entity ID (patternkit_pattern:456) and the pattern name (pattern:@patternkit/example/src/example), neither of which should have records to invalidate if this is the first usage.
    • After a pattern has already been used, the user should never be prompted to update it and the tags for either the pattern entity ID or the pattern name should not be invalidated as long as the files loaded from the filesystem for it never change causing the hash to differ. Assuming that's the case, updates to existing blocks and even the creation of new blocks using this pattern should never cause invalidation of either the pattern entity ID or pattern name.
    • If the files for a pattern are changed, an available update should be detected and a user is prompted to update the pattern on all block edit forms using that pattern. Clicking the update button should save and create a new revision for that pattern entity, but it should not invalidate caches for either the pattern ID or pattern name cache tags yet. Once the layout is saved, cache tags for the patternkit block, the pattern entity, and pattern name should all be invalidated. This will cause invalidations to hit other nodes where that same pattern may be used even if they aren't targeting the new revision yet, but without changing all of the pattern entity cache tags to target revision ID instead of entity ID there's not much way around it.

    TL;DR

    • Block entity ID tags should be invalidated when a layout is saved after creating or updating a new patternkit block
    • Pattern entity ID tags and pattern name tags should be invalidated when a layout is saved after first using a new pattern or updating the pattern to use changed files on the filesystem
  • 🇮🇳India kunalkursija Mumbai

    I have tested the patch and the patternkit_block:BLOCK-ID cache tags are now getting invalidated only when a new node revision is created. This is how Drupal core is operating as well with custom-blocks. Things are looking good.

    +1 on getting this merged in.

  • 🇺🇸United States slucero Arkansas
  • 🇺🇸United States slucero Arkansas
  • 🇮🇳India minsharm India

    Retested the changes and it is working as expected now.

    Steps:

    1) Install the modules: Patternkit, Patternkit Example, Layout Builder
    2) Enable cache header output by enabling http.response.debug_cacheability_headers as true in core.services.yml file
    3) Enable Layout Builder with page-specific overrides on a content type
    4) Create and save a new page (Page 1)
    5) Edit the layout for this page and place a new "[Patternkit] Example" block
    6) Save the layout and view the page
    7) View the cache headers for page 1
    Results : Expect to see a cache miss

    8) In the original window, create and save a new page (Page 2)
    9) Edit the layout for this page and place a new "[Patternkit] Example" block, saving the layout
    10) View the cache headers for Page 2:
    Results : Expect to see a cache miss

    Block Editing

    11) Edit the layout for Page 1
    12) Edit the example block already on the page
    13) Change some field content and submit the block form (Do not save the layout yet)
    Results : At this point, the changed content should be visible on the layout preview screen.
    view the published version of Page 1:
    Results: Expect to see the unedited block content and a cache hit for the page

    14) In the original tab, save the updated block layout.
    Results :

    Expect to see the changed content on the published view of Page 1
    Expect to see a cache miss for the page.
    15) View the published view of Page 2:
    Results: Expect to see a cache hit for the page

    Attaching Screenshots.

  • Status changed to RTBC almost 2 years ago
  • 🇺🇸United States slucero Arkansas
    • slucero committed 039c49e4 on 9.1.x
      Issue #3339014 by slucero, minsharm, kunalkursija: Patternkit blocks...
  • Status changed to Fixed almost 2 years ago
  • 🇺🇸United States slucero Arkansas

    Merging for inclusion in the Beta 7 release.

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

Production build 0.71.5 2024