Updated: Comment #71

Problem/Motivation

After #1597696: Consider whether HttpCache offers any significant benefit over the existing page cache has been postponed to 9.x, the page caching mechanism inherited from 7.x needs to be refactored into a container service.

As the current maintainer of Authenticated User Page Caching (Authcache) I have a distinct interest into preparing the Drupal 8 page cache such that it can be easily extended by contrib modules.

Proposed resolution

  1. Follow the proposition from #2023495-4: Make bootstrap three steps:

    Page cache should be a service that works only in index.php and not maintenance mode

  2. Extract three major components: Storage (record and deliver cached pages), Policy (allow/deny recording/delivery) and Vary / Version control (record/deliver different versions according to properties of the request, i.e. Accept-headers)

The scope of this issue is restricted to the conversion of procedural code into injected container services. It is especially important to maintain execution order, such that the risk of introducing performance regression is minimized. Therefore the delivery of cached pages is attempted immediately after the initialization of the kernel and before drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE) is called in drupal_handle_request.

As a consequence, there will be no page-caching for subrequests/fragments for the moment. Only main-requests delivered through index.php will be cacheable.

If we also succeed in properly splitting up responsibilities like outlined in point 2, this will help with several pending issues. E.g.:

#40 has a detailed description of the architecture implemented by the patch in #41.

Performance

In order to asses whether the chosen approach is feasible, two different entry points for page delivery from the cache have been benchmarked (see #45): a) Immediately before the kernel and DIC are initialized (pre-kernel) and b) immediately after the kernel and DIC are initialized. The benchmark reveals a performance regression of an order of an order of magnitude (post-kernel) and up to a factor of 5 (pre-kernel). Neither connecting to the storage backend (DB, Redis, Memcache) nor the client-server connection overhead is included in the measurement. When arbitrarily adding 40ms to the whole resultset, the regression drops to around 30% #47.

While post-kernel page caching leads to a performance regression, pre-kernel hurts usability (only configurable through settings.php and no more via performance admin screen). In IRC @catch states that dropping the "Use internal page cache" checkbox on the performance admin page is not an option. Therefore the goal here is to build a system where most components can be reused for both variants.

Security

In order to integrate the toolbar page cache (intended for authenticated users), it is necessary to remove the following hook from session.inc. Removal of this check is compensated by Drupal\Core\PageCache\PolicyRule/NoSessionOpen. This change however needs special attention from a security team member. It would be possible to remove toolbar page caching altogether if this change is unjustifiable. However that would also prevent contrib from using the new API for any other authenticated page caching.

--- a/core/includes/session.inc
+++ b/core/includes/session.inc
@@ -253,9 +253,6 @@ function drupal_session_initialize() {
     // anonymous users not use a session cookie unless something is stored in
     // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
     drupal_session_start();
-    if ($user->isAuthenticated() || !empty($_SESSION)) {
-      drupal_page_is_cacheable(FALSE);
-    }
   }
   else {
     // Set a session identifier for this request. This is necessary because

Remaining tasks

User interface changes

None

API changes

New services / classes:

page_cache_policy
Drupal\Core\PageCache\Policy (from factory method createDefault)
page_cache_veto
Drupal\Core\PageCache\Veto (from factory method createDefault)
page_cache_kill_switch
Drupal\Core\PageCache\VetoRule\KillSwitch (instance of Drupal\Core\PageCache\VetoRuleInterface, used to prevent that pages are recorded in the cache when e.g. messages are displayed to the user)
page_cache_response_subscriber
Drupal\Core\PageCache\CacheControlResponseSubscriber (responsible for making sure that appropriate cache-control headers are set on any response)
page_cache_cid_generator
Drupal\Core\PageCache\Storage\CidGenerator (responsible for computing the cache key according to a given request object.)
page_cache_storage
Drupal\Core\PageCache\Storage\InternalCache (responsible for recording / retrieving cached pages to / from the page cache bin.
toolbar.page_cache_policy.toolbar_path
Drupal\toolbar\PageCache\IsToolbarPath (responsible for allowing caching on the /toolbar/subtrees path, even for authenticated users)

Removed procedural code:

  1. Remove DRUPAL_BOOTSTRAP_PAGE_CACHE and _drupal_bootstrap_page_cache
  2. New namespace: Drupal\Core\PageCache.
  3. Replace drupal_page_cache_get_cid</li> with CidGenerator::getCid() (service <code>page_cache.cid_generator)
  4. Remove drupal_page_get_cache(), replaced with \D\C\PageCache\StorageInterface::get
  5. Remove drupal_page_set_cache(), replaced with \D\C\PageCache\StorageInterface::get
  6. Remove drupal_page_is_cacheable(), instead use \Drupal::service('page_cache_kill_switch')->trigger(); (in order to prevent a page from being recorded to the cache) or add a policy rule to the container and tag it according to your needs (one of page_cache_policy.allow, page_cache_policy.deny and page_cache_veto.rule).
  7. Replace drupal_serve_page_from_cache by \D\C\PageCache\CacheControlResponseSubscriber, service <code>page_cache_response_subscriber
CommentFileSizeAuthor
#88 2177461-fourth-generation-refactor-page-cache-88.patch79.7 KBznerol
#87 2177461-fourth-generation-refactor-page-cache-86.patch79.95 KBznerol
#86 2177461-fourth-generation-refactor-page-cache-85.patch79.22 KBznerol
#85 interdiff.txt12.71 KBznerol
#85 2177461-fourth-generation-refactor-page-cache-84.patch0 bytesznerol
#82 interdiff-renames.txt29.88 KBznerol
#82 interdiff.txt42.79 KBznerol
#82 2177461-fourth-generation-refactor-page-cache-82.patch79.44 KBznerol
#80 2177461-fourth-generation-refactor-page-cache-80.patch78.99 KBznerol
#71 2177461-fourth-generation-refactor-page-cache-71.patch79.69 KBznerol
#67 interdiff.txt3.78 KBznerol
#67 2177461-fourth-generation-refactor-page-cache-66.patch74.11 KBznerol
#64 interdiff.txt84.32 KBznerol
#64 2177461-fourth-generation-refactor-page-cache-64.patch74.21 KBznerol
#63 internal-cache-performance-ssd-hw.pdf11.21 KBznerol
#63 internal-cache-performance-crappy-hw.pdf11.72 KBznerol
#63 third-gen-test-results.tar_.gz603.12 KBznerol
#62 interdiff.txt41.5 KBznerol
#62 2177461-third-generation-refactor-page-cache-59.patch97.59 KBznerol
#58 2177461-third-generation-refactor-page-cache-57.patch98.24 KBznerol
#57 2177461-third-generation-refactor-page-cache-56.patch97.06 KBznerol
#56 2177461-third-generation-refactor-page-cache-55.patch95.33 KBznerol
#46 2177461-pre-vs-post-kernel-performance.tar_.gz64.29 KBznerol
#46 7x-no-db-do-not-test.diff989 bytesznerol
#45 8x-post-kernel-do-not-test.diff987 bytesznerol
#45 8x-pre-kernel-do-not-test.diff975 bytesznerol
#45 7x-no-db-do-not-test.diff987 bytesznerol
#45 internal-cache-performance-ssd-hw.pdf11.5 KBznerol
#45 internal-cache-performance-crappy-hw.pdf8.34 KBznerol
#45 2177461-pre-vs-post-kernel-performance.tar_.gz64.29 KBznerol
#28 interdiff-28.txt1.06 KBianthomas_uk
#28 2177461-refactor-page-cache-28.patch44.97 KBianthomas_uk
#25 2177461-refactor-page-cache-24.patch44.74 KBznerol
#23 2177461-refactor-page-cache-22.patch45.17 KBznerol
#22 interdiff.txt3.58 KBznerol
#22 2177461-refactor-page-cache-21.patch46.46 KBznerol
#19 interdiff.txt7.57 KBznerol
#19 2177461-refactor-page-cache-18.patch45.56 KBznerol
#17 interdiff.txt1.61 KBznerol
#17 2177461-refactor-page-cache-16.patch44.42 KBznerol
#15 2177461-refactor-cache-page-14.patch42.81 KBznerol
#11 2177461-refactor-cache-page-10-interdiff.txt12.8 KBznerol
#11 2177461-refactor-cache-page-10.patch42.6 KBznerol
#6 2177461-refactor-cache-page-5.patch39.57 KBznerol
#5 2177461-refactor-cache-page-4.patch39.59 KBznerol
#2 2177461-refactor-cache-page-2.patch39.48 KBznerol
#1 2177461-refactor-cache-page-2.patch1.13 KBznerol
#44 8x-head-simulated-pre-kernel-cache-hit-ab.log_.gz675 bytesznerol
#43 2177461-42-ap-test.tar_.gz1.43 KBznerol
#41 2177461-completely-refactor-page-cache-41.patch74.27 KBznerol
#39 2177461-completely-refactor-page-cache-38.patch74.53 KBznerol
#38 2177461-completely-refactor-page-cache-37.patch72.08 KBznerol
#36 2177461-completely-refactor-page-cache.patch69.98 KBznerol
#35 interdiff.txt3.46 KBznerol
#35 2177461-completely-refactor-page-cache-do-not-test.patch69.98 KBznerol
#34 2177461-completely-refactor-page-cache-do-not-test.txt68.32 KBznerol
#33 2177461-refactor-page-cache-33.patch47.29 KBznerol
#32 2177461-refactor-page-cache-32.patch44.76 KBianthomas_uk

Comments

znerol’s picture

Status: Active » Needs review
StatusFileSize
new1.13 KB

Ok, this is a bit nasty to take apart. At least #2168011: Remove all 7.x to 8.x update hooks and disallow updates from the previous major version will kill some of the weirder hacks around page caching.

I think this patch shouldn't do much more than moving around some blocks of code. Until now it moves the following stuff:

  • core/includes/cache.inc is gone. Moving the remaining functions cache() and cache_invalidate_tags() into core/includes/bootstrap.inc.
  • Add the following new interfaces, classes and services:
    • interface Drupal\Core\PageCache\PageCachePolicyInterface
    • class Drupal\Core\PageCache\DefaultPageCachePolicyInterface
    • interface Drupal\Core\PageCache\InternalPageCacheInterface
    • class Drupal\Core\PageCache\InternalPageCache
    • page_cache.policy: Drupal\Core\PageCache\DefaultPageCachePolicy
    • page_cache.internal: Drupal\Core\PageCache\InternalPageCache
  • Moved the following functions:
    • drupal_page_cache_get_cid(): InternalPageCache::getCid() (protected)
    • drupal_page_get_cache(): InternalPageCache::get() (protected)
    • drupal_page_is_cacheable(): PageCachePolicyInterface::isCacheable() (public)
    • drupal_serve_page_from_cache(): InternalPageCache::populateResponse() (protected)
    • _drupal_bootstrap_page_cache(): InternalPageCacheInterface::deliver() (public)
    • drupal_page_set_cache(): InternalPageCache::set() (protected)
  • I think that we can get rid of the page cache special-casing from drupal_rebuild() entirely
  • Some funny things go on in the toolbar module. It would be possible to circumvent the cache-policy replacement if there is a way to enable/disable policy rules on a per-request basis. However I'd rather explore this possibility in a followup.
znerol’s picture

StatusFileSize
new39.48 KB

Actual patch...

znerol’s picture

The last submitted patch, 1: 2177461-refactor-cache-page-2.patch, failed testing.

znerol’s picture

StatusFileSize
new39.59 KB

Actually it is necessary to ensure that database.inc gets included somewhere during bootstrap. Because the page cache phase does not exist anymore, lets move that to the variable phase.

I still have at least the Text editor administration test failing in spectacular way though.

znerol’s picture

StatusFileSize
new39.57 KB
znerol’s picture

I still have at least the Text editor administration test failing in spectacular way though.

Interestingly enough this is only on my machine, the test passes on simplytest.me.

The last submitted patch, 2: 2177461-refactor-cache-page-2.patch, failed testing.

The last submitted patch, 5: 2177461-refactor-cache-page-4.patch, failed testing.

The last submitted patch, 5: 2177461-refactor-cache-page-4.patch, failed testing.

znerol’s picture

Got around removing most static invocations and instead use injected services. Most notable changes since #6:

dawehner’s picture

Great work so far.

I tried to figure out whether this will let run more code or less code and indeed this does not change the behavior, as we still basically execute after the kernel is ready. This should be better documented in the issue summary to be honest.

znerol’s picture

Issue summary: View changes
znerol’s picture

Issue summary updated, I hope it is clearer now. I'd like to minimize the risk of introducing performance regressions, and therefore I very much would prefer to focus onto the conversion of procedural code into container services. The patch already moves a lot of code around.

znerol’s picture

Status: Needs review » Needs work

The last submitted patch, 15: 2177461-refactor-cache-page-14.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
Issue tags: -SprintWeekend2014 +#SprintWeekend2014
StatusFileSize
new44.42 KB
new1.61 KB

Scripts should bootstrap to DRUPAL_BOOTSTRAP_KERNEL, then include legacy API by hand (database.inc). Only care about authorize.php and statistics.php, but do not touch update.php because most of that crazy stuff will be removed anyway.

dawehner’s picture

Here is a bit more deep review.

  1. +++ b/core/core.services.yml
    @@ -66,6 +66,11 @@ services:
         arguments: [path]
    +  page_cache.internal:
    +    class: Drupal\Core\PageCache\InternalPageCache
    +    arguments: ['@cache.page', '@content_negotiation', '@config.factory', '@settings']
    +  page_cache.policy:
    +    class: Drupal\Core\PageCache\DefaultPageCachePolicy
    

    Initial thought:
    Just in general it is odd that we need two services to be injected in the finish response subscriber.

  2. +++ b/core/includes/bootstrap.inc
    @@ -1456,7 +1301,7 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
    +    \Drupal::service('page_cache.policy')->cancel();
    ...
         // Mark this page as being uncacheable.
    

    The new code is certainly less descriptive.

  3. +++ b/core/includes/bootstrap.inc
    @@ -1705,6 +1549,19 @@ function drupal_handle_request($test_only = FALSE) {
    +
    +  // Attempt to serve a cached response from the internal page cache.
    +  $use_internal_cache = \Drupal::config('system.performance')->get('cache.page.use_internal');
    +  if ($use_internal_cache && $cache_policy->isCacheable()) {
    +    $page_cache = \Drupal::service('page_cache.internal');
    +    if ($page_cache->deliver($request)) {
    +      exit();
    +    }
    

    Is it just me that the configuration should be hidden inside the page_cache.internal service? Maybe we should also find a better name.

  4. +++ b/core/includes/bootstrap.inc
    @@ -2670,6 +2480,57 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
    + * @} End of "defgroup lock".
    

    This certainly looks wrong.

  5. +++ b/core/includes/bootstrap.inc
    @@ -2670,6 +2480,57 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
    + * @defgroup cache Functions for cache handling
    

    Is it just me that moving this deprecated methods feels out of scope of this method? Can't we simply load cache.inc a little bit earlier?

  6. +++ b/core/includes/utility.inc
    @@ -45,15 +45,6 @@ function drupal_rebuild() {
     
    -  // Disable the page cache.
    -  drupal_page_is_cacheable(FALSE);
    -
    -  // Bootstrap up to where caches exist and clear them.
    -  drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
    -  foreach (Cache::getBins() as $bin) {
    -    $bin->deleteAll();
    -  }
    

    What happened with this functionality?

  7. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -22,18 +26,49 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
    +   * @var \Drupal\Core\Language\LanguageManager
    

    Note: we do have an interface for this now.

  8. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -22,18 +26,49 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
    +   * The PageCachePolicy object necessary to decide whether a page is cacheable.
    

    Maybe we find a better word for the pageCachePolicy object that this becomes basically obvious.

  9. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -22,18 +26,49 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
    +   * The InternalPageCache object used to store cached pages.
    ...
    +  protected $internalPageCache;
    

    So what about using PageCacheStorage instead?

  10. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    + * Definition of Drupal\Core\PageCache\DefaultPageCachePolicy
    

    The convention is to use "Contains \Drupal..."

  11. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    +/**
    + * Page caching service based on the internal cache.
    + */
    +class DefaultPageCachePolicy implements PageCachePolicyInterface {
    

    Both the name and the documentation should explain what this thing is doing.

  12. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    +  /**
    +   * Prevent that a page gets saved to the cache.
    +   */
    +  public function cancel() {
    +    $this->canceled = TRUE;
    +  }
    

    maybe we could use something like disableForThisRequest() or something like that?

  13. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    +  public function isCacheable() {
    ...
    +  public function isCanceled() {
    ...
    +  public function isExcluded() {
    

    I wonder whether we need all this public methods if just isCacheable is important at the end..

  14. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    +    $this->excludeOpenSession($request);
    ...
    +    $this->excludeUncacheableHTTPMethod($request);
    +    $this->excludeCLIRequest($request);
    

    Nice splitup!

  15. +++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
    @@ -0,0 +1,101 @@
    +    if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
    

    Aren't these two information stored on the request object as well? Request->getMethod() should do that.

  16. +++ b/core/modules/toolbar/lib/Drupal/toolbar/PageCache/ToolbarPageCachePolicy.php
    @@ -0,0 +1,23 @@
    +}
    ...
    +/**
    + * Page caching service based on the internal cache.
    + */
    +class ToolbarPageCachePolicy extends DefaultPageCachePolicy {
    +  /**
    +   * Do *not* exclude users with an open session.
    +   */
    +  protected function excludeOpenSession(Request $request) {
    +    // Intentionally left blank.
    +  }
    

    So just to be clear, this caches the response of a toolbar not something we consider as "page"?

  17. +++ b/core/modules/toolbar/toolbar.module
    @@ -114,28 +114,19 @@ function toolbar_element_info() {
    +  // Replace page cache policy with our own implementation.
    +  $toolbar_page_cache_policy = \Drupal::service('toolbar.page_cache.policy');
    +  \Drupal::getContainer()->set('page_cache.policy', $toolbar_page_cache_policy);
    

    Mhh it really feels wrong to dynamically exchange the service to be honest, though copying the page cache logic, as it was before, is certainly even worse. ... possible patterns we could use here would be observer (throwing an event on which multiple page cache policies can work on)

znerol’s picture

StatusFileSize
new45.56 KB
new7.57 KB

Removed another set of static invocations (especially calls to drupal_get_http_header and drupal_add_http_header).

The substitution of the $boot_headers with $response->headers in InternalPageCache::populateResponse probably requires further explanation. drupal_get_http_header returns all headers set during a request. When a page was delivered from the cache, the headers added during hook_boot are returned. Because hook_boot is gone, we can forget about that. There is still some code around calling the old API drupal_add_http_header on full page builds though. However headers set using the old way already get collected by FinishResponseSubscriber::onRespond and added to the response object. Therefore we can get rid of $boot_headers and use $response->headers instead.

@dawehner: Thanks for the review. The header-stuff took a bit longer than I hoped for. I will address your points in the next iteration.

Status: Needs review » Needs work

The last submitted patch, 19: 2177461-refactor-page-cache-18.patch, failed testing.

The last submitted patch, 19: 2177461-refactor-page-cache-18.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new46.46 KB
new3.58 KB
znerol’s picture

StatusFileSize
new45.17 KB

Addresses #18: 4, 5, 6:

  • Instead of moving around deprecated cache functions, include cache.inc where necessary.
  • Let's just leave the redundant cache-flushing in place in order to prevent side-effects. However removal of drupal_page_is_cacheable is safe, because the risk that a cached version is delivered during the bootstrap does not exist anymore.
    @@ -40,16 +40,10 @@ function drupal_rebuild() {
       restore_error_handler();
       restore_exception_handler();
     
    -  // drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL) will build a new kernel. This
    -  // comes before DRUPAL_BOOTSTRAP_PAGE_CACHE.
       PhpStorageFactory::get('service_container')->deleteAll();
       PhpStorageFactory::get('twig')->deleteAll();
     
    -  // Disable the page cache.
    -  drupal_page_is_cacheable(FALSE);
    -
    -  // Bootstrap up to where caches exist and clear them.
    -  drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
    +  drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
       foreach (Cache::getBins() as $bin) {
         $bin->deleteAll();
       }
    

Status: Needs review » Needs work

The last submitted patch, 23: 2177461-refactor-page-cache-22.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 25: 2177461-refactor-page-cache-24.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
ianthomas_uk’s picture

Issue tags: +@deprecated
StatusFileSize
new44.97 KB
new1.06 KB

Marking the old function names as deprecated as part of #2183187: [META] Remove or document all obsolete APIs before beta. I've also opened #2184985: Remove cache_invalidate_tags() and cache() to actually remove them, postponed on this.

Doing this I spotted that the use statement in cache.inc was incorrect. I'm surprised this wasn't picked up by tests. Hopefully that's because we are testing the new versions of the functions, rather than the legacy wrappers.

Status: Needs review » Needs work

The last submitted patch, 28: 2177461-refactor-page-cache-28.patch, failed testing.

The last submitted patch, 28: 2177461-refactor-page-cache-28.patch, failed testing.

znerol’s picture

+++ b/core/includes/cache.inc
@@ -5,7 +5,7 @@
-use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache;

Not really. Drupal\Core\Cache is the namespace and Drupal\Core\Cache\Cache is the class name.

ianthomas_uk’s picture

StatusFileSize
new44.76 KB

I clearly need to brush up on my namespaces. This just reverses the change I made to the use statement.

znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new47.29 KB

After #2168011: Remove all 7.x to 8.x update hooks and disallow updates from the previous major version it is now possible to remove DRUPAL_BOOTSTRAP_PAGE_CACHE entirely.

I'm still exploring ways on how to further improve the design. Especially I'd like to decouple the internal page cache from adding headers for external caches (proxies / browsers). However this might result in larger changes than desirable for this issue.

znerol’s picture

Ok, this is an intermediate update of what I have to this date in the branch with the radical approach. Regrettably I have some tests failing there because for example cache revalidation works a little different in symfony. I'd rather not touch the tests but instead preliminary mimic the behavior of the old implementation if possible.

This attempt has the following key characteristics:

  • Use event subscribers / dispatcher
  • Separation of internal page cache from external cache control.
  • Extracted gzip into separate service.
  • Removal of the toolbar page-cache hack, instead completely rely on the new architecture.
  • At this point I'd appreciate some comments an the architecture as well as class / interface naming.

    znerol’s picture

    This #34, however there is a remote chance that tests pass here... The interdiff demonstrates the changes necessary to work around the tests exercising legacy-behavior.

    znerol’s picture

    Status: Needs review » Needs work

    The last submitted patch, 36: 2177461-completely-refactor-page-cache.patch, failed testing.

    znerol’s picture

    Status: Needs work » Needs review
    StatusFileSize
    new72.08 KB

    Also extract cid generator as a separate service (this will help with #2062463: allow page cache cid to be alterable).

    znerol’s picture

    znerol’s picture

    Now that tests are passing I'd like to discuss the changes introduced by #39.

    Page cache service (event dispatcher) and global policy

    The page_cache service is an instance of PageCacheInterface. When allowed by the page_cache.policy, it is responsible for emitting PageCacheEvents before and after a request is handled by the kernel. Only requests coming in through the CLI or those neither having the GET nor the HEAD method are rejected by the GlobalPolicy. More restrictive policy rules are then assigned directly to cache backends and filters. For example only allow caching when no session is open implemented via DefaultPolicy.

    The PageCacheInterface::handle method largely corresponds to _drupal_bootstrap_page_cache while PageCacheInterface::record mostly incorporates code extracted from the FinishResponseSubscriber.

    The PageCache service is also responsible for adding default headers (Cache-Control: no-cache, ... and Expires: distant past) when caching is prohibited.

    When user-specific content is rendered on a page (e.g. messages), page caching can be disabled entirely by issuing \Drupal::service('page_cache.policy')->isNotCacheable(). This was drupal_page_is_cacheable(FALSE) before.

    Note that Etag for non-cacheable requests is gone (as suggested in #1573064: Remove unique ETag hack as it is no longer necessary). If this is not desired, it would be possible to reintroduce it in PageCache::setDefaultHeaders().

    Internal page caching based on the cache_page bin (event subscriber)

    The CacheStorage class listens to PageCacheEvents::GET and PageCacheEvents::SET events. The class largely implements the responsibilities of drupal_page_get_cache and drupal_page_set_cache. The page_cache.storage.cache uses the page_cache.policy.cache, an instance of CacheStoragePolicy. Because this policy derives from DefaultPolicy, only requests without an open session can be stored. Also the policy rules only evaluate to TRUE when internal cache is effectively turned on.

    In order to #2062463: allow page cache cid to be alterable, the cid-generation is implemented in a separate service, namely page_cache.cid_generator (CidGenerator:getCid formerly known as drupal_page_cache_get_cid).

    Also note that the HTTP status code is now saved along with the cache-entry.

    Page compression (event subscriber)

    The methods for compressing and conditionally decompressing response content found a new home in CompressFilter and UncompressFilter controlled by CompressFilterPolicy and UncompressFilterPolicy respectively. Instead of relying on the page_compressed flag of the cache-data object introduced with #1476810: (followup) drupal_serve_page_from_cache can serve uncompressed data with Content-Encoding gzip header with page_cache_without_database = 1, the standard HTTP header Content-Encoding is used to specify whether a cache-entry contains compressed content. This allows the separation of the internal cache service from page compression.

    Cache-Control headers for external caches (event subscriber)

    The ExternalStorage class is responsible for adding appropriate Cache-Control, Etag and Vary headers, as well as perform cache-revalidation (a.k.a respond with 304 Not Modified) when appropriate. The ExternalStoragePolicy also inherits from DefaultPolicy and therefore only allows caching for requests without a session. Additionally caching is only allowed when the site administrator configured a max_age.

    Custom cache backend implementation for toolbar (event subscriber)

    Requests directed to /toolbar/subtrees/{hash}<?code> are handled by a the module specific <code>ToolbarStorage page cache backend. When a request is accepted by the ToolbarPolicy, this backend adds custom Cache-Control and Expire headers. The policy explicitly does not reject requests from authenticated users and therefore page caching as well as external caching is enabled for all users. As a consequence, the hacky _toolbar_initialize_page_cache is gone completely.

    Outlook

    Note that due to the modular design, it is now possible to mix and match policies and backends. For example it would be possible to substitute the external backend and issue Cache-Control headers with different max_age depending on the status-code of the response. Also it is easier for contrib modules to provide their own page cache backends and policies.

    znerol’s picture

    znerol’s picture

    Regrettably the new architecture has an impact on page delivery performance. I took measurements with Apache Bench and it looks like the OO-architecture makes serving pages from the cache about 3ms slower. By using the APC loader, it is possible to compensate some 5ms.

    However what is worrying me more is the performance regression compared to Drupal 7. Especially due to the service container initialization serving pages from the internal cache takes twice as long in Drupal 8. The picture gets even worse when comparing to a tuned Drupal 7 setup with a high performance cache backend (e.g. redis) and $conf['cache_without_database'] = TRUE;. In this case Drupal 7 performs an order of magnitude better than Drupal 8.

    I think we should at least provide a way for third party implementations to perform cache delivery before the kernel / service container is initialized.

    Summary of test results
    
                           min  mean[+/-sd] median   max
    8x patch default loader 10   23   5.5     23      39
    8x patch APC loader      8   18   6.2     18      35
    8x head default loader   9   22   6.0     22      37
    8x head APC loader       8   15   6.9     13      30
    7x DB cache              3    9   2.1     10      16
    7x Redis (no DB)         0    1   0.3      1       3
    
    Results from #44
    8x head sim. pre kernel  2    7   1.8      8      11
    
    znerol’s picture

    StatusFileSize
    new1.43 KB

    Forgot to add ab-test results.

    znerol’s picture

    In order to complete the picture, I've taken another benchmark. This time with a fake response built and delivered just after bootstrapping to configuration and before the kernel gets initialized.

    znerol’s picture

    Edit: 7x-no-db-do-not-test.diff in #46 is the correct/tested patch.

    I took another benchmark with this explicit goal in mind: Gather the data necessary to decide whether pre-kernel/container page cache retrieval is worth considering.

    Pre-kernel page-caching is harder to implement and also much harder to configure. Backends operating at this level have no access to config, and therefore need to be configured via settings.php. Also cache-bins are not there at this early stage. Therefore page cache backends would be responsible for setting up connections to storage. Also when delivering pages from the cache, backends would be responsible for adding appropriate headers for external caches (proxies / browsers) to the response. However this problem could be solved by just saving Cache-Control headers along with cached pages. With this method it would be possible to preserve the decoupling of internal storage based backends and external caching.

    Benchmark method: Measure the time it takes to generate and send a fake-cache-hit-response (see attached patches) under the following conditions:

    Drupal 8 post-kernel
    After the kernel is created and the DIC is initialized:
    1. No-optimization (APC disabled)
    2. Optimized: APC enabled (no-stat), FileReadOnlyStorage for DIC, APC class loader
    Drupal 8 pre-kernel
    Before the kernel is created and the DIC is initialized.
    1. No-optimization (APC disabled)
    2. Optimized: APC enabled (no-stat), APC class loader
    Drupal 7
    page-cache without database
    1. No-optimization (APC disabled)
    2. Optimized: APC enabled

    The goal was to measure raw bootstrapping-performance without any database-overhead. Also in order to minimize connection overhead, HTTP keep-alive was used such that subsequent requests went through the same TCP session. I'm using ab in the following manner:

    ab -k -c1 -n500 -g /dev/shm/8x-pre-kernel-apc.csv hxxp://localhost:8080/8x-pre-kernel/node > /dev/shm/8x-pre-kernel-apc.log

    I did run the full test suite once on crappy hardware (Somewhat dated SOHO NAS with slow CPU und spinning disks in LVM RAID-1 configuration) and once on a recent laptop (with SSD).

    The results are as follows:

    1. Drupal 8 post-kernel/container page cache delivery is slower by a factor of 8 (crappy HW) through 14 (modern HW) - a.k.a one order of magnitude.
    2. By moving execution order and deliver paged caches before the kernel/container is initialized, it is possible to improve delivery performance by a factor of ~3. This will still result in a page delivery performance regression compared to Drupal 7 with a factor of 2.5 (crappy HW) through 5.0 (modern HW).
    3. APC/aggressive optimizations reduced delivery times by a factor of 3.5 (modern hardware) through 5 (crappy hardware).

    Drupal 7 was released 36 months ago. According to Moore's law CPUs are supposed to have 3 times as many transistors nowadays (and hence should be 3 times more powerful). Lets pretend that this is true and apply it to our results:

    1. 8/3=2.67 14/3=4.67: D8 post-kernel page delivery compared to D7 is still slower by a factor of 2 through 5 after applying Moore's law as of today.
    2. 2.5/3=0.834 5.0/3.0=1.67: D8 pre-kernel page delivery compared to D7 is faster by a factor of 1.2 or slower by a factor of 1.67, depending on hardware/system configuration. However this is only after applying More's law as of today.

    The important question is now:

    What is more important: Keep the "Use internal page cache" checkbox on the performance admin page or keep the page cache delivery performance of Drupal 7 (taking Moor's law into account).

    znerol’s picture

    StatusFileSize
    new989 bytes
    new64.29 KB
    ianthomas_uk’s picture

    I discussed the figures in #42 on irc and pointed out that my real-life D7 site takes around 40-80ms to serve a page from the cache, rather than the 9ms shown in the tests above.

    If we assume the network is adding 40ms to the page load time, then moving from 7x DB cache to 8x patch default loader would take the mean load time from 49ms to 63ms. That's still a 14ms regression, but it's now only about 30%.

    Max page throughput is likely to be greatly reduced, but if that's a concern then you should probably be using a dedicated cache such as varnish or a CDN rather than Drupal's page cache.

    znerol’s picture

    As per #47 and @catch on IRC ("we need to keep the checkbox IMO. This should be enabled by default in the standard profile."), pre-kernel cache delivery is out of the game.

    znerol’s picture

    Issue summary: View changes
    berdir’s picture

    Didn't we use to have basically both with the old no_database thing, that you could set in settings.php, before it connected to the database to load variables? Should be easy enough to keep that concept with a setting that wins when set otherwise fall back to a config?

    berdir’s picture

    Oh BTW, the weird test fails that you had locally have been fixed in #2182077: Editor manager in $form_state causes test failures on PHP 5.4

    znerol’s picture

    Issue summary: View changes
    znerol’s picture

    Issue summary: View changes
    znerol’s picture

    Re #51: I actually experimented with something like $settings['page_cache_pre_kernel'] = 'Drupal\Driver\PageCache\YourClassHere'. Like this it would be possible to provide a location where incoming page requests can be intercepted by third party code before the kernel and the DIC gets initialized. However this might spill over in the proposed architecture in quite ugly ways. Because the DIC is not available, it would be necessary to somehow initialize the cache-policy in advance and reinject it into the DIC on cache miss and pass. The other possibility would be to simplify the proposed architecture and lump everything including the cache-policy back into one class and try to make it inheritable, such that third-party implementors may reuse core logic by subclassing. I only want to go down that path if it is inevitable...

    znerol’s picture

    I've been busy rewriting the patch again during the last week. This time I'm trying to come up with an architecture which fits both use cases, i.e. pre-kernel and post-kernel (container based) cache retrieval. It is still a bit rough around the edges. Also I didn't have the chance to test the pre-kernel stuff yet.

    I plan to implement a simplified storage backend based on redis or memcache in order to gather some hard numbers on the new pre-kernel stuff. Not sure whether I have much time to work on that during the next week though.

    znerol’s picture

    StatusFileSize
    new97.06 KB

    This is another work-in-progress patch, adding a pre-kernel storage factory for database backed page cache. I will now concentrate on another performance analysis and after that on cleaning up the patch.

    When reviewing please concentrate your attention on the architecture - aka the big picture.

    znerol’s picture

    StatusFileSize
    new98.24 KB

    Reroll after #2209583: Remove use of drupal_set_title()/drupal_get_title()/_system_path from page cache, also separate pre-kernel and post-kernel page cache factories (FastPageCacheFactory vs PageCacheFactory).

    wim leers’s picture

    Issue tags: +Needs tests

    I came here via #2209583: Remove use of drupal_set_title()/drupal_get_title()/_system_path from page cache — didn't know about this at all!

    Great work so far, and thanks for your excellent issue summary and comments — that really helps when digesting a patch as big as this one! Also, big kudos for taking this on in the first place, and finally: thanks for cutting away so much cruft!

    General feedback

    1. I like DRY and small, easier-to-understand components as much as anyone, but it feels like we're adding far more services than is really necessary. I was already thinking that in the early iterations of this patch (yeah, I read the entire issue and looked at most patches along the way, to get a sense of evolution), but definitely in the latest patch in #57. If I didn't miscount, that's a grand total of eighteen new services! What is the performance cost of that? Do we have performance numbers that show the cost of adding more services?
    2. I said I really liked your earlier comments that explained the architecture. That's what I'm missing now, in the latest iteration. Could you provide a high-level explanation of how it all fits together? Usually, it's not that hard to figure out, but in this case, you're adding a few dozen new files, which makes it much harder.
    3. I'm still sorely missing additional test coverage — but that's fine, because you're still experimenting in the first place :)
    4. One thing that hasn't been mentioned at all (probably because it's so crazy): what if we switch to storing cached pages on disk instead of in DB? Sites that have multiple web heads (for which this wouldn't work probably) usually have (and probably should have) a custom caching strategy anyway. Then the smaller sites that are satisfied by Drupal's page cache can simply not hit Drupal at all!

    (Partial) code review

    1. +++ b/core/core.services.yml
      @@ -64,6 +64,63 @@ services:
      +  page_cache.public_external_cache:
      
      +++ b/core/includes/bootstrap.inc
      @@ -1579,19 +1413,36 @@ function drupal_handle_request($test_only = FALSE) {
      +  if ($page_cache_helper->deliverFromCacheBeforeKernel(settings(), $request)) {
      ...
      +  if ($page_cache_helper->deliverFromCacheAfterContainer(\Drupal::service('page_cache'), $request)) {
      

      "before kernel" vs. "after container"

      s/container/kernel/? :)

    2. +++ b/core/includes/bootstrap.inc
      @@ -1579,19 +1413,36 @@ function drupal_handle_request($test_only = FALSE) {
      +  $page_cache_helper = new PageCacheHelper();
      

      s/PageCacheHelper/PageCache/ would make the code cleaner.

    3. +++ b/core/includes/utility.inc
      @@ -41,16 +41,12 @@ function drupal_rebuild() {
         // Bootstrap up to where caches exist and clear them.
      -  drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
      +  drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
      

      This comment is now probably outdated.

    4. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
      @@ -22,17 +21,17 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
      -   * @var LanguageManager
      +   * @var \Drupal\Core\Language\LanguageManagerInterface
      

      Just an FYI: splitting these sorts of changes into separate issues would make this patch a bit smaller and easier to review.

    5. +++ b/core/lib/Drupal/Core/PageCache/CacheControl/PublicExternal.php
      @@ -0,0 +1,136 @@
      +   * Whether or not "Vary: Cookie" should be added to the response.
      

      'a "Vary: Cookie" header should be"

    6. +++ b/core/lib/Drupal/Core/PageCache/CacheControl/PublicExternal.php
      @@ -0,0 +1,136 @@
      +  public function setCacheable(Response $response, Request $request) {
      +    // Disable caching in ancient browsers and for HTTP/1.0 proxies and clients.
      +    if (!$response->headers->has('Expires')) {
      +      $this->setExpiresNoCache($response);
      +    }
      

      An override for setCacheable(), yet disabling caching in ancient browsers. Why?

    7. +++ b/core/lib/Drupal/Core/PageCache/CacheControl/PublicExternal.php
      @@ -0,0 +1,136 @@
      +    $max_age = !$request->cookies->has(session_name()) || $vary_customized ? $this->maxAge : 0;
      +    $response->setMaxAge($max_age);
      

      max-age should be limited to 1 year in case it's greater than one year, according to Google PageSpeed's docs, which say that's according to the RFC.

      It's known that (enormously) huge values confuse browsers, so better to be safe than sorry.

    8. +++ b/core/lib/Drupal/Core/PageCache/CacheControl/PublicExternal.php
      @@ -0,0 +1,136 @@
      +    // Entity tag should change if the output changes.
      

      s/Entity tag/ETag/

    9. +++ b/core/lib/Drupal/Core/PageCache/CacheControlBase.php
      @@ -0,0 +1,50 @@
      + * Defines an interface for Cache-Control header generators.
      ...
      +abstract class CacheControlBase implements CacheControlInterface {
      

      This is not the interface, but a base class.

    10. +++ b/core/lib/Drupal/Core/PageCache/CacheControlBase.php
      @@ -0,0 +1,50 @@
      +    // place. Therefore remove Etag and Last-Modified in that case.
      +    $response->setEtag(NULL);
      +    $response->setLastModified(NULL);
      +    $response->setVary(NULL);
      

      Apparently also Vary.

    11. +++ b/core/lib/Drupal/Core/PageCache/CacheControlBase.php
      @@ -0,0 +1,50 @@
      +  protected function setCacheControlNoCache(Response $response) {
      ...
      +  protected function setExpiresNoCache(Response $response) {
      

      I like this separation between HTTP 1.1 and 1.0. Very clear, very explicit. This really helps with long-term maintenance :)

    12. +++ b/core/lib/Drupal/Core/PageCache/CacheControlBase.php
      @@ -0,0 +1,50 @@
      +   * HTTP/1.0 proxies does not support the Vary header, so prevent any caching
      

      s/does/do/

    13. +++ b/core/lib/Drupal/Core/PageCache/CacheControlDefaultFactory.php
      @@ -0,0 +1,52 @@
      + * A cache control factory configured through config and settings.
      

      Additional comments here explaining which things are configurable through config and which are configurable through settings, would be most welcome here.

    14. +++ b/core/lib/Drupal/Core/PageCache/CacheControlInterface.php
      @@ -0,0 +1,36 @@
      + * Defines an interface for Cache-Control header generators.
      

      Im missing docs here that contain a justification to explain why Symfony\Component\HttpFoundation\HeaderBag is insufficient. That also has a addCacheControlDirective() method, plus siblings.

    15. +++ b/core/lib/Drupal/Core/PageCache/CacheControlInterface.php
      @@ -0,0 +1,36 @@
      +interface CacheControlInterface {
      +  /**
      ...
      +  public function setNotCacheable(Response $response, Request $request);
      +}
      

      We always have a newline after the opening of a class/interface and a newline before its closing.

      Please fix that here and elsewhere.

    16. +++ b/core/lib/Drupal/Core/PageCache/CacheControlInterface.php
      @@ -0,0 +1,36 @@
      +   * Add Cache-Control and Expire header to a cacheable response.
      ...
      +   * Add Cache-Control and Expire header for a response which is not cacheable.
      

      s/Expire/Expires/

    17. +++ b/core/lib/Drupal/Core/PageCache/CompoundPolicy.php
      @@ -0,0 +1,93 @@
      + * A policy implementation which can contain any number of rules.
      

      s/A policy/A page cache policy/

      ?

    18. +++ b/core/lib/Drupal/Core/PageCache/CompoundPolicyObserverInterface.php
      @@ -0,0 +1,23 @@
      + * Defines the interface for policy rule observers.
      ...
      +interface CompoundPolicyObserverInterface {
      

      Docs don't match.

    19. +++ b/core/lib/Drupal/Core/PageCache/CompoundPolicyObserverInterface.php
      @@ -0,0 +1,23 @@
      +   * Verify results produced by applying a set of policy rules.
      ...
      +   *   A list of policy rules applied.
      ...
      +  public function onCompoundPolicyResult(array $rules, $result);
      

      "set" vs. "list" vs. "compound"

      Let's make this consistent.

      Does order matter? -> "list"
      Does order not matter? -> "set"

    20. +++ b/core/lib/Drupal/Core/PageCache/DelegatePageCache/CacheControlSelectorInterface.php
      @@ -0,0 +1,26 @@
      + * Defines the interface for storage selector implementations.
      ...
      +interface CacheControlSelectorInterface {
      

      "storage selector" vs. "cache control selector"

      Confusing.

    21. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,183 @@
      +      $this->prepareCachePass($response, $request);
      

      Why "cache pass"? It's always been called "cache hit".

    22. +++ b/core/lib/Drupal/Core/PageCache/PolicyRule/DenyCommandLineOrUnsafeMethod.php
      @@ -0,0 +1,40 @@
      +class DenyCommandLineOrUnsafeMethod implements PolicyRuleInterface {
      
      +++ b/core/lib/Drupal/Core/PageCache/PolicyRule/DenyOpenSession.php
      @@ -0,0 +1,28 @@
      +class DenyOpenSession implements PolicyRuleInterface {
      
      +++ b/core/lib/Drupal/Core/PageCache/PolicyRule/InternalCacheConfig.php
      @@ -0,0 +1,42 @@
      +class InternalCacheConfig implements PolicyRuleInterface {
      

      I appreciate the elegance here.

      Maybe rename InternalCacheConfig to RequireInternalCacheConfig for consistency?

    23. +++ b/core/lib/Drupal/Core/PageCache/Storage/CidGeneratorInterface.php
      @@ -0,0 +1,26 @@
      +interface CidGeneratorInterface {
      

      Instead of returning a cache ID, you could also opt to return cache keys.

      Ideally, you'd leverage Drupal\Core\CacheableInterface in this patch.

    znerol’s picture

    Do we have performance numbers that show the cost of adding more services?

    @berdir told me that someone tested that once during Drupalcon Prague. He said that performance breaks down once the compiled container surpasses apc.max_file_size (or the equivalent limit of any other opcode cache in use).

    I can see two strategies to reduce the number of services (and remove some complexity) introduced in this patch:

    1. Add cache-control headers before saving pages to the internal cache. This would also simplify delivery of cached pages. Also in this case it would be possible to leverage the cache-control stuff already implemented in the Symfony HeaderBag
    2. Go back to anonymous-only page caching and consequently remove the ability to deliver cached versions of the toolbar tree for authenticated users. But still design the system such that contrib can fill this gap (e.g. Authcache).

    I'm still sorely missing additional test coverage

    I actually have some unit tests lying around in the branch for the second generation patch. However I discovered that unit-testing event-driven code (lacking proper interfaces) is no fun at all, and that's one of the reasons that motivated me to rewrite the patch again... I will sure be able to recycle some test-code.

    storing cached pages on disk instead of in DB

    This shouldn't be too much hassle with the current architecture. There is PageCache\StorageInterface and page_cache.internal_storage service which can be overridden. In fact I already have a minimal working Redis-implementation.

    Thanks for your review, I'll save the rest of it for the next train-ride.

    wim leers’s picture

    Add cache-control headers before saving pages to the internal cache. This would also simplify delivery of cached pages.

    I don't see why that can't happen? :)

    Also in this case it would be possible to leverage the cache-control stuff already implemented in the Symfony HeaderBag

    Excellent!

    storing cached pages on disk instead of in DB

    This shouldn't be too much hassle with the current architecture.

    Interesting. Would you say that's a valid alternative to circumvent the problems of "before vs. after kernel initialization"?

    znerol’s picture

    StatusFileSize
    new97.59 KB
    new41.5 KB

    Re #59 (Partial code review)

    1. "After kernel" is a bit ambiguous: The entry point really is after the kernel was construct AND the container is initialized. Instead of describing the entry point in the method name, it might be better to simply use generic terms: deliverPageFromFastCache, deliverPageFromCache.

    2. I would like to save the term PageCache for the proper service. PageCacheHelper is really a utility class responsible for keeping track of the fast-page-cache instance and such. The service itself inherits from PageCacheInterface which is completely unrelated to PageCacheHelper.

    3. In fact the comment still applies. Further down we loop over all cache bins and flush them.

    4. Sure. Filed #2212411: Code style cleanup of language system and #2212427: Use interface instead of class name for language manager where appropriate.

    5. Right

    6. Ok, let's just copy over the docs from the method definition.

    7. The highest value one could choose from the UI is 24 hours (86400 seconds). I'd rather limit the number in one of the factories instead of in the PublicExternal implementation. I added a sentence to the constructor parameter documentation for now.

    8. Right. I think our ETag-stuff is broken anyway. I rather would use a hash instead of the time stamp. According to the RFC 2616 (13.3.3 Weak and Strong Validators), ETag based on the timestamp with one-second resolution should be marked as weak-validator. We are claiming a strong-validator here. However I'd rather dig into that in a follow-up (or reuse #1573064: Remove unique ETag hack as it is no longer necessary). A change here will require a lot of research.

    9.-13. Done

    14. The justification is that Drupal 7 does it like this... I will try and remove the cache-control stuff in favor of Symfony HeaderBag later.

    15.-20. Fixed

    21. I borrowed this term from Varnish: pass = bypass (not miss, not hit).

    22. Great. I was trying to come up with common pairs (Allow/Deny, Accept/Reject, etc.) but that did not really work. Let's use Require and Deny as class-name prefix.

    23. The cid (cache-id) and Cache-Tags are two very different concepts. The only input we can use to generate the cid is the request object. Nothing else is known at the point in time when pages should be delivered from the cache. Therefore the CacheableInterface does not seem to be a good fit for the page cache.

    znerol’s picture

    This is a performance analysis of the third-generation-patch. Unlike in #40 the measurements were conducted with real cache backends (mysql database and redis). The rest of the setup was the same (hardware).

    The goals for this analysis were:

    1. Find an answer for #59: What is the performance impact of moving page caching into the DIC.
    2. Compare pre-kernel page caching as introduced by #56 to database-less page caching in 7x.

    DIC regression

    I found that moving the page caching stuff from procedural _drupal_bootstrap_page_cache into a couple of services results in a performance regression of 6-8% (on crappy hardware). On modern hardware, the new DIC-approach varied between 10% faster (APC not enabled) or 25% slower (APC enabled). The results for modern hardware seem a bit bogus, I probably need to redo this part of the test.

    Pre-Kernel page caching vs 7x page cache without database

    Pre-Kernel page caching is still slow compared to the page-cache without database in 7x. On crappy hardware the regression is between 38%-56% (with better results when APC is on). On modern hardware the numbers are even worse: 56%-79% (also with better results when APC is on).

    Before (7x) it was possible to boost page caching up to 6 times (crappy HW, no APC) when switching from DB page caching to Redis. With the third-generation patch an improvement of 2-3.5 is possible when moving from the post-kernel DB cache to pre-kernel Redis.

    znerol’s picture

    StatusFileSize
    new74.21 KB
    new84.32 KB

    This is the fourth generation. I'd like to highlight the following changes:

    • Removed 11 services compared to the 3rd generation patch, 7 services remain
    • Moved Cache-Control-stuff before cache-storage and thus simplify pre-kernel cache-delivery a lot
    • Introduced singleton \Drupal\Core\PageCache\Policy as the only thing required to be shared between pre-kernel and post-kernel page-cache implementations.
    • Leverage symfony ResponseHeaderBag cache-control stuff as much as possible

    This is more or less a complete rewrite - the interdiff is actually bigger than the diff...

    This is still wip, I hope to deliver more documentation / performance numbers later.

    Status: Needs review » Needs work

    The last submitted patch, 64: 2177461-fourth-generation-refactor-page-cache-64.patch, failed testing.

    dawehner’s picture

    I am sorry that you actually use matlab :P Impressive work in general.

    • Please use units on your y-axes. Assume these are milliseconds?
    • Have you made similar measurements for D8 without your patch, like the D7 results?
    • Did you also made some xhprof tests? Where are the main parts the code is slower. Is this mostly file loading due to the involved classes?
    • It would be great if you would link to your contrib module in the issue summary to explain why you have such many ideas
    1. +++ b/core/includes/bootstrap.inc
      @@ -1463,19 +1297,36 @@ function drupal_handle_request($test_only = FALSE) {
      +  if ($page_cache_helper->deliverFromCacheBeforeKernel(settings(), $request)) {
      +    return;
      +  }
      ...
      +  // Try to retrieve the page from the cache.
      +  if ($page_cache_helper->deliverFromCacheAfterContainer($request)) {
      

      What are your long-term goals here would both code paths merge one day, for example in a stacked http kernel? I guess we always need a pre-kernel page cache for performance?

    2. +++ b/core/includes/bootstrap.inc
      @@ -1463,19 +1297,36 @@ function drupal_handle_request($test_only = FALSE) {
      -  $response = $kernel->handle($request)->prepare($request)->send();
      +  $response = $kernel->handle($request);
      +
      +  // Save the rendered markup to the page cache and add relevant headers for
      +  // external caches like browsers and proxies if necessary.
      +  $page_cache_helper->recordAndDeliver($response, $request);
       
      

      tbh it is a bit odd that we kind of require the page cache to be part of our main tool chain. I wonder whether we should at least still deliver/send the result in this method.

    3. +++ b/core/includes/bootstrap.inc
      @@ -1599,8 +1450,8 @@ function _drupal_bootstrap_configuration() {
      +  # set_error_handler('_drupal_error_handler');
      

      Do you plan to change that?

    4. +++ b/core/includes/session.inc
      @@ -253,9 +253,6 @@ function drupal_session_initialize() {
      -    if ($user->isAuthenticated() || !empty($_SESSION)) {
      -      drupal_page_is_cacheable(FALSE);
      -    }
      
      +++ b/core/includes/utility.inc
      @@ -41,16 +41,12 @@ function drupal_rebuild() {
      -  // Disable the page cache.
      -  drupal_page_is_cacheable(FALSE);
      

      These once got coverted to vetos?

    5. +++ b/core/lib/Drupal/Core/PageCache/BuildPolicyPass.php
      @@ -0,0 +1,53 @@
      +
      +        $policydef->addMethodCall('deny', array(new Reference($id)));
      

      Have you tried to lazy-load the service references. Would that speed up the performance? We could for example introduce service proxies to achieve this goal.

    6. +++ b/core/lib/Drupal/Core/PageCache/BuildPolicyPass.php
      @@ -0,0 +1,53 @@
      +        if (!$reflection_class->implementsInterface('Drupal\Core\PageCache\PolicyRuleInterface')) {
      +          throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
      +        }
      

      +1 for this DX!

    7. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriber.php
      @@ -0,0 +1,89 @@
      + * Definition of Drupal\Core\PageCache\CacheControlResponseSubscriber.
      

      Ensure that all classes use "Contains ..."

    8. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,98 @@
      +/**
      + * Base class for page cache implementations.
      + */
      +class PageCacheBase implements PageCacheInterface {
      

      Is this really a base class?

    9. +++ b/core/lib/Drupal/Core/PageCache/Policy.php
      @@ -0,0 +1,106 @@
      +  /**
      +   * Return the singleton policy instance.
      +   *
      +   * If it does not exist, construct a new policy instance and populate it with
      +   * default rules.
      +   *
      +   * @return \Drupal\Core\PageCache\Policy
      +   *   The singleton policy instance.
      +   */
      +  public static function getSingleton() {
      +    if (!isset(static::$instance)) {
      +      $policy = new static();
      +      $policy->deny(new PolicyRule\CommandLineOrUnsafeMethod());
      +      $policy->allow(new PolicyRule\NoSessionOpen());
      +      static::$instance = $policy;
      +    }
      

      Do we really need a singleton here?

    znerol’s picture

    Issue summary: View changes
    Status: Needs work » Needs review
    StatusFileSize
    new74.11 KB
    new3.78 KB

    Thanks for the review dawehner.

    I am sorry that you actually use matlab :P [...]

    Actually its GNU octave, that doesn't make the language any better though. Measurements are in milliseconds, I will try to remember adding a label when performing the analysis the next time. Data series for Drupal 8 (without the patch) are labeled "8x DB" and "8x DB APC" on the graph in #63. The class loader and the service container are indeed the worst offenders performance-wise. The APC-series actually were conducted with $settings['class_loader'] = 'apc'; and Drupal\Component\PhpStorage\FileReadOnlyStorage for the service container.

    1. The dilemma is that with pre-kernel page caching is only configurable through settings.php. In order to make it easy to set up, we always wil need post-kernel (i.e. post-container) cache retrieval. I do not yet have an opinion on whether wrapping the kernel is a desirable goal and I'm not clear yet which consequences this would have on the proposed implementation.
    2. The reason is that HTTP revalidation (304 responses) should be issued after $response->prepare($request) (at least that's what Symfony HTTPCache::handle suggests). It would be possible to leave the $response->send()</li> in <code>drupal_handle_request.
    3. That actually explains why some completely unrelated tests blew up :)
    4. Both of them are not necessary anymore. There is a section in the issue summary about the change in session.inc. The cache exclusion in utility.inc is not necessary anymore because with the new architecture the page cache is only active when the request is handled using drupal_handle_request.
    5. The policy needs to be loaded completely on every request. Therefore we do not benefit of lazy loading here.
    6. Right
    7. Not anymore. I'm thinking of merging that one with PageCacheHelper like Wim suggested before, its only little left of it anyway.
    8. It is very important that we use the same policy regardless of whether pre-kernel or post-kernel page caching is active. I might try to use a syntetic service instead but that my conflict with the container builder pass. I need to check that.

    Note that the test fails from #64 also revealed #2218463: Private file and image style downloads throw 500 error when page cache is active. Setting to needs review to trigger a test run, still needs work...

    Crell’s picture

    Code review only:

    1. +++ b/core/lib/Drupal/Core/PageCache/BuildPolicyPass.php
      @@ -0,0 +1,53 @@
      +        $reflection_class = new \ReflectionClass($class);
      +        if (!$reflection_class->implementsInterface('Drupal\Core\PageCache\PolicyRuleInterface')) {
      +          throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
      +        }
      

      We don't have this added protection on any other tagged services. No need to do it here.

    2. +++ b/core/lib/Drupal/Core/PageCache/BuildPolicyPass.php
      @@ -0,0 +1,53 @@
      +        $reflection_class = new \ReflectionClass($class);
      +        if (!$reflection_class->implementsInterface('Drupal\Core\PageCache\PolicyRuleInterface')) {
      +          throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
      +        }
      

      Ibid.

    3. +++ b/core/lib/Drupal/Core/PageCache/FastPageCacheFactoryBase.php
      @@ -0,0 +1,49 @@
      +  /**
      +   * Return the global page cache policy.
      +   *
      +   * @return \Drupal\Core\PageCache\PolicyRuleInterface
      +   *   The global policy to be used in order to test whether caching is allowed.
      +   */
      +  protected function getPolicy(Settings $settings, Request $request) {
      +    return Policy::getSingleton();
      +  }
      

      Wait, what? this patch has a big OO framework to handle cache policy and then tosses in a global singleton? Does not compute.

    4. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,108 @@
      +  public function revalidate(Response $response, Request $request) {
      +    $last_modified = $response->getLastModified();
      +    if (!$last_modified) {
      +      return;
      +    }
      +    $created = $last_modified->getTimestamp();
      +
      +    // See if the client has provided the required HTTP headers.
      +    $if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
      +    $if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
      +
      +    if ($if_modified_since && $if_none_match
      +      && $if_none_match == $response->getEtag() // etag must match
      +      && $if_modified_since == $created) {  // if-modified-since must match
      +      $response->setStatusCode(304);
      +      $response->setContent(null);
      +
      +      // In the case of a 304 response, certain headers must be sent, and the
      +      // remaining may not (see RFC 2616, section 10.3.5).
      +      foreach (array_keys($response->headers->all()) as $name) {
      +        if (!in_array($name, array('content-location', 'expires', 'cache-control', 'vary'))) {
      +          $response->headers->remove($name);
      +        }
      +      }
      +    }
      +  }
      

      The response class already does most if not all of this in the prepare() method.

    5. +++ b/core/lib/Drupal/Core/PageCache/PolicyRule/CommandLineOrUnsafeMethod.php
      @@ -0,0 +1,37 @@
      +class CommandLineOrUnsafeMethod implements PolicyRuleInterface {
      

      The name of the class says explicitly that the class is doing too much.

    Crell’s picture

    znerol: I appreciate the work you're putting into this issue. Really. But I can't help but think that this is all horribly over-engineered.

    1) The baseline for page caching in 2014 is a reverse proxy. We should assume that anyone "serious" is using a reverse proxy (Varnish, nginx, etc.), and our PHP-space page caching is simply for those people who don't have one (but should).

    2) That means we *should not* offer more functionality and flexibility than can be represented by HTTP itself, because that's all the proxy will see.

    3) That means, effectively, writing a PHP reverse proxy.

    4) We already have a PHP reverse proxy in core. It's called HttpCache, and is linked from the issue summary. :-)

    5) That issue was bumped to Drupal 9 in June of 2013 on the grounds that Simpletest and the installer prevented us from using a wrapped Kernel, which is what HttpCache is. However, the work you're doing here is all pre-kernel... which means it could just as easily wrap the kernel. Meanwhile, Larowlan has made huge progress on #2016629: Refactor bootstrap to better utilize the kernel. (larwolan++!) It may be we can actually pull that off after all.

    6) All of that points to saying that we should revisit HttpCache, as it is *existing code that does what we need* that we're already even shipping, and it would be less work to use than writing our own mini-framework. In fact, the Response object already tracks nearly all of the data we need, and as noted above does the necessary header mangling for various response statuses already. That in turn means that all we need are a few response listeners to set the headers needed, and maybe a service or two to act as an API to the rest of the system, maybe. (Although for partial page caching purposes we really want to make caching automatic when it is practical rather than relying on module developers to get it right.)

    That is... I am really not at all convinced this is close to the right approach for us to take.

    znerol’s picture

    Thanks for the review @crell.

    Re #68.1: Actually I copied that over from somewhere. I do not care whether this code is in or not, #66 is pro #68 is con so its 1:1

    $ git grep InvalidArgumentException.*Service.*must.*implement
    core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php:        throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s
    core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTwigExtensionsPass.php:        throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s"
    core/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/RegisterListenersPass.php:                throw new \InvalidArgumentException(sprintf('Service 
    

    Re #68.3: Noted. We can get rid of that and replace it with a synthetic service. Reasons for my original choice are stated in #67.9.

    Re #68.4: Response::prepare alone does not cut it, we need to use Response::isNotModified(), like stated in the @todo. The reason because this legacy code is in there is that I do not intend to break existing tests during a refactoring and apparently Response::isNotModified() produces slightly different results than the legacy code.

    Re #68.5: Noted.

    Re #69.1: Agree absolutely. That's why I did put great effort into strictly separating the internal page cache from adding cache-control headers to the response.

    Re #69.2-#69.5: I follow. The problem with any pre-kernel page cache (and consequently wrapped kernel) is that we loose the ability to configure it through the GUI. @catch stated in IRC that this is not acceptable. I'm open to revisit this precondition.

    Re #69.6: I'm not opposed to that.

    znerol’s picture

    StatusFileSize
    new79.69 KB

    Another iteration... This time I've explored ways to wrap the kernel (to be precise the \Drupal\Core\DrupalKernel and not Drupal\Core\HttpKernel). This made it possible to get rid of the ugly PageCacheHelper and its static methods with funny names. It is possible to swap out the implementation via the page_cache setting. I still need to work on the factory, it looks a bit ugly at the moment.

    Rebuilding the page cache as a kernel wrapper also revealed that RequestCloseSubscriber is currently broken: #2220687: Convert system_run_automated_cron into a subscriber for KernelEvents::TERMINATE.

    Also I was able to get rid of the policy-singleton by splitting it into policy-service and veto-service. The policy is applied before attempting to retrieve a response from the cache while veto is applied before a response is recorded in the cache. Veto must be respected by all possible page level cache implementations (internal, external). For example it can be used to prevent that a page with messages on it is stored. However, because veto only needs to be present at a late stage in the request it can live in the container. This makes it possible that any cache-backend or request-subscriber can use its own policy and therefore it is not necessary anymore to inject the policy via singleton pattern or synthetic service into the container.

    Crell’s picture

    +++ b/core/lib/Drupal/Core/PageCache/PageCacheFactoryInterface.php
    @@ -0,0 +1,28 @@
    +interface PageCacheFactoryInterface {
    +
    +  /**
    +   * Construct an appropriate page cache implementation for the given settings.
    +   *
    +   * @param \Drupal\Core\DrupalKernelInterface $kernel
    +   *   The drual kernel instance to wrap.
    +   * @param \Drupal\Component\Utility\Settings $settings
    +   *   The settings instance.
    +   */
    +  public static function createFromSettings(DrupalKernelInterface $kernel, Settings $settings);
    +
    +}
    

    There's no reason for this to be static. A for-reals object should be the default. If you want a static single-method factory, don't bother with a separate class for that.

    That said, holy crap you managed to wrap another HttpKernelInterface around DrupalKernel! :-) Please humor me... Can you do that with HttpCache? It's *already there*, so if we can use it directly we should. (With all the usual caveats of "you really should use varnish instead of all of this crap.")

    znerol’s picture

    @Crell: My understanding is that the HttpCache is supposed to wrap the HttpKernel. In contrast the patch in #71 wraps DrupalKernel. It is possible that I'm completely wrong about that, but I think that the more interesting things implemented by HttpCache (like e.g. ESI) only work properly when all requests (including sub-requests) are passing through it (?). #71 does not work that way, only the main request is passed through the cache, it does not see any sub-requests.

    znerol’s picture

    Issue summary: View changes

    Update issue summary reflecting #71

    znerol’s picture

    Issue summary: View changes
    ParisLiakos’s picture

    The architecture looks straight forward to me. thanks for the great issue summary @znerol

    Tbh, i think i agree with Crell here, but i havent check HttpCache much yet, so i wont comment on that yet

    Here is a code review. There are docs missing in several places but i show the @todos so i didnt bother

    1. +++ b/core/lib/Drupal/Core/PageCache/BuildPolicyPass.php
      @@ -0,0 +1,53 @@
      +   * Implements CompilerPassInterface::process().
      
      +++ b/core/lib/Drupal/Core/PageCache/BuildVetoPass.php
      @@ -0,0 +1,41 @@
      +   * Implements CompilerPassInterface::process().
      

      should be {@inheritdoc}

    2. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriberBase.php
      @@ -0,0 +1,193 @@
      +  public function setMaxAge($max_age) {
      +    $this->maxAge;
      

      you probably forgot to assign the passed variable to the property

    3. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,188 @@
      +    $this->status &= $this->getPolicy()->apply($request);
      ...
      +    $this->status &= !$this->getVeto()->apply($response, $request);
      

      It is not clear to me as to why the reference is needed here?

    4. +++ b/core/lib/Drupal/Core/PageCache/PolicyRule/CommandLineOrUnsafeMethod.php
      @@ -0,0 +1,37 @@
      +  protected function isCLIRequest(Request $request) {
      

      per our coding standards it should be isCliRequest

    znerol’s picture

    Thanks for the review. Committed 1,2,4 to my local repository, will be part of the next reroll.

    Re #76.3: This is actually not a reference assignment but a bitwise-and operation ($x &= $y is equivalent with $x = $x & $y). It could be rewritten into a longer form, if this is too confusing (e.g. if (!$y) { $x = FALSE }).

    ParisLiakos’s picture

    oh, thanks, sorry, i am blind, totally misread this

    Crell’s picture

    HttpCache can wrap anything that implements HttpKernelInterface; that includes HttpKernel, Symfony's KernelInterface, or DrupalKernel. That's the point.

    HttpKernel subrequests never leave HttpKernel, so the wrapper (or lack thereof) doesn't matter. ESI-esque requests, from the POV of the DrupalKernel, are the same regardless of whether they come from a wrapping kernel or from Varnish/nginx. Remember, the goal is that midrange to high-level sites will be using a real reverse proxy anyway, so using only what HTTP supports; we just need a fallback for sites that don't have that option available.

    znerol’s picture

    Chasing HEAD, also contains suggestions by #76.

    wim leers’s picture

    Status: Needs review » Needs work

    Awesome work! This is significantly simpler already, which was the major concern for several people in earlier iterations AFAICT.

    I think the policy vs. veto concept is almost sufficiently clear. Almost, becasue maybe the naming isn't: AFAICT policies are for requests and vetoes are for responses? If this is the case, this should be made much more explicit.

    Then there is PageCache vs. FastPageCache. The differences there also aren't clear enough. AFAICT FastPageCache is for the pre-kernel case, and PageCache for the post-kernel case?

    1. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriber.php
      @@ -0,0 +1,91 @@
      +class CacheControlResponseSubscriber extends CacheControlResponseSubscriberBase {
      

      This response subscriber contains "default Drupal assumptions". Maybe we should rename it to DefaultCacheControlResponseScruber?

    2. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriber.php
      @@ -0,0 +1,91 @@
      +    // @todo: Let's get rid of that mechanism and instead instruct site
      +    // builders and module authors to swap out
      +    // page_cache.cache_control_response_subscriber when custom Vary headers
      +    // are required.
      

      Agreed! I didn't even know we supported this right now.

    3. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriber.php
      @@ -0,0 +1,91 @@
      +    $max_age = !$request->cookies->has(session_name()) || $vary_customized ? $this->getMaxAge() : 0;
      

      Can we have parentheses here to remove any ambiguity? :)

    4. +++ b/core/lib/Drupal/Core/PageCache/CacheControlResponseSubscriberBase.php
      @@ -0,0 +1,193 @@
      +   * Do not specify a value greater than one year (31536000 seconds).
      

      Hurray! :) Maybe refer to the RFC, to explain why there is this seemingly arbitrary limit?

    5. +++ b/core/lib/Drupal/Core/PageCache/FastPageCache.php
      @@ -0,0 +1,55 @@
      +class FastPageCache extends PageCacheBase {
      
      +++ b/core/lib/Drupal/Core/PageCache/PageCache.php
      @@ -0,0 +1,43 @@
      +class PageCache extends PageCacheBase {
      

      The docs for these are identical, though they serve very different purposes. The differences should be more explicit.

    6. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,194 @@
      +    // The RequestCloseSubscriber currently breaks on lean requests because it
      

      What are "lean requests"?

    7. +++ b/core/lib/Drupal/Core/PageCache/PageCacheBase.php
      @@ -0,0 +1,194 @@
      +    if ($if_modified_since && $if_none_match
      +      && $if_none_match == $response->getEtag() // etag must match
      +      && $if_modified_since == $created) {  // if-modified-since must match
      

      This implies a 304 will only be sent if both an If-Modified-Since and If-None-Match are sent. That seems wrong.

    8. +++ b/core/lib/Drupal/Core/PageCache/Policy.php
      @@ -0,0 +1,94 @@
      +  public static function createDefault() {
      

      It feels weird/wrong to have this (useful) helper hardcoded in here. I think a DefaultPolicy extends Policy might be cleaner?

    9. +++ b/core/lib/Drupal/Core/PageCache/Storage/CidGenerator.php
      @@ -0,0 +1,46 @@
      +class CidGenerator implements CidGeneratorInterface {
      

      Can you explain the value of having the page cache ID generator in a separate interface and class? Why not make it part of the thing that uses it?

    10. +++ b/core/modules/toolbar/lib/Drupal/toolbar/Controller/ToolbarController.php
      @@ -23,10 +23,22 @@ class ToolbarController extends ControllerBase {
      +    $max_age = 365 * 24 * 60;
      

      This is the number of hours in a year, not the number of seconds :)

      But this was apparently already the case in the old code!

    (Apologies if the review is off, I'm still wrecked from Drupal Dev Days Szeged.)

    znerol’s picture

    StatusFileSize
    new79.44 KB
    new42.79 KB
    new29.88 KB

    Thank you for the review Wim.

    I think the policy vs. veto concept is almost sufficiently clear. Almost, becasue maybe the naming isn't: AFAICT policies are for requests and vetoes are for responses? If this is the case, this should be made much more explicit.

    OK, renaming Policy to RequestPolicy and Veto to ResponsePolicy. Also CacheControlResponseSubscriber to DefaultCacheControlResponseSubscriber. Interdiff-renames.txt is produced with git diff -M25% in order to keep it reviewable.

    znerol’s picture

    Status: Needs work » Needs review
    znerol’s picture

    Addressing rest of the review from #81:

    1. Done
    2. Done
    3. I was not able to find this in the RFC. Do you have a trustworthy source for that?
    4. Docs added
    5. This was a hack to work around #2220687: Convert system_run_automated_cron into a subscriber for KernelEvents::TERMINATE. Not necessary anymore.
    6. This is the old Drupal 7 logic. I'd like to replace it with \Symfony\Component\HttpFoundation\Response::isNotModified() only in a followup because that will involve changing existing tests also.
    7. Extracted to DefaultRequestPolicy<code> and <code>DefaultResponsePolicy.
    8. There are a couple of use-cases where module authors or site-builders will want to replace the cid generation logic. For example in order to support delivering different versions of a page depending on the user agent (mobile vs. desktop, silly example but still...), see #2062463: allow page cache cid to be alterable.
    9. Fixed
    znerol’s picture

    znerol’s picture

    znerol’s picture

    Status: Needs review » Postponed
    Parent issue: » #2257695: [meta] Modernize the page cache

    I'm tired of constantly rerolling this, let's split this up: #2257695: [meta] Modernize the page cache

    mgifford’s picture

    znerol’s picture

    alimac’s picture

    Issue tags: -#SprintWeekend2014 +SprintWeekend2014

    Minor tag cleanup - please ignore.

    mgifford’s picture

    Lots of progress has been made on this meta issue #2257695: [meta] Modernize the page cache - should this one still be marked postponed?

    znerol’s picture

    Status: Postponed » Closed (duplicate)

    Yes, the other issue brought in everything.

    mile23’s picture

    related: #2549041: Remove deprecated _drupal_set_preferred_header_name()

    Ugh. Added to the wrong issue. But still related. :-)