Skip to content

[HttpKernel] PSR-6 HttpCache store #42295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.4
Choose a base branch
from

Conversation

Toflar
Copy link
Contributor

@Toflar Toflar commented Jul 27, 2021

Q A
Branch? 5.4
Bug fix? no
New feature? yes
Deprecations? no
Tickets -
License MIT
Doc PR -

As per @nicolas-grekas' request, here's a PR to merge my alternative PSR-6 HttpCache store into Symfony itself.
This is a simple copy & paste with a few namespace and test adjustments to get the discussion started for now.

Here are some additional thoughts:

Issues with the current StoreInterface / HttpCache

  • Does not support auto pruning which means it grows forever. This is has become an even bigger issue since the introduction of the CachingHttpClient which also leverages the HttpCache. So you also grow endlessly big cache directories for your external HTTP requests with Symfony's HttpClient.

  • Does not support cache tagging which is an extremely useful feature for reverse proxies.

  • Does some custom lock handling instead of relying on Symfony's Lock component.

  • Does not support caching BinaryFileResponses.

Issues with this PR, things to be clarified

  • It's not a 100% the same as my current implementation. Specifically, the current StoreInterface lacks the methods for the following three capabilities:

    • clear() to be able to flush a cache completely.
    • invalidateTags() to be able to invalidate certain cache items of a given set of cache tags only.
    • prune() to be able to prune expired cache items.
  • If you look at the things the Psr6Store has to do, it kind of becomes evident, that HttpCache currently delegates too many responsibilities to the StoreInterface. Vary handling and cache key generation e.g. should be the same for all stores.

  • The current CachingHttpClient relies on HttpCache as well and does not support greedy caching which is something that should be supported too, imho.

  • I think, we should rather replace the current HttpCache with an alternative and deprecate the current one together with the current StoreInterface. Imho that alternative could rely on the Symfony Cache abstraction directly. However, we should see if we can add the missing three capabilities to the Cache contracts as they are vital for a reverse proxy cache. We already have TagAwareCacheInterface for invalidateTags() but we're lacking prune() and clear() which would both make sense imho.

List of things to do:

  • Update changelog
  • Docs

@carsonbot carsonbot changed the title PSR-6 HttpCache store [HttpKernel] PSR-6 HttpCache store Jul 27, 2021
@derrabus derrabus added this to the 5.4 milestone Jul 27, 2021
Copy link
Contributor

@OskarStark OskarStark left a comment

Choose a reason for hiding this comment

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

I left some comments, great work, thank you!

private $locks = [];

/**
* When creating a Psr6Store you can configure a number options.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* When creating a Psr6Store you can configure a number options.
* When creating a Psr6Store you can configure a number of options.


/**
* When creating a Psr6Store you can configure a number options.
* See the README for a list of all available options and their description.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would rather remove this sentence and add the options to the Symfony docs

}

/**
* Invalidates all cache entries that match the request.
Copy link
Contributor

Choose a reason for hiding this comment

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

Sometimes you write Request, sometimes request, only one should be used

/**
* Releases the lock for the given Request.
*
* @param Request $request A Request instance
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we need this @param annotation 🤔

$response->headers->set('X-Content-Digest', $contentDigest);

// Make sure the content-length header is present
if (!$response->headers->has('Transfer-Encoding')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if it already has a Content-Length header? Do we always override it?

* @param int $expiresAfter
* @param array $tags
*/
private function saveDeferred(CacheItemInterface $item, $data, $expiresAfter = null, $tags = []): bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private function saveDeferred(CacheItemInterface $item, $data, $expiresAfter = null, $tags = []): bool
private function saveDeferred(CacheItemInterface $item, $data, int $expiresAfter = null, array $tags = []): bool

and remove the doc block?

use Symfony\Component\Lock\LockInterface;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;

class Psr6StoreTest extends TestCase
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class Psr6StoreTest extends TestCase
final class Psr6StoreTest extends TestCase

$this->store->cleanup();
}

public function testCustomCacheWithoutLockFactory(): void
Copy link
Contributor

Choose a reason for hiding this comment

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

No void return in test classes

Comment on lines +905 to +908
/**
* @param null $store
*/
private function getCache($store = null): TagAwareAdapterInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be more precise about the type here?

Comment on lines +821 to +822
// This test will fail if an exception is thrown, otherwise we mark it
// as passed.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be on one line

@OskarStark
Copy link
Contributor

I think it makes more sense to target 6.0 and introduce this new Store as experimental. Or is it not worth, because the API is already designed and proven?

$this->locks[$cacheKey] = $this->lockFactory
->createLock($cacheKey);

return $this->locks[$cacheKey]->acquire();
Copy link
Member

Choose a reason for hiding this comment

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

if lock is not acquired, should you unset this->locks[$cacheKey]?

one could retry to lock the resource several time until it works

@fabpot fabpot modified the milestones: 5.4, 6.1 Oct 29, 2021
@fabpot fabpot modified the milestones: 6.1, 6.2 May 20, 2022
@peter17
Copy link
Contributor

peter17 commented Jun 27, 2022

Any news on this topic? Anything I can do to help? Thanks

@fabpot
Copy link
Member

fabpot commented Jun 27, 2022

I'm very interested in this PR as well. I've thought a bit about the best path forward, but I'm not sure yet.
I will try to comment again soon about my thoughts.

@stof
Copy link
Member

stof commented Jun 27, 2022

  • The current CachingHttpClient relies on HttpCache as well and does not support greedy caching which is something that should be supported too, imho.

To me, the mistake there was reusing the HttpCache store for the CachingHttpClient while it was designed for a reverse proxy cache, which has slightly different needs.
We will probably need to separate them at some point (we used to have a ticket about that, but I think carsonbot closed it as stale).

@fabpot
Copy link
Member

fabpot commented Jun 27, 2022

I'm very aware of the issue @stof and this is part of my thoughts; I think I would like to start a new component that would include both types of proxies.

@nicolas-grekas nicolas-grekas modified the milestones: 6.2, 6.3 Nov 5, 2022
@nicolas-grekas nicolas-grekas modified the milestones: 6.3, 6.4 May 23, 2023
@nicolas-grekas nicolas-grekas modified the milestones: 6.4, 7.1 Nov 15, 2023
@xabbuh xabbuh modified the milestones: 7.1, 7.2 May 15, 2024
@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@fabpot fabpot modified the milestones: 7.3, 7.4 May 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants