 OK, so recording is the first slide. Awesome. For the recording, let's go. OK, so my name is Cristiano Hernando. I'm a senior Drupal developer at Factorial. I live in Belgium. I'm the maintainer of the group module. I'm happily married. Since about a year and a half, I've been the proud dad of a daughter. And I'm ruled by my cats. I'm also a highly sensitive person. I like to pass here for a second. This is something I always mention during my talks. Is anyone else here in the room highly sensitive, by the way? Raise your hand, please. Sucks, doesn't it? So it basically means that the whole world is overwhelming, like your senses are continuously on edge, which leads to a fucking of anxiety. Literally, you're always anxious. So I may get a panic attack during this session, or I preventulate, or whatever. I'll try not to, but I can't make any promises. So I always like to mention this, because it makes me feel a bit safer that the crowd knows this. Also, before we get started, I'm actually not a caching guru. I just spend a lot of time reading the course caching layer. So yeah, don't get your hopes up if you have any questions regarding varnish or something. That's not me. I just know the core side of things. So moving on. What is the goal of this session? The goal of this session is basically to inform you about new, or quote unquote, new features in Drupal Core. They may have been around for a couple months already, but still relatively new compared to when Drupal 8 first came out. I'm going to provide you with some tips on how to improve your code using this new technology and give you a sneak peek of what's to come in the near future, or almost near future. We're going to start easy with some really simple tips. And then we're going to end with massive confusion of people questioning what is still real. Like, honestly, there's some very exciting stuff at the end, but hopefully it will all make sense. So let's start with the memory cache. I'm pretty sure most people in the room might have seen the change record for the memory cache, but just to quickly go over it. What is it? It's a new cache. Again, air quotes. It's derived from the existing static cache. For those of you who've written tests for Drupal Core before, the static cache was one of those in-memory caches that we used to quickly test the behavior of caching without having to set up a database back end. So this basically mimics the functionality of either static properties or properties on a service, because services are usually only instantiated ones. So they're properties, kind of, Akmik static properties. This cache does perform better than the regular static cache, because it's a regular static cache used to mimic the database cache. And it does so by serializing everything that goes into it and serializing everything that goes out of it, so that if you request something from the database, it's a new object. The static cache used to do the same thing. Now, the memory cache doesn't. It's basically a channel of the existing static cache, but it removes that serialization so that it's way faster. And it supports cache stacks like any other cache. So that's a big thing that we'll discuss later on. You can read the full discussion about the introduction of this cache on that URL. You don't need to take pictures. My slides will be online. The links are clickable, so please just enjoy the session while it lasts. Although it's rather poorly named, there is an issue about that too. It's called the entity memory cache, but you can basically use this for any in-memory cache. You don't just need to use it for entities. And it's not implemented as a proper cache by default by the entity API. Just to show you, is this readable? Yeah, it is cool. So it's defined just as a service, but nothing else. So this means that in the entity classes, you'll notice that they still need to manually invalidate their cache tags and everything. JSON API, for instance, ran into that. They had this issue on Drupal.org. Turns out, as some of you may know, you need to tag your cache pin so that the cache tag invalidated will pick it up and then all of that automagically cool stuff happens when you try to clear a cache tag. So here, and it also doesn't have a cache factory. That's not necessarily a bad thing. You don't need one, but most of the caches in core do have one. And it's useful in some cases, where you can just define the bin that you're looking for, ask the factory for that bin. So I would have preferred if it had one, it doesn't. So this is how the original static cache was defined, and you can see that it's a lot more verbose. But yeah, so there is still some work to be done there, but it's functional, it's there. So when do you use it? If something inexpensive is being calculated often, and you'll see that core has already solved this problem with this pattern, you see this a lot in core. Just take a quick second to look at it. We set like a property on a service. If it's null, then we try to calculate it if we have it, we start returning it from that point on. Now there's a problem with that. What if outside code needs to clear this cache? Or what if the calculation that's being done depends on something? You wanna add those cache tags. Now yeah, static property doesn't really know much, doesn't really do much. So you end up seeing a lot of these classes that use this pattern defining a clear cache method or reset cache or reset index or whatever. Like it's littered all over core, and this really sucks because then any instance that might need to clear that cache needs to manually think of the fact that that cache needs to be cleared to implement that code and it's just sloppy. So let's stop using this, please. Oh, by the way, this is like the first icon I found when I typed cross. I just wanted to have a cross across it, but I found this to be cool one. So yeah, har har mateys or something, whatever. So this is how you use the same pattern with that memory cache. So basically you define your cache ID and then if it's in the cache, you return that. If it's not, you set it. Also, this is just a simple example so that it would fit on screen. Around these parts, you would set your cache tags if it depended on something that requires cache tags, but it's an advanced station, so I do expect people know how to use a cache back in Tindra.core. So yeah, there is an advanced use case you can have for this as well. So what if you have expensive calculations? So not just something simple, but something that is actually really expensive to calculate. So after the request, you still want to have that data. So then you can use this pattern. So you combine the memory cache with a regular cache, which persistently stores it. And then as you can see, you first look for your memory cache. If it doesn't have it, you look in your persistent storage. If that has it, you return it, but you set it in the memory cache. If none of them have it, you do your expensive calculation and store it in both. This is a very cool pattern, but don't do it. Like it's weird that I say, but don't do it because we actually have something really cool in core that does this for you. So what we have is we have the backend chain. And the backend chain behaves completely the same way like API-wise. It implements the cache backend interface as well. But it allows you to specify any number of cache backends in a very specific order. So it's important to have this order right. And it will do exactly what I showed you here. So it does that for you. So you don't need to write any code there. And then when you try to access your, in this case, JSON API resource types cache, you just access it like if it were a regular cache backend. So this is something that if anything you wanna take home today to improve your caching and you have expensive stuff, start using this. Like replace every cache you have with the memory cache in front of it and then a regular cache behind it. It really improves the speed of your code. Second thing I wanna talk about is you should start caching smaller. This is a lesson I've learned from the render cache. I've read it from like A to Z and Z to A for a particular reason, which I will get to later. I'm not a seto mesochist or something, but I really needed to read like the render cache. And yeah, I ended up seeing this and I thought, hey, we can use this. So when building something complex, cache the smaller parts of it. Render cache does this really well. So when you're building a render array, you have your cache keys at the top. But then anyone can start adding stuff to that. And it is actually your job that if it's expensive, you either provide a placeholder or a lazy builder, but you also set cache keys there. And basically what happens in the render cache is as it's going over the children, well actually in the renderer, this happens. As it's going over the children, it looks for every child that has cache keys and then says, hey, render cache, do you have this piece of information cached separately for me? So imagine if you wanna calculate the distance between Earth and the Sun live for anyone, but there's other information on that page that may vary, then you don't wanna recalculate the distance between the Earth and the Sun for every single visit. No, you wanna cache this little bit and then maybe the rest of the page is easy. So it's really up to you to figure out which parts or hearts are very dynamic and then in case of the render array, you would assign it different cache keys. Don't overdo it. You still need to make that consideration like it's looking up this information, the round trip to the database, more expensive than me calculating it or not. If you overdo this, you end up with a million round trips to your database and then your performance is still very shitty, so please don't do this. Too much, by the way. So there's an example usage in group just to give you an idea how you can use this. So not to go into too much detail about the group module but I have basically three layers of permissions. I have the anonymous, I have what I call outsider, it's just authenticated users and then I have the authenticated users who are part of a group. Now in that order, the permissions become from very invariable to very, very, very variable. So I have this cache context which needs to return a hash of all of your permissions. If I were to store all of your permissions in a cache, then I may as well not cache it because it's so dynamic. But I realize that two out of three parts are actually pretty much cacheable and just the latter parts, in most cases, is cached per user. So what I ended up doing is I have these three methods, one for anonymous permissions, one for authenticated and one for the member permissions and each of them are cached separately. So the anonymous cache has only got one entry, the outsider cache has got maybe five or six entries and then you've got the member cache which has a lot of entries. But when I need to calculate your permissions, I speak to all three of these caches and the last one is very likely to have a miss but the first two will have hits. So I only need to calculate the smaller subset of your permissions again to be able to give you the full hash. So this is another takeaway. Like, you know, try to consider what you're caching and if just the small part of that is very dynamic, stop caching that as part of the whole. Like start caching the rest separately and then cache the very dynamic part separately. So this was the easy part of the presentation. I just wanted to warm up with a few tips. Okay, yeah, so yeah, I had this slide because I expected that. So no really, this was the easy part. So let's talk about a variation cache or the fact that we want to use cache context everywhere. So in core, there is a difference in how we cache things. We have two backends basically. We have the regular cache backends we just saw and we have the render cache. And if you want to set something in a regular cache backend, we need to provide on top of the data three items. A cache ID and expiry dates optional but you can provide it and cache tags again optional. If you want to set something in the render cache, we have a system that I like way more and it's cache keys and max age, cache tags and cache contexts. I'm pretty sure everyone here is familiar with that but if you were ever wondering why render cache has that nice system and the cache backend does not, I'm about to explain that. So the thing on the right gets converted to the thing on the left. So the max age becomes an expiry date and the cache keys and context are turned into this long cache ID where the keys are concatenated. First, followed by the context with context name equals and then the active value for that context. Why only the render cache? So why does only the render cache do this? Well, it's the only place that has cache redirect handling. So more on that later. It's optimized around predictable base cache ability. So this is this pre-bubbling thing that you have in the render cache. You know what the cache context are on the very top level parent. So instead of asking the cache for just the keys you immediately know, well, we're going to vary by these contexts. So it has this optimization in building. And due to time constraints, basically it was never finished as a proper cache. So it's all like hard-coded into the render cache. All of these behavior with cache context is hard-coded into the render cache. Which leads to some very questionable solutions. So here's an example of some questionable codes. So the dynamic page cache is this new cache in Drupal which allows you to cache pages way better than before. But it caches based on cache context. But it's not a render array. So how does this happen? Yeah, take like five seconds to read this while I drink my tea. Yeah, I was thirsty. So basically we have this thing sitting at the top of that class. And I was like, whoa, the first time I saw this. Like what the hell's going on here? So let's look a little further. What happens when we set something in a dynamic page cache? Response to render array. I was like, what? That method literally puts the response in the render array and stores it in the render cache. That's how we achieve dynamic page cache. We store responses as render arrays in the render cache. Yeah, not perfect. But maybe we should look further in core. Like this might not be the only problem. So let's look further and I found another use case. Variable cache IDs. I'm using air quotes a lot. Like there's nothing filming me so people at home won't see that. But variable cache IDs. So just to be clear, a cache ID can have variables. It's not that it's a crime to have variables in your cache IDs. That's fine. But those variables should really define the thing itself. So if you are rendering node one, regardless of the page you run, then it's okay to have the ID which is a variable in the cache keys. But they should never define a version of the thing. So if you are rendering node one for people with a given permission that can edit that and therefore the node has edit links, you don't put the fact that they can edit that in your cache keys. So yeah, example, the cache key for nodes leads, one, three, three, seven is that specifically but only if you really want to see that node. Yet because we lack cache contexts everywhere, people create their own variable cache IDs and this is like really bad. So let's look at the route provider in core. The route provider is supposed to give you the active route collection for the current requests. And yet it receives a request and starts adding stuff from that request and the language in its cache ID. Weird. It makes sense now because we don't have cache context but still this is weird. So the thing that itself is the route collection, not the route collection for a specific request. There's never a scenario where we're on request A and we're telling Drupal, oh hey, give me the route collection for request B. Like why would you do that? So you're actually caching the route collection. So this should just have the cache key route collection and the following cache context. It varies by the URL language, it varies by the URL path and the query arguments. So that's what we should be using. This becomes a problem if other modules need to affect that code. So look at the domain access module. This is by the way a code from the domain access module. So because we don't have those cache contexts and because the domain access module makes all of your content, pages, whatever, also vary by what domain you're on, they need to make changes. And as you can see in the middle part, it's really easy to make those changes if something depends on cache context. All they need to do to make sure that your entire website runs using the domain as a variation as well is just add URL.site to the required cache context. That's all they need to do. It's really awesome if you ask me. Yet we have that route provider class and they were making their own cache ID. So they need to change or swap out that route provider class with their own. And they do that and then they just add request gethost to the cache ID. So we didn't have request gethost here and then they just add it. Now, this is not sustainable because what if more modules need to do this? Like there's only one module that can swap out the service. Then you need to start decorating. But decorating is a pain in the ass because you need to take care of all of the methods. It's really annoying. And you're not always this lucky because in this case, the route provider has a method dedicated to give you the cache ID. But more often than not, the cache ID is just a string which is set as part of a very large method which does the expensive calculation. So now you need to copy paste all of that code if that code later on has a security issue or whatever. You don't carry that over. So this leads to some very sloppy codes. So let's talk about these cache redirects that actually power cache context. They're at the heart of the render cache right now. And I'm just going to talk about them in a nutshell. I wanted to have an Austin Powers quote in here, but I couldn't find anything but a GIF and that's distracting. So yeah, this is me in a nutshell in a moment. So eventually they do lead to these variable cache IDs. So they start from your cache keys. But as I mentioned earlier, they then convert the cache context from the active context and then add that to your cache ID. But when we try to retrieve the data that we set before, we only have the original cache keys or ID to go on. And this is why cache redirects exist. So what we do is at the original location where the cache keys lead us. We store a redirect that says, you know what? I'm not giving you the answer just now, but I know that for the answer to be found, you need to tell me what the current values are of a set amount of cache context. And then you should look there. So it's basically like a treasure map that sends you on another journey. And basically this quote says it also, when I last stored this item, it varied by x and y. So in order to find it, we need to build a cache ID with the active values for those contexts. So I have this example. So suppose you have this hotel that allows you to bring pets. And we know that you are either a person with a pet or without a pet. So this is our pre-bubbling cache context that we already have. So at the front desk, there is someone that asks you, do you have a pet or don't you have a pet? In case you do, don't have a pet, just grab a regular room. In case you do have a pet, I need to ask you some more questions to find a suitable room for you, whether it has an aquarium, a scratch ball for a cat, or whatever. The fact that you have to grab this list and ask them some questions about your pet, that's a cache redirect. That is exactly what that does. It's like, I have this extra list of questions and then I can help you. So the render cache implementation of this system is optimized because we know the pre-bubbling cache logic. So this is what I said. It would really suck if you went to that hotel and you were to go to a desk. And that desk was like, yeah, I first need to ask whether you have a pet or not. And you can go to that desk and they'll help you with that question. And that's silly. We already know that we need to ask that question. So that is that optimization that we have. There only one redirect is ever stored, though. And this is really crazy. So what that does is it basically says, you might vary by this or that or both. But I really don't know. So how about we just look up all values? To go back to my hotel situation, suppose I have a guest coming in. And my first question is, is your dog a salt water or a freshwater dog? Oh, no answer. OK, does your fish need a scratching pole? This is what we're currently doing. So the redirect that we have, there's only one. And that one keeps growing and growing and growing and growing. If someone comes in my hotel with a dog, we tell the system or the guy at the front desk, like, ask questions about a dog. If next someone comes in with a fish, we tell them, oh, and also ask questions about a fish. Yeah, but what if he has a dog? No, no, ask questions about a fish. It might be a fish. OK. So this leads to some problems, as you can imagine. The redirect keeps on growing and growing and growing. But because it's such a very volatile redirect, we tag it with everything we can imagine. So if a dog has cash tax, we add those. If a fish has them, which obviously leads to the fact that this redirect is invalidated very frequently. And this causes several problems. One, we have it to take care of caching. But because it's invalidated so frequently, we basically don't cache highly dynamic things. You can test this in core. With given render array structures, you'll notice that they almost never really come from the cache because they're always invalidated. And it also limits how we use certain things in core. Here's a quick story from the group module. At one point, I had a very annoying thing with trying to show lists. And I wanted to make sure that a list would vary by what group it belonged to. So I tried to create this cache context, which you could pass an argument like we have for the user.rolls cache context. You can ask, does this given user have a particular role? And I tried to add that. But because there can be a very large amount of groups on our website, we would have a very large amount of possible parameters to give to that argument. So I asked around some people who maintain core. I asked them, is this a good approach? And they said, no, you can't do that. Can't is like a very strong word. Why can't I do that? If you do that, some pages will literally become one, uncacheable, and two, actually get a performance hit. Why? Yeah, because of cache redirects. I didn't understand cache redirects at the time. And I was like, OK, this sounds like mumbo jumbo to me. I don't know what that means. So I'm going to trust your answer. And I developed the group a different way, a very eventually more powerful way, but different. But just to give you an idea of what this means, suppose you have your local actions block, your tabs at the top of a page. And you have this system where you can subscribe to a node or not. And based on whether you're subscribed or not, that tab says, manage your subscription or subscribe. You would want to vary that page based on a cache context, which just accepts the argument, a node ID, and then whether or not you're subscribed. You can't do that. Because if you do that on page one, then you add this cache context for node one. If you do that on page two, you add it for page two. But all of this gets crammed into that redirect. So what happens is after you visit a million nodes, any page you visit starts checking, are you subscribed to node one, two, three, four, five, six? And your page just crashes. So this is why they told me it's not possible. And I was like, excuse my friends. Shit, I want this to be possible, because I'm really stuck. The group module is insane. In that way, I'm always running into the limits of core. And this was really a limit that I just could not live with. So what if I told you, I fixed this? You can be serious, is probably what you're thinking. No, I am serious. I spent the last seven months fixing this. So introducing the variation cache. So what is the variation cache? Variation cache is something that abstracts everything that we saw in render cache into a separate cache system. So the render cache became way smaller. We immediately updated dynamic page gas subscriber to no longer convert your response into a render array, but just use the same cache. And both of those changes didn't break core. All tests go green. It handles redirects differently by using a cascading system. So now in my hotel, if you come to me with a dog, I'm like, what type of animal do you have? A dog. Oh, I have a question list for a dog. Or if you come to me with a fish, I have a question list for a fish. But if it turns out that there are more variations, and then we have a saltwater fish, I'm like, OK, it doesn't need like a small tank or a big tank. There is more questions as you go. So it's basically the same optimization like we had before. It accepts your initial state. So there's no optimization or performance loss initially. If you have pre-bubbling cache context, it does accept that. And it immediately starts building its cache IDs based on that. But then as you can bubble up cache context, so do these redirects learn from them. So sometimes it introduces a cache miss because the system is teaching itself. But eventually, it leads to a full tree of potential redirects which never have to be flushed. So you get like a full tree of cache redirects, no types attached, no expiry. Because once you get a miss, the system will start self-learning again and potentially alter the tree. It's really cool, actually. I'm really proud of that part. And to show you that it works, it's already part of the group I go. So I was done, like this may sound really bad. So don't take this the wrong way. I was done waiting for stuff to get into core. So what I did was I created it. I wrote tests for it. I have it in the issue queue. I discussed it properly with core maintainers. They're excited about it. They really want to get it into core, but they're currently really busy with 8.8 and 9.0. So Wim Lears is lyrical about this. He really wants to see this go in. It's Dev Days in Ghent next year. I'm hoping to have some time with Catch there because apparently he's going and he's not here at this event. So Wim already told me this is getting into core. We just need to find the time, but this is getting into core. You can read the full patch here. And reviews are more than welcome, but I do warn you, I think the patch is about 150 kilobytes because, well, it's a lot of code to move around and then some tests needed to be adjusted because all of the old tests were putting stuff in render arrays and putting that through the render cache. And yeah, basically, all of the cache IDs which we hard-coded in all of our tests needed to be converted to cache keys, and that tends to add up to a lot of kilobytes of just useless cruft that makes the patch very unreadable, whatever. So just to give you a sneak peek, because I don't want to just tell you, hey, I fixed this and that's it. I just want to give you a sneak peek. So what does this look like right now? This is the same code that we saw earlier in the dynamic page cache subscriber. So we don't change anything anymore. We just say the cache case response. We put the response directly into the cache so there's no converting to a render array or whatever. We grab the cache metadata, as you can see above, from the response and we add our persistent cache context, which you saw in that weird render array earlier. But our initial pre-bubbling state was just those cache contexts, and that's it. And if you look at the render cache right now, this is what it turned into. Sets in the render cache is just this now. So it works for everything, also render arrays. So you just grab the cache case from your render array. You set the data as the cacheable render array. That method just moves some stuff around, but nothing too serious. And then you just create cacheable metadata from the render array and add the cache tag render because that's what the renderer does. And you get the pre-bubbling elements, and that's it. I think I went, wait. No, actually, 10 minutes is fine. So any questions? I was seriously worried that I'd have 20 minutes left, but it's only nine, so yes. I think there's a mic there, or I can repeat the question. This is super cool. Because all of those services should be swappable, have you thought of, or have you already put this into a contrived module to sort of shim this into core without having to patch core? No, actually I haven't. And reason is I can put this into core already. So I could swap out the render. Yeah, I could swap out both, but actually that's not really a bad idea. Let's do it tomorrow. Yeah, I'm still here tomorrow, but I can already prove that it works because the whole patch goes green, and then JSON API wanted to add this whole new thing to core, and I so happened to stumble upon their patch, and I saw that they were putting a lot of their responses into a renderer, and I was like, oh God, no, please don't add another thing that I need to alter later on. So I took their patch, and I said, you know what, I'm just, for shits and giggles, going to add my code to it, and see whether that still goes green, and it went green. Like from the first try, it went green. Like this huge JSON API patch that I didn't understand what was happening there just went green. I was like, okay, I came up with something really cool here apparently, so it's working everywhere. But no, I haven't done that. I should have done that. You still can? Yeah, I still can, but as I mentioned, it's part of the group module now, and the way I did it was just because I needed to get moving on with some of the stuff that needed that I just have this core fixed folder in the group module now, where all of this resides with a big ass warning saying, yeah, this is internal, I'm using it, don't use it, it will get into core someday, and then we should really just start using it, but please don't use this code yourself. I should have done that and made that a dependency. Where were you like two months ago? No, okay, anyone else? No, did, like, not or whatever, if you, did everyone like enjoy this session? Like, because usually if there's no questions, I start darting like, was it too difficult or something? But the first part, those tips, were they helpful? Cool. Second part, did that blow your mind? Awesome, then I have nothing left to add except maybe join us for the contribution opportunities and what did you think and then there's no more slides left, so yeah, so again, reviews, welcome, like, read the whole thing, but yeah, if you're not very familiar with the render cache, half of it might not make sense, but just do nitpicks, piss me off, like, oh, you forgot whatever there or something, but more reviews means more traction, means more stuff is happening, so if you wanna review it, please do. If not, no hard feelings, I'm starting to get used to the fact that sometimes getting things into core is a very lonesome journey, so. All right, that's it. Can I press the red button again to make this thing?