Skip to content

Magento_CacheInvalidate mis-handles very large tag patterns when doing a PURGE #26255

Closed
@moloughlin

Description

@moloughlin

Preconditions:
\Magento\CacheInvalidate\Model\PurgeCache::sendPurgeRequest is responsible for taking arbitrarily large tag patterns and chunking it into HTTP PURGE requests based on the maximum header length Varnish will accept.

Currently, \Magento\CacheInvalidate\Observer\InvalidateVarnishObserver::execute will join tags with '|' and call sendPurgeRequest. Those tags themselves may include '|' characters.

\Magento\CacheInvalidate\Model\PurgeCache::splitTags will then separate on '|' and re-join components until the length of those components reaches the max. header length. This is too naive, since it may now split an individual tag (one which itself included '|' characters) across two requests. Both of those requests now have an invalid pattern - this would mean that at the very least, some purges will be missed. In the worst-case scenario, I expect this could also lead to Varnish being completely purged, depending on the tag that was incorrectly split.

For the sake of brevity, the example below has been limited to a small subset of my real-world example; as such let's pretend the max header size is 256 bytes. Note that this is a typical pattern that would result from a product save event.

((^|,)cat_p_1468(,|$))|((^|,)cat_p_1361(,|$))|((^|,)cat_p_1473(,|$))|((^|,)cat_p_930(,|$))|((^|,)cat_p_933(,|$))|((^|,)cat_p_934(,|$))|((^|,)cat_p_664(,|$))|((^|,)cat_p_487(,|$))|((^|,)cat_p_490(,|$))|((^|,)cat_p_491(,|$))|((^|,)cat_p_153(,|$))|((^|,)cat_p_156(,|$))|((^|,)cat_p_157(,|$))|((^|,)cat_p_497(,|$))|((^|,)cat_p_498(,|$))|((^|,)cat_p_499(,|$))|((^|,)cat_p_227(,|$))|((^|,)cat_p_228(,|$))|((^|,)cat_p_229(,|$))|((^|,)cat_p_242(,|$))|((^|,)cat_p_244(,|$))|((^|,)cat_p_245(,|$))

Based on my desired max. header size, this will be chunked into two separate requests:

((^|,)cat_p_1468(,|$))|((^|,)cat_p_1361(,|$))|((^|,)cat_p_1473(,|$))|((^|,)cat_p_930(,|$))|((^|,)cat_p_933(,|$))|((^|,)cat_p_934(,|$))|((^|,)cat_p_664(,|$))|((^|,)cat_p_487(,|$))|((^|,)cat_p_490(,|$))|((^|,)cat_p_491(,|$))|((^|,)cat_p_153(,|$))|((^

,)cat_p_156(,|$))|((^|,)cat_p_157(,|$))|((^|,)cat_p_497(,|$))|((^|,)cat_p_498(,|$))|((^|,)cat_p_499(,|$))|((^|,)cat_p_227(,|$))|((^|,)cat_p_228(,|$))|((^|,)cat_p_229(,|$))|((^|,)cat_p_242(,|$))|((^|,)cat_p_244(,|$))|((^|,)cat_p_245(,|$))|((^|,)cat_p_245(,|$))

Expected split behaviour:
((^|,)cat_p_1468(,|$))|((^|,)cat_p_1361(,|$))|((^|,)cat_p_1473(,|$))|((^|,)cat_p_930(,|$))|((^|,)cat_p_933(,|$))|((^|,)cat_p_934(,|$))|((^|,)cat_p_664(,|$))|((^|,)cat_p_487(,|$))|((^|,)cat_p_490(,|$))|((^|,)cat_p_491(,|$))|((^|,)cat_p_153(,|$))

((^|,)cat_p_156(,|$))|((^|,)cat_p_157(,|$))|((^|,)cat_p_497(,|$))|((^|,)cat_p_498(,|$))|((^|,)cat_p_499(,|$))|((^|,)cat_p_227(,|$))|((^|,)cat_p_228(,|$))|((^|,)cat_p_229(,|$))|((^|,)cat_p_242(,|$))|((^|,)cat_p_244(,|$))|((^|,)cat_p_245(,|$))


Steps to reproduce:

  • Have full_page cache enabled
  • Have caching application configured to Varnish
  • Run code below
<?php

require __DIR__ . '/app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);

class DummyEntity implements \Magento\Framework\DataObject\IdentityInterface
{
    public function getIdentities()
    {
        $ids = range(807, 1193);
        return array_map(function ($id) {
            return 'cat_p_' . $id;
        }, $ids);
    }
}

$objectManager = $bootstrap->getObjectManager();
$appState = $objectManager->get('Magento\Framework\App\State');
$appState->setAreaCode('frontend');
$eventManager = $objectManager->get('\Magento\Framework\Event\Manager');

$myObject = new DummyEntity();

$eventManager->dispatch('clean_cache_by_tags', ['object' => $myObject]);

Actual Result:
Observe two resulting PURGE requests sent to Varnish with invalid patterns (e.g. xdebug_break inside \Magento\CacheInvalidate\Model\PurgeCache::sendPurgeRequestToServers and observe the X-Magento-Tags-Pattern header)

Expected Result:

Metadata

Metadata

Assignees

Labels

Component: CacheInvalidateFixed in 2.4.xThe issue has been fixed in 2.4-develop branchIssue: ConfirmedGate 3 Passed. Manual verification of the issue completed. Issue is confirmedIssue: Format is validGate 1 Passed. Automatic verification of issue format passedIssue: Ready for WorkGate 4. Acknowledged. Issue is added to backlog and ready for developmentPriority: P2A defect with this priority could have functionality issues which are not to expectations.Reproduced on 2.4.xThe issue has been reproduced on latest 2.4-develop branchSeverity: S1Affects critical data or functionality and forces users to employ a workaround.Triage: DoneHas been reviewed and prioritized during Triage with Product Managers

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions