Problem/Motivation
Since #1605290: Enable entity render caching with cache tag support has landed (and the related #2099131: Use #pre_render pattern for entity render caching), we render cache entire rendered entities. We vary them by theme, user role and — if translated — by translation.
But we don't yet vary by time zone! This means that if a person in western Europe (CET) would have looked at a node, then the rendered time would say e.g. "17:00", but when a person from the US east coast would then look at it, the same time would be displayed, instead of "11:00".
Much thanks to Berdir for pointing this out!
Proposed resolution
- Add a
cache.context.timezone (TimeZoneCacheContext) service.
- Make
EntityViewBuilder::getBuildDefaults() use this.
Remaining tasks
Review.
User interface changes
None.
API changes
Added a cache.context.timezone (TimeZoneCacheContext) service.
Comments
Comment #1
wim leersComment #3
yched commentedNaive question :
This unconditionally splits render caches by timezones for all entities. In many cases (e.g teasers that do not display a date), that's overkill, and adds unneeded cache misses.
Is there a way this could be triggered only if a rendered formatter introduces timezone considerations in the displayed entity ?
Comment #4
wim leers#3:
I hoped for that question :)
First: teasers actually do show a timezone-dependent time, e.g. "Submitted by Anonymous (not verified) on Sat, 05/24/2014 - 17:04".
Second: yes, this should be added by a field formatter. But that's currently not yet possible, for two reasons:
So: yes, eventually that will be the case, but for now, we unfortunately have to do it like this for now. I added a
@todo.Comment #6
tstoecklerSince rendering a time in a timezone is very cheap, would this perhaps be a candidate for #post_render_cache? I.e. a placeholder with a Unix timestamp should suffice.
Comment #7
wim leers#6: No, because there are usually dozens of timestamps on the page. We should keep
#post_render_cachecallbacks only for those things are 1) not numerous, 2) would cause a quasi-limitless number of render cache items. There are only a few dozen possible timezones, therefore varying by timezone is most sensible.Comment #8
tstoecklerThanks for that explanation. Makes a lot of sense!
Comment #9
wim leersThe above patches trigger an infinite loop :)
When a page is requested, the user and session objects are constructed, and at the end of
SessionManager::initialize(),date_default_timezone_set(drupal_get_user_timezone())is called. Butdrupal_get_user_timezone()usesUserTimeZoneCacheContextin the above patches, which calls$this->user->isAuthenticated(), but the user object is not yet initialized, so it ends up going toSessionManager::initialize()again, etc.Plus, it was technically even wrong, because it's possible that another module called
date_default_timezone_set(), and then the above would've yielded wrong results.This new approach is simpler and should always be correct.
Comment #10
wim leersComment #12
znerol commentedTest:
Result:
I think that this modest performance penalty introduced by a
#post_render_cachepass is justifiable. Even more because there is still the option to offload this kind of processing to JavaScript.Comment #13
wim leersThis should be green.
#12: The biggest cost will not be the building of the date, but the fact that the date itself may be *themed* (invoking the theme layer). Plus, even if it were cheap, generating 1000 render cache placeholders and searching them in the dozens of kilobytes worth of HTML… that doesn't seem too appealing.
Standardizing on
#post_render_cacheto render every date that is timezone specific is counter to the reason it was added to Drupal core: for the one-offs that are not cacheable. Rendering per timezone is easily cacheable: the N remains low.Let us please not do that.
Comment #14
berdirAgreed. Also keep in mind that many sites will not have configurable user time zones and one more cache tag to check per page isn't going to hurt them much but if date rendering would be a post render cache callback, they would have to run all that additional complexity as well.
Comment #15
wim leersSo… RTBC? :)
Comment #16
moshe weitzman commentedI'm not particularly happy about cache misses going up by 23x for entities and anything else that uses this context. But I can't think of a better approach.
I know that my statement only applies to sites that allow user configurable timezones. Thats a small consolation.
Comment #17
wim leers#16: Indeed. Except that very, very, very few sites are actually visited by authenticated users from 24 different timezones (because this will also only affect authenticated users, anonymous users always get the site's timezone).
Note that *if* we were to do this in JS, then:
Therefore, let us please get this patch in now, so that at least what we have is correct. We can then worry about improving performance.
Comment #18
moshe weitzman commentedYes, javascript is out. I think the only alternative solution is #post_render_cache. I'm OK with committing this for now. Curious what catch and msonnabaum think.
Comment #19
catchI think I agree with Wim that in practice the cache won't be divided by an additional 24. Additionally peak traffic from particular timezones is likely to be concentrated at similar time periods during the day.
Could we avoid applying this if 'as time ago' formatting is being used though? That's not affected by timezone. Could be a follow-up if it's tricky.
Comment #20
wim leers#19: Yes, that would work, but we can't quite do that yet:
Comment #21
berdirAgreed, We can look into optimizing it but I'm not too worried about this, the amount of timezones of most sites is limited to one or a few and it's always the same for anonymous users.
Note that this adds a new "Time Zone" selection to the block caching settings, I guess that's OK and can't be avoided atm anyway.
Comment #22
wim leers#21: Correct, every request cache context is automatically exposed in a cacheable block's cache settings.
Thanks! :)
Comment #23
catchghttps.
Fixed on commit.
Comment #24
wim leersThanks!