Skip to content
This repository was archived by the owner on Sep 3, 2026. It is now read-only.

[WIP] Feature EZP-25696: HTTPCache Multi Tagging/labeling - #1772

Closed
andrerom wants to merge 28 commits into
masterfrom
feature-EZP-25696
Closed

andrerom wants to merge 28 commits into
masterfrom
feature-EZP-25696

Conversation

@andrerom

@andrerom andrerom commented Sep 8, 2016

Copy link
Copy Markdown
Contributor

Epic: https://jira.ez.no/browse/EZP-25696
Meta PR: ezsystems/ezplatform#143

As we have own layers for HttpCache it is possible to solve this without waiting for others.

TL;DR; Multi tagging for for all solving many edge cases, very simple multi tagging support added for Symfony HttpCache Proxy in our own cache store layer, meant to be replaced by using Symfony Cache component once we move to Symfony 3.x, and change to xkey header to align with change on Varnish side to use xkey vmod for better (more stable) performance.

Solves:

Performance:

  • Makes it possible to cache anything, example included is ContentType REST calls
  • Less work on purge, no need to iterate the content tree to calculate what needs to be cleared
  • Better Varnish performance by move to xkey over Banning to allow soft purging (grace handling)
  • With this we can set default cache TTL much higher, as it more reliably clears relevant cache now
  • Makes it possible to combine several responses more reliably in one response (needed for REST Embedding to speed up Platform UI by reducing round trips on thins like content > main location and more importantly for Permissions lookup REST API to not greatly slow down UI further)

Issues:

  • Solves lots of cache clearing issues, edge cases, especially when deleting content (hence possible to set higher TTL as written above)

BC

This change on purpose rather intrusive to HTTPCache layer, not all strictly needed to solve the issues, but overdue as it was somewhat jungle in there and cleanup highly needed as it has received changes over the years.

  • see doc/bc/changes-6.7.md
  • not mentioned yet, but as smart cache clearing won't come back in the form it current has the PR also deprecates and strips out use of GatewayCachePurger and implementations in favor of just the PurgeClientInterface layer.

Testing

See meta PR.

Todo

  • VCL, change to only have one: for Varnish 4.x (and 5.x as it has same syntax) with xkey installed
  • Fixes to FOSPurgeClient once VCL changes are in place to align
  • Fix inline todos
  • Remove the added RelationView stuff and instead document and adjust for setting such logic inline in templates when relation fields are viewed so changes on the relation goes back to self (the reverse relation) when need only

Future

  • (2.x): Refactor TagAwareStore to just simply use Symfony Cache component once 3.2 is out to have more solid tag handling, and built in expiry support natively to reintroduce staleness (however we need to look into how to avoid staleness for Platform UI requests as it will easily break the UI as mentioned in regards to Varnish above)

André R added 9 commits September 7, 2016 12:04

@bdunogier bdunogier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review, but some elements can be discussed already.

Comment thread doc/bc/changes-6.5.md Outdated
- `LOCATION_CACHE_DIR` and `LOCATION_STALE_CACHE_DIR` and related functionality is gone as tags are used instead
- See `doc/specifications/cache/multi_tagging.md` for further details.

* Smart cache clearing is currently disabled, and might come back in form where it generate tags to clear,

@bdunogier bdunogier Sep 14, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we list what will be affected by that ? Mostly thinking about clearing that used to be performed, and wouldn't anymore with these changes.

opposed to that it is done.

But to avoid stale cache issues caused by cache being refreshed before search index is or similar, it is now important
that HTTP Cache ttl *(default_ttl)* is not set to high, especially for REST endpoints used by UI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: s/to/too/.


$this->cacheManager->invalidate(array('X-Location-Id' => '^(' . implode('|', $locationIds) . ')$'));
// @todo: Need to take advantage of xkey if enabled to avoid BAN unless VCL handles this for us.
$this->cacheManager->invalidate(array('xkey' => '^(' . implode('|', $tags) . ')$'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short array syntax while at it ?

public function purgeAll()
{
$this->cacheManager->invalidate(array('X-Location-Id' => '.*'));
$this->cacheManager->invalidate(array('xkey' => '.*'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short array syntax ?


- `content-type-<content-type-id>`

*Tagging*: Used for tagging content responses with type id.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other response types, such as content type ? We don't currently cache responses from the content type controller, but if we have content-type tags, we can very do it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the PR's description gives ContentTypes as a first example of what could be cached thanks to this.

In that case, the HttpCache slots on ContentType service are missing for this.

*Clearing*: When a operation is specifically affecting parent location(s), on tree operations `path` is more relevant.

- `path-<path-location-id>`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs doc.

*Clearing*: When a operation is specifically affecting just given content.


- `content-type-<content-type-id>`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about content-type-group-<content-type-group-id> ? It would be useful for list of content types by group, to expire those when a new content-type is added to the group.

and the signals will need to be expanded to contain the relevant info depending on operation they correspond to.


### Predefined System Tags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add a tag for Roles as well ?

André R added 5 commits November 4, 2016 14:59
Conflicts:
	doc/bc/changes-6.5.md
	eZ/Publish/Core/REST/Server/Controller/Content.php
…llowup PR

With exception of header name and value change in CachedValue to make sure current tests passes.
André R added 4 commits November 11, 2016 18:23
Conflicts:
	eZ/Publish/Core/MVC/Symfony/Cache/Http/LocationAwareStore.php
	eZ/Publish/Core/MVC/Symfony/Cache/Http/SignalSlot/PurgeForContentHttpCacheSlot.php
	eZ/Publish/Core/MVC/Symfony/Cache/Tests/Http/LocationAwareStoreTest.php
/**
* Returns the Relations for the current entity being viewed.
*
* @return \eZ\Publish\API\Repository\Values\Content\Relation[]

@andrerom andrerom Nov 12, 2016

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bdunogier Note: I assumed you would prefer full object, but for tag needs I just need to keep id's around, and if we did that we could optimize the loading of relational data to only be one spi calls instead of 1+n (where n is number of relations). The design of this does not make a differance when cached, but it does make a differance in dev (either on cold cache or with cache disabled).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input from @bdunogier, instead of doing this no matter if relation is rendered or not we should use manual tagging in this case. there is a twig function by fos_http_cache for this.

For some reason host is not being applied, so this might be a workaround for something else going on here.
The exception given is: https://github.com/FriendsOfSymfony/FOSHttpCache/blob/1.4/src/ProxyClient/Varnish.php#L141-L145

@bdunogier bdunogier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Managed to dive a bit deeper into it.

Overall, I think it's good. I still have doubts in regards to tagging responses. As discussed face-to-face, we might be able to make that MUCH easier for developers by tracking API calls, and tagging the response based on API values that have been loaded.

But maybe it's too much for the time being ?

Do you want me to take care of the some of the todos, or did you intend to do it yourself ?


#### Other eZ Repository domains

Tags for other domains in the Repository also have tags, e.g.:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be only one "tag" in this sentence, methinks :)

@@ -1,255 +0,0 @@
<?php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC: could we leave the file where it is, and mark it as deprecated ? It doesn't cost anything as long as we handle removals for 7.0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if someone use it they won't get a clear error saying they need to adapt, they'll continue using that and it won't work as headers are different.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we can add deprecation warnings, can't we ?

They will for sure notice if they were using it and we remove it :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we can add deprecation warnings, can't we ?

we can, so you prefer that then, and that deprecation warning says class most likely won't work anymore.

@bdunogier bdunogier Nov 24, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the warning should say which class replaces it. The class works, it's just not used anymore. If somebody has a service that uses it, it will throw the warning during container compilation.

Unless you're really against it, I prefer it that way. Thank you :)


/**
* Injects a Filesystem instance
* For unit tests only.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe prefer if we removed this from the class, and handled this in unit tests:

  • either using reflection
  • or by making $fs protected and adding a test class that extends TagAwareStore and adds this method.

Just saw that the getCachePath method below is public for the same reason, but it could be solved if it was a dep, as suggested.

$this->fs = new Filesystem();
}

return $this->fs;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in why do we need getFilesystem()? I'll have to check, as many of your other questions it's inherited from the previous class.

@bdunogier bdunogier Jan 16, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question was actually why we needed the if (!isset())... new part.

The fact that it comes from the parent class is a good reason.

$digest = $response->headers->get('X-Content-Digest');
$tags = $response->headers->get('xkey', null, false);

if ($response->headers->has('X-Location-Id')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also remove the header ? Or is this handled somewhere else ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be logged as separate enhancement, it was not covered before, and parent Store class does not seem to strip anything either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright.

public function purgeAllContent()
{
$cacheTagsCacheDir = $this->getTagPath();
$this->getFilesystem()->remove($cacheTagsCacheDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is supposed to return bool. Since remove doesn't return anything, maybe we should catch exceptions it sends, and return true/false depending if something was caught ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to check against old class, might be we can adjust doc here.

}
}
$fs->remove($files);
// we let folder stay in case another process have just written new cache tags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return true.

// Flip the tag so we put id first so it gets sliced into folders.
// (otherwise we would easily reach inode limits on file system)
$tag = strrev($tag);
$path .= DIRECTORY_SEPARATOR . substr($tag, 0, 2) . DIRECTORY_SEPARATOR . substr($tag, 2, 2) . DIRECTORY_SEPARATOR . substr($tag, 4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be:

$path .= implode(
  DIRECTORY_SEPARATOR, 
  array_slice(str_split(strrev($tag), 2), 0, 4)
);

Not sure it's better, but it's different. Maybe easier to make configurable (folder length, number of folders).

Did you consider making this an external service ? It's an important responsibility, and it could be worth testing individually.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept it like this as this is how it is done upstream in Symfony for getPath(), I also find this more readable then the example you propose even if it indeed avoids the long line.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, no problem.

* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*
* @version //autogentag//

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

$this->fs = new Filesystem();
}

return $this->fs;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in why do we need getFilesystem()? I'll have to check, as many of your other questions it's inherited from the previous class.

public function purgeAllContent()
{
$cacheTagsCacheDir = $this->getTagPath();
$this->getFilesystem()->remove($cacheTagsCacheDir);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to check against old class, might be we can adjust doc here.

* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*
* @version //autogentag//

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*
* @version //autogentag//

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Examples:
- (123|456|789) => Purge locations #123, #456, #789.
- .* => Purge all locations.
- If "http" is used, only one Http PURGE request will be sent, with xkey header containing tags to purge seperated by space.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix, several request are done per key, as per what xkey supports.

@andrerom

andrerom commented Nov 24, 2016

Copy link
Copy Markdown
Contributor Author

Do you want me to take care of the some of the todos, or did you intend to do it yourself ?

@bdunogier Missed that comment in the reviews, I can take care about it today, but let's sync on it so we find a good fit for doc. BTW any tought on #1772 (comment)

@bdunogier

Copy link
Copy Markdown
Contributor

Replaced by http://github.com/ezsystems/ezplatform-http-cache. Please do not delete the branch yet.

@bdunogier bdunogier closed this Feb 8, 2017
@andrerom
andrerom deleted the feature-EZP-25696 branch February 8, 2017 18:15
@andrerom
andrerom restored the feature-EZP-25696 branch February 8, 2017 18:15
@andrerom
andrerom deleted the feature-EZP-25696 branch February 8, 2017 18:15
@andrerom
andrerom restored the feature-EZP-25696 branch February 8, 2017 18:15
@andrerom
andrerom deleted the feature-EZP-25696 branch April 20, 2017 19:45
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants