 So, welcome to the last session of TripleCon, I guess, like the last, last lot of sessions. So I hope you had a good time so far. Today's session, I've talked about rendering and caching in the past, but today's session is meant to be more practical, more pragmatic, so it's not going into all the technical implementation details, it is not going into edge cases, it's going into the common case, and how you can use render caching, dynamic page cache, page cache, pick pipe in practice, how you can debug them in practice. So if that's not what you're looking for today, then maybe switch to a different session. If that is what you're looking for today, then I hope you will enjoy what's to come. I am going to do a live demo, live debugging, so if you have questions along the way, raise your hand so I can jump into things on the spot. Hopefully all goes well, it's a live thing, but I think it's more useful to see it being demonstrated how you can step through things than it is just on pure slides. So the point is that I'm taking you through the different layers, through the depths of Drupal to some extent, if you will, hopefully after this session it will not be as scary anymore if it is scary today. How many of you have had experience, or how many of you are using big pipe? Raise of hands? Cool, about 10%, 15% maybe. How many of you have disabled page cache? Three hands, four hands, interesting. How many of you have seen the ex-Drupal dynamic cache response header and have seen it set to uncacheable and wondered why? Okay, good. So hopefully all of those things should become clear. And again, please interrupt me if you have a question. So let's get started. I'm going to go very light on theory, but this is basically the only slide I have about caching. This is the principle of caching, right? I think I don't really need to explain it, but I'll just go over it real quick. The whole point of computing is that you have a bunch of inputs, you apply computations to it and you get output. So we're making a robot to a bunch of work and it takes time and we want it to go faster. So what is caching? It is instead of making the computation again and again, based on the inputs we generate a cache ID, something that represents uniquely the thing that we are computing. And then we read the computed output that we stored previously and just return that. So the point is to save time by storing some data somewhere that we can look up easily. That's simple, right? But the tricky thing about caching is not caching in and of itself. The tricky thing is cache invalidation. As the saying goes, or the joke goes, there's two hard problems in computer science, caching validation and naming things. Well, Drupal 8 tried to solve caching validation and hopefully you'll see or if you've already experienced it, you've already noticed that it has solved caching validation, not in the general way perhaps, but for the purposes of Drupal, definitely it did. And so perhaps what does caching validation mean? Well, the thing is that if inputs change, that the output should also change. And that's a tricky thing. We don't always know perfectly well when inputs change. And that was a very big problem in Drupal 7 and before, but for Drupal 8 we have the concept of cache tags and cache contexts. So cache tags describe data dependencies. They describe when stored data is modified, when it changes. So when you have something that is stored in cache, so some output that we cached, what matters is that the cache ID is always correct and that the stored data that we read is always up to date. Otherwise, we're showing them either wrong data or stale data. And we want to avoid that. So tags describe when an input has changed. So for example, when a user profile, let's say it's a user profile, and they change their hobby from table tennis to snowboarding, like that's important that it shows up right away on the rendered outputs, that's what cache tags describe, such stored data changes. Contacts on the other hand describe circumstances, the context. And an example would be rain or no rain. So imagine it is raining, that should show one result. And it is not raining, it should show the other results. So based on tags and contexts, we can define what the cache ID should vary by. So the cache ID is a thing we look up, and either we should look up the rain or the no rain cache entry or the rain cache entry, either one. In this case, there's only two possible values, but perhaps for some cache context, there is 10 or 100 or 1000s. So cache contexts impact the cache ID. Describe variations. Cache tags describe changes in the stored data. And both of them are necessary to do this correctly, to do cache lookups correctly and invalidate them correctly. There's a third that is less important, Max-H. Probably all of you have heard of Max-H before, because it's part of the HTTP cache control header. It's the same principle. Max-H, you only need for freshness, staleness, data that is always changing. Things like the current temperature. If I show the current temperature right now, then one second later, it might be different, right? So it's only for time-sensitive data, and that means things like charts, graphs for stock exchange, for weather and whatnot. So that's really all it's about. The caching means taking in-and-puts, going to an output. But that's trivial. The tricky thing, the hard thing, that wasn't solved until Drupal 8 was invalidating correctly. In Drupal 7, what happened was either you were serving stale data, or we were just invalidating the entire page cache, the entire render cache. Every single time a comment was posted, a title was edited, like everything was invalidated. Well, of course, things are correct then, but you're not really using the cache system effectively. This was about the cache system. This is about the render pipeline. The diagram, this looks pretty scary, right? So that's why I have this fitting emoji there. So I'm not going to go into detail about this. I did a talk about this two and a half years ago, and it's still completely accurate. So if you do want to know the ins and outs of the render pipeline and where you can tie into things and whatnot, please go to that talk or go and look at that talk. That's not what we want to do here today. It can be overwhelming for sure. This is the diagram we're going to look at. Hand drawn took me a lot of time, so it's a bit strange, but I hope you will find it clear. So for Drupal, what is really happening, Drupal and any other CMS or anything on the web, really, it receives a request and it builds a response. That's what the web is about, right? Now, when Drupal receives a request, it routes that to a controller, controller or page callback in Drupal 7, but controller in the end, what it does is returning a render array, so something like this. And eventually, the render system will decorate it with, for example, blocks if you're using the block module or if you're using panels, then there will be panels surrounding the main content and whatnot, but basically, you're effectively building a massive render array who here has cursed render arrays at some points. Okay, everybody else clearly hasn't used render arrays yet. Yeah, they're good, they're great, but they can be frustrating. So we go from a request, we go to a controller. The controller returns a render array. The render array can be expanded further, by, for example, the block module, but eventually it goes into the renderer. And the renderer, what it does is take those render arrays and build HTML fragments. So for example, an entire sidebar with over here at the bottom, the red part is a placeholder. This may be, for example, let's say a video. This may be the header. This may be the main content with a bunch of personalized links. Like for example, if you look at a node, then it will show you five new comments, and you can perhaps post the comments, or perhaps you can't. That's the dynamic nature there. But in any case, we take something that is very much PHP-like, render arrays, and we turn that into HTML fragments. Okay, that kind of makes sense, right? At some point, you need to get to HTML. But then the next step is for these HTML fragments that are each like separate different areas of the page to be assembled into one page. And once it reaches dynamic page cache, we end up with something like this. It's something that looks like a HTML page. So can people in the back actually see the diagram sufficiently well? Yeah, I see nothing. Okay, I could zoom in like this if you want me to. So basically, we are looking at a page and every fragment is assembled into the page, but it's still a partial HTML response because the red bits, those mean that those are placeholders that still aren't rendered. Like there's just a placeholder there, like something that could be injected in there, but that means the page is not yet fully assembled. The interesting thing, though, is that dynamic page cache can cache this page as it is as you're looking at it right now, which means that instead of going through the process of going to a controller that returns a render array, then we build a render array further, then we turn it into HTML fragments, and then we assemble this HTML fragments into an HTML response. We can skip all that. We can just go from request right away to dynamic page cache. So we can skip a lot of code that we would be executing otherwise. This is how dynamic page cache can accelerate things. You can go right from incoming request to a partial HTML response, at which point the only things that still need to be rendered are these tiny red bits. So that's a huge time saver, which is why dynamic page cache is in Drupal Core, which is why it's enabled by default. It helps everyone. It helps anonymous, authenticated, sessionless session responses. It helps all of them. But then, as you can see, we've reached this fork in the road, if you will. So we either go to page cache or we go to big pipe. And I saw that only 15% of you have enabled big pipe. There's no reason for you not to enable big pipe, honestly. It's stable as of Drupal 8.3. We're about to release Drupal 8.4. It is being proposed to be enabled by default in the standard install profile. That is still ongoing. There have been other priorities. But after what you will see today, hopefully you'll consider enabling it because it is helpful. But anyway, page cache. Page cache is what we use for anonymous users. Page cache is what can accelerate the responses for anonymous users, which would mean that we can go right from the request, not the dynamic page cache, but immediately to page cache responses. And page cache contains final HTML responses. So as you can see, the red bits have turned green, meaning the placeholders have been replaced with their final contents. So for example, the thing here at the bottom of the left sidebar will contain a personalized block and the green bit over here in the main content contains the comments, five new comments links. You can post a comment or, well, it's an anonymous user, so you probably can't. The point is we can go right from request, bam, to a final response, which is why page cache is able to send responses in a few milliseconds super fast. But that doesn't help you if you have an authenticated user. So that is why there is big pipe to accelerate those responses, which means that we still are going from request straight to dynamic page cache's partial HTML response. But then we go to page, to big pipe. And what we can do is we send immediately the thing that dynamic page cache stored, which is a partial HTML response. So we can send this data, this HTML right away, as you can see here. But then we keep the connection open. We keep the TCP IP connection open and we send the fragments, the placeholders, the red bits, the values for those, we send them afterwards, which means that immediately, even for authenticated users, you see the majority of the page. You see the skeleton of the page at the very least, the menu and whatnot, the header. But then the personalized bits show up later. And that is pretty powerful. I did make one simplification because most of us care about anonymous versus authenticated. But for many of us, it's actually also the case that what we also care about is anonymous users with sessions. For example, a e-commerce website, Drupal commerce sites. So Drupal commerce, if you add something to the shopping cart, as an anonymous user, obviously you started this session. And that means that page cache cannot be used anymore. But you can still use big pipe. And so big pipe can still send everything in a page and then stream the contents of the cart or the rendered cart can be streamed afterwards, which means that Drupal commerce has an advantage over other e-commerce solutions because it can stay fast, even when using a shopping cart, which was a huge problem before. So long story short, request comes in, turns into a renderer way. That's what you provide as a module developer. The renderer turns it into HTML fragments. The HTML fragments are assembled together, but the placeholders aren't replaced yet. Before it ends up in page cache, the placeholders are replaced. And at that point, things are super fast for anonymous users. But for authenticated users, or more specifically, users with sessions, anonymous or not, we can stream the result that is stored in dynamic page cache right away, which means you, within milliseconds, get something. And then the final bits will follow dozens of milliseconds later where you have the replacements for those placeholders. So together, this means that Drupal has a pretty complete solution for accelerating things at different levels. You could argue that the HTML fragments, which, by the way, are also cached individually, like we wouldn't necessarily need that if you always have dynamic page cache. But it's still helpful in certain other cases, because, for example, maybe page A and page B, they both use this same HTML fragment, if that is also cached and that can be reused. But at each of these layers, so the HTML fragment layer, the partial HTML response layer, the fully assembled page cache layer, all of those are performing caching. So that's where rendering and caching come together. That was basically all the theory I have. So let's look at a concrete example. By the way, everyone still with me so far? Yeah. Cool. So let's look at an uncacheable block. I have this code is available in the demo module that, once the talk is published, you will be able to download and play with all of this yourself. So the idea of this uncacheable block is we're showing the weather forecast, and the weather forecast is changing at all times. However, it's a very shitty weather forecast, because it's always predicting rain. However, we're pretending that we're actually doing the work of computing an actual weather forecast. And so it's taking some time, because it's talking to some external weather service, supposedly. But we are indicating that this is never, ever cacheable. So we say maxH is zero, as in, this can be cached for exactly zero seconds, which means not at all. This block, when rendered, looks something like this. So we have a timestamp there, which is not very helpful, but I'm just using a timestamp so that you can see that it's, when it will see the live demo, you will see that it's changing at every request, so that it's not cached at all. The next one, a personalized block that is personalized per session. So every session gets different content. So we start with five funny emojis, and then we render something. What we do is we pick one at random from those five, and then we say, well, here's a funny emoji just for you. Pan picked at this particular time. The time again allows you to see when it was generated, so yet we can see whether it's been changed or not in the demo. And what we're seeing as well, we're caching this per session. The session cache context indicates this is cacheable per session. And the next one is exactly the same. So, well, this is what it looks like. The next one is exactly the same per user. So again, five funny emojis. This one says today's funny emoji for you. Again, picking a one at random. This time the user cache context instead of the session cache context. And one thing on top of that, we're saying that it's cacheable for 86,400 seconds, which is one day. And so when you look at this, again, a random emoji, just to visualize how it works. And last but not least, a cacheable block. So in this case, what we're doing is we're showing which user is a root user, user number one. So we're getting their name. We're only showing this to users that aren't user one. And we're saying, well, this is cacheable until user one is modified, meaning that we're adding the cache tag for user one, which means that it looks like this for my site because my root user is called root. But this should be changed every time the root user, user one, changes their username. So I think I hope that's pretty clear. So we have four blocks. One is uncacheable and it's always showing the same text, but it's showing like every block is showing a unique time, the time at which it was generated. This one is not cacheable at all. The next one is cacheable per session. The next one is cacheable per user. And the final one is cacheable across everything, but it's not displayed for user one and will or should update automatically as soon as user one changes their name. So later on, you'll be able to download the demo. Time for live debugging. And so again, please interrupt me if you have questions. So let me get started by turning on the exit, but first probably let me zoom this in. This is the first time I'm doing this. So I'm apologizing ahead of time if this isn't a great format. I'm just trying to shake things up. Let me know if you like it afterwards. So we are looking at something. Well, actually, let us start from scratch. So I've got a connection to my database here and I'm going to select cache dynamic page cache, cache render and cache page. So those are the cache bins for render caching, page caching and dynamic page cache. And I'm going to truncate all of them so that we start from scratch. And I'm going to regenerate the page. Okay, so we get... Well, that's kind of annoying, but they picked the same one at random. I can't help that. So let's actually truncate again and hope for better this time. Make things more clear. Yay. Okay, so we got somebody holding up their hands and a monkey covering their face. Okay, and let's look at the anonymous user here. And interestingly enough, even though this is not plugged in and this one is, we get the same thing. That's not good. But as you can see, it is completely at random because actually this one was picked at this time and this one was picked at a different time. So ends in two, ends in seven. Okay, that's really annoying. Perhaps I should clear again because it's not clear otherwise for you guys. Okay, probably should have picked a different strategy. Oh, okay, I know what's going on. See, I'm refreshing this page and nothing is happening, even though it's definitely loading. The reason being... Let's see if I can display this clearly. So you can see in the response headers or you should be able to see XdrupleDynamicCacheMiss, okay, but XdrupleCacheHit. So we're getting a page cache hit, which is why we're not seeing anything too. We're not seeing anything change here because we didn't clear the page cache. So as I said, live debugging. So I'm talking out loud. Hopefully the reasoning that I'm pronouncing is actually helping you as well. So now I cleared the page cache and I refresh and I again get the same emojis. Great. Well, actually this time it's actually okay. Funny emoji just for you. The authenticated side as user two. I'm seeing the laughing, crying face and on the anonymous side I get the hands up in the air face. Yeah, as you can see, they're different. Okay. So we already ran into something as in page caching, obviously. I was a anonymous user and so I can clear render cache entries as much as I like. As long as this page cache entry exists, I'm still going to be able to see the same cached page, which makes sense, right? Because as I showed in the diagram, we go directly from requests to the fully assembled response. That's what you guys are seeing. Okay. So that's one thing demonstrated. Hooray. It's kind of stressful doing these things live. Okay. So let's probably look at render cache. So I'm going to clear. Well, I'm actually going to start with renderer and I'm going to need to do this zooming in thing all the time because PHP storm broke its zooming behavior, at least in my machine. I don't know why but anyway, so we're looking at the renderer but I actually know that I need to look at the render cache because that's where it's actually being stored. So if you go to the renderer and scroll down, you'll see that one of the injected services is this one, render cache. So I'm going to click on this one, which is the interface. But if I then press control H, I can see the hierarchy and I can see that there's only one implementation, which is render cache. If I go in there, I'll see this method get. So getting something from the render cache and either is returning the cached result, meaning cache hit, or it's returning false, meaning cache miss. Actually, let's look at all the breakpoints. Perhaps turn on XeVoc first. Okay. And perhaps already, okay, that works. And actually I can show you all of the breakpoints. So I just added these two, the ones in render cache for cached elements and for false. I also added some in other places. For example, for let's start with the beginning, I basically looked at every place in dynamic page cache and page cache where it's setting a header because both modules signal to you as the end user or to you as a developer. What actually happened here? So it always says XdrupleDynamicCache uncacheable or XdrupleDynamicCache miss or XdrupleDynamicCache hit. You can guess which what each means, I guess. Miss and hit seem pretty obvious. Uncacheable means that even though it was processed by dynamic page cache, it was deemed uncacheable. So we can look at that because I saw some hands earlier that that was not perfectly understood. So if we go and look at the code, we are seeing that it's being set here. So the responses uncacheable market as such is what the comment says. When is this being set? Well, when this thing returns false as in when it's not able, when should cache response is returning false? If we look at that, what happens here is this receives a response object. We retrieve the cache ability metadata in the response object and we look at both, well, at the max age, the contexts, and the tags. And so there are some other placeholder conditions which means that if either this max age auto placeholder condition or this cache context auto placeholder condition or this cache tag auto placeholder condition is met, then we will automatically turn it into a placeholder because it is so dynamic that it would otherwise infect or pollute the overall page because let's say we're looking at an entire page. So something like this. And let's say that this thing here at the bottom is perhaps not cacheable at all. Well, if we would be caching things correctly, then this entire page should become uncacheable because otherwise, you would end up with serving something that was not cacheable and yet still caching it and serving it to everyone. So you may be serving still data. That is why we invented the concept of placeholders in Drupal 8. So these red bits are placeholders. Why? To not infect the rest of the page with super highly dynamic or uncacheable things. And that is exactly what dynamic page cache is detecting here. If it is something super highly uncacheable, that is when it says, nope, you shouldn't cache this response because it's not safe. It doesn't make sense. There would be too many variations. So digging a little bit deeper on a placeholder conditions, if you grab the entire code base for that, then you'll see that there's one entry in core.services.yaml. If we go and look there, you'll see that these are the default other placeholder conditions. It's in the container. As you can see, it's in a container parameter called render.config. So you can change it to your liking. But by default, we'll say that max-h is 0, is too dynamic, as in we will not cache this because it's just changing too much in this case. It's changing at every request. And session and user, because caching per user, if imagine you have a million users, then you're going to be caching a million variations. That doesn't make sense. Similarly, for use, for sessions, you'll usually have many, many sessions, so it doesn't make sense to cache for every session. Even though I'm doing that here for demonstration purposes, you shouldn't do that. Okay, so that answers, hopefully, the question of what does that mean, the uncacheable dynamic page cache header. Then we have similar break points in page cache for page cache hits and for a page cache miss. And then in big pipe, we have a break point for the sand chunk method, which we'll see more about later. Okay, everyone's still with us? Any questions? Not zoom in? Okay. Okay, I will do that. Thank you, that's really helpful. But I think I should zoom in, right, because otherwise, you can't read it. Yes, I will do that. Thank you. Was it too hard to follow for everyone or was it okay for some people? Okay, damn it. Sorry, I blame PHP Storm. This used to work fine until last week. Okay, so let's go through an entire process of something being rendered. So I'm going to clear page cache and dynamic page cache so that we are hitting only, well, actually, I can clear everything again. I guess I'll demonstrate now how a render cache works. So I've cleared dynamic page cache and page cache, which means that we are not skipping certain steps. We're going through the entire process. So if we reload this page now, we should be breaking on something that is in the render cache, which is where we are at. The first request is kind of special because, as you can see, it's very, it's kind of difficult now to, is this better? We're looking at dynamic page cache here, but if we, because dynamic page cache reuses render cache, it's an implementation detail. But if we skip the next one, we'll see more clear examples. So we'll see, for example, in this case, we're looking at, I hope that is readable. This is a render cache request for viewing user two in the full entity view display mode. So what we're doing here is retrieving a fully cached version of user profile two being rendered. And the cached markup we can see right here. So if I copy paste that out, to make it more visual, this is what has been stored, what has been rendered cached. I can't. I can't, but like last time I used it, it failed miserably. Hey, okay. Last time I tried this, it worked terribly. Live demo of PHP storm failing. I can't apply, I pressed okay. What more can I do? Yeah, but it's then I lose the, then I lose the debug mode. So PHP storm is not meant to do live debugging. I guess that should have told me something. Yeah, it's still better. Okay, so this shows you one render cache entry being retrieved. If I, so I keep jumping ahead. So I'm pressing the continue shortcut, resume program, option commands are. So now we are retrieving entity view block, bardic account menu. So the account menu block we are retrieving from render cache. We're retrieving the branding block. We're retrieving the main menu block. Retrieving the messages block. Retrieving the breadcrumbs block and so on and so on. So on this particular page is very simple. So you're not seeing much besides blocks because what you're looking at if I refresh the page without, you see that this is basically the only entity on there and everything else is blocks. So that's why we're only seeing render cache entries being discovered. So that's the first phase. A page can be accelerated by render cache, but then it's assembling each of these different parts. All the small parts are being assembled separately. Now, if I refresh the page again or let's do something else. Let's look at the network while refreshing the page. We should in theory now be able to see the dynamic page cache has a cache hit. And indeed it has a cache hit because I just refreshed it so I filled the cache and now it's filled. So we have a cache hit. So if I clear it again, I should see a cache miss. Cache miss. And so every subsequent request for this particular combination of contexts will be a cache hit. But you will probably notice that there is no xDruple cache header in here because we're not an anonymous user. We can only hit the page cache as an anonymous user. So let's look at that instead. So again, cache page is one entry now. And that means if I refresh this, look at the response headers. Dynamic page cache was a miss because the cache hit was for the logged in user. Currently I'm the not logged in user which makes sense and that dynamic cache is a miss. But xDruple cache is a hit. Now if I go and delete it again, the page cache entry, so I'm still in the cache cache page bin. If I delete the only entry in there, then I should be getting a cache hit. Sorry, a cache hit for xDruple dynamic cache because it was filled the previous time. But a cache miss for page cache because I just deleted that. So in this case, we're seeing a cache miss for page cache but a hit for dynamic page cache. Why? Well, because what is happening is this was cached. So this is what the hit is representing. We had a partial HTML response that is stored and that can allow us to skip the steps from render array to fragments to partial HTML response. We can start with the partial HTML response with that part as a hit. But page cache was empty which means that was the thing that was missed. So that is why I can imagine it's sometimes confusing why some things are sometimes a hit and sometimes a miss. Hopefully this clears that up a bunch. Now if we go to the... Are there ways to deal with this from a client-side rendering or an edge-side rendering aspect? Let's say you have a reverse proxy in front of it. So the question is, can you make this integrate well, I think, with a edge cache? Is it a basic question? Is it PSI or H.E.C.C.H. or HTML, WageX? Because although this looks very efficient, you want to avoid hitting the PHP runtime and consuming web server connections under load. I wasn't planning on covering that but it's a fine question. So page cache here is effectively something like varnish. It is effectively implemented without any inside Drupal knowledge. So anything that page cache can do, also, by definition, must be possible in varnish or any other edge cache, which means that for sessionless responses at the very least, it is totally possible to do cache tag-based invalidation of anonymous responses. So the answer is yes for anything cache tag-based. For cache contacts, that's more difficult because cache contacts require inside knowledge because, for example, if something varies by user, like the edge is never going to know which users exist or which the current user is or which session it is and so on, although there are techniques to make that work but it's super advanced, super complex, requires a lot of complex infrastructure and the edge needs to be communicating with the origin, so that makes things more complex but there is a way to do it. The question is, are there no, is there no built-in way to render ESI tags for these fragments? Yeah, H include is kind of a that thing, though, but yeah, as far as I know. But so, yes, you can actually integrate this very easily. That is actually what BigPipe does. BigPipe uses exactly the same approach. So I will cover that now, actually. So hopefully that will answer your question in more detail then. So let's refresh the page again and note that this uncashable block is being changed every single time. So if I reload it and I'll turn off Exibug for a second, so you can see that it's now 79, 84, 85, 86, as you can see, I can keep refreshing and it's custom every single time. Now, how is this actually being rendered? Well, that's where BigPipe comes in. Let's turn Exibug on again and let's refresh the page. And actually turn on listening for debugging again. So I'm going to skip the render cache stuff because we covered that. This is also like I put a breakpoint there. So this is an ideal way for you to debug these things and try to get an understanding. So I specifically chose these points which because they're very easy to find. So if you grab dynamic page cache or page cache for literally hit or miss, then you're able to easily find those points that you want to put a breakpoint at because just before those points you have the decision logic that determines whether it is going to be a cache hit or a cache miss. But we are interested in BigPipe right now so I'm going to disable this breakpoint as well and now we are in the BigPipe class and we're sending chunks. I'm trying to zoom in and out slowly and I'm not sure if I'm succeeding. Very sorry. I'm going to try to demonstrate all the things at the same time. Okay, so I'm going to kill this first. Oh crap, I closed Firefox. I'm going to go to a blank page about the load user two. So now you can see we start from a blank slate, right? The first thing we are sending, the first chunk we are sending in BigPipe is this and I'll copy paste it in here. That should be more than legible. Perhaps it's too big. So as you can see, just an HTML document as you would expect, but there is no closing body tag. That's strange. Well, it's because what we're doing is sending placeholders and we still want to stream the replacements for those placeholders. So we have BigPipe-style placeholders here right now. We have one for the rendering of messages. We have one for local tasks. We have one for the uncashable block, the one that was just refreshing all the time with the weather forecast. We have one for the personalized block per session, one for the personalized block per user and then finally one for the cashable block. So as you can see, all of these are placeholders and there's no actual content in there. This is very much like ESI, as you were asking. So what is happening is in Drupal, there's a concept of placeholder strategies. So you can choose to replace placeholders in multiple ways. BigPipe is just one way of replacing placeholders. Another way could be an ESI module that renders ESI includes and then just refers to a URL where those includes can be fetched and rendered. So that's exactly what you were asking. So as we can see, we have like six placeholders and if we continue now in Firefox and in the debugger, let's let PHP send this first chunk. Bam. This is what happens basically when BigPipe is being used. You can see that the majority of the page is visible. You can see that the cashable block is there. The personalized block, both of them, aren't there. The uncashable block is also not there. Which means that everything was sent, except for those red bits, if I'm talking in this metaphor, right? What we basically did was sending this part. The green parts is what we are about to send next, the replacements for those placeholders. So what is the first next chunk that we are sending? It's this strange looking, well actually let me just append to the bottom. That's conceptually also what's happening. We first send an enormous chunk that is the majority of the page and then what we do is send a BigPipe start event. The next thing we send is a whole blob of strange looking HTML. Well, it's actually a bunch of JSON that the Drupal Ajax system knows how to interpret. And the Ajax system then goes and takes this Ajax command which is about inserting, well actually replacing some HTML, which HTML? Well, the thing with this placeholder ID, which one? The local tasks one. And then it replaces it with this HTML. And yes, it is HTML. It's just encoded as JSON. This just means, if you remember correctly, this is the equivalent of the apostrophe. So we're sending the rendered version of each block, of each placeholder individually after the initial page was sent. And so as you can see, this is what we had initially. And now Ajax, once it's JS runs, it hasn't run yet, which is why we don't see the first block up here yet. But once it has run, it will show up. And it isn't running simply because we are doing debugging right now. The next chunk is for another block. It is for the uncashable block. Then another block is for the personalized block, procession block, and so on and so on. Still hasn't processed yet. It's because we're debugging. And so I can continue a few more times. So this is replacing the per user personalized block. Once more, this is then the messages block. Once more, we're done. The big pipe stop event. At this point, big pipes JavaScript will stop processing the new things that are appearing at the very bottom of the HTML. So we kept the connection open. Oh, and at this point, it has been processed. But as you can see, we're seeing it in super slow motion at this point. But these three blocks, the uncashable block, the personalized procession block, and the personalized per user block, those three did not appear on the page initially, nor did the view and edit local tasks thing here. Messages, there aren't any so there aren't any visible. But those bits of the page showed up later and everything else of the page showed up immediately, which especially is very helpful on high latency connections or on pages where there's a lot of complex computations, where you have content that is highly dynamic, uncashable completely, or perhaps requires a lot of number crunching, for example, to determine the favorites of the user or what has been happening recently. Those kinds of things, those can be served later on, streamed via big pipe, and then the overall page still remains fast. And so we've now sent the stop events and the very last thing that is sent is a body HTML, the closing body and closing HTML tag. Right now you can see that, well, I can't go there apparently without disappearing, interesting. In the bottom left of the screen, you can see that it's still waiting for data from the host. And as soon as we send the remaining or the closing body in HTML tag, it will finish, bam, done. Now if of course I turn off X debug, and there is no more long waiting periods, then this is much faster. But as you can see, the casual block shows up immediately. Three other ones show up later. Hopefully that was clear. Hopefully this is all helping, and I'm not making a fool of myself. So that's the live debugging I wanted to cover. Any questions about live debugging? Anything else you want to see right now? Anything that is unclear? Yes. That's a good question. Yes, exactly. That's a good question. So he was asking whether we are using a chunk to transfer encoding because we don't know the content length. The answer is you're correct on both counts. We are using chunks to transfer encoding, and we do not know the content length. So that is the number one requirement, actually. So let me, I know one way of easily finding the documentation page. So let me just look it up for you real quick. Apparently there isn't anymore. Okay, wonderful. The big pipe documentation. This is like live usability testing of the Drupal 8 documentation. Modules. Contributed modules. Wonderful. Core modules and themes. That's what we want. And then big pipe. Apparently still not listed. Click this first. Hopefully it's there. Big pipe module. Big pipe environment requirements, which is exactly what you were asking about. So what is required is that you are not doing output buffering because if there's output buffering then it's still going to wait until Drupal is done sending the HTML and then you don't get the benefits. So worst case, you do have output buffering and then big pipe will be not effective because it's still waiting for everything to complete. But other than that, it can't break anything. But that's the requirement that you don't have output buffering enabled. There's also instructions on how to enable varnish compatibility. The requirement is that you have streaming, again, because otherwise not streaming means output buffering, which means that you can't repeat. Okay, so we're talking about how to make big pipe compatible with your environment. The requirements are essentially that you are not buffering the output because otherwise you are not effectively streaming, which means you're still waiting for the entire response to be complete before sending it. The whole point is that we're sending every bit that we have available right away, which is how things are made faster, which is how the initial majority of the content can show up right away and then the replacements can show up later. This documentation page shows how to integrate or how to configure things correctly, so that it works with everything correctly. But worst case, if you enable big pipe, it just will not have any effect because there is output buffering, because it is not sending each chunk as it becomes available, and then it's just waiting, just like as if big pipe was not enabled. Effectively, yes. So the question is, from a PHP standpoint, are you effectively just keeping the connection open and basically waiting in between echoes and then sending HTML, echoing, printing, HTML? The answer is yes. There's not more to it. Like that's actually the ancient technique, but Drupal, like most CMSs, actually forgot that this was a thing that was possible in the first place for a long time. So almost all CMSs, including Drupal 7 and before, what they are doing is effectively waiting, waiting until every tiny little bit is rendered and then sending it all at once, which means that you're waiting sometimes for the tiniest bits of information, which is not efficient. Any other questions? Yeah? That would be wonderful. So I had a doubt about your initial code that you showed there. The doubt is, so we saw that there are these specific methods to specify the MAC page and so on, but also the output of the block is a render array, and the documentation that I could find always refers to putting this information inside the render array. The question is, would it work? Anyway, of course it's more clear this way, but does it work if you just put that information inside the render array that you return when you run the build? Yeah, it's a good question. I've had the same question like a week ago. So yes, ideally you put it in the purpose-built methods, like for example, get cache max age here, get cache context here, and then get cache tags here. Those are the three. The benefit is that it's more clear, but yes, you can also just put the cacheability metadata in the render array. Why? The reason is this may be calling other templates and other logic, and those themselves may have cacheability metadata as well. So it would be impossible, let's say that this block would be rendering an entity, for example, an entity, let's say, or the current user. The current user doesn't, and then perhaps the current user, and then it's modifying, there is a module that's modifying what is being rendered and that's adding yet more data. Like you wouldn't possibly be able to know in the logic of this block what eventually might be rendered. That's impossible. Render arrays can be extended and altered. That's the whole point. And so that is why it is allowed for you to specify cacheability metadata also in the render array that the block returns. But probably it's clearer in majority of cases to explicitly put it in there if it is something that is, like for example, explicitly varying by user. But for many use cases, it's actually impossible because things have to bubble up from the contents that are deeper within the block. So the question is what happens if you enable PageCache and Bigbyte at the same time? Fortunately, the answer to this one is simpler. So they are enabled at the same time on my computer right now. That works fine. Why? Because PageCache only operates on requests that do not have a session. Because it is assuming, like it is caching something, it is caching the final HTML response and it's assuming that it's the same for everyone, which is only true for users without a session. As soon as you have a session, it could be personalized either per user or based on shopping cart or whatnot. So PageCache is the simple case. Bigbyte only works if you do have a session. So there, that's the fork in the road, basically session, yes or not. And so Bigbyte accelerates the other case. PageCache is much faster because it can skip even more steps. It doesn't need to do any rendering, but it's only actually usable in the case of having no session. Cool. No other questions? Okay. So the next bit, testing. So using cacheability metadata is one thing, but if you don't apply it consistently and correctly, then things can break in unexpected ways, which is probably not what you want. You probably don't want to have built a client project and then have everything working correctly and staging and testing and manual testing. But then as soon as it goes live, oh, apparently things are breaking in horrible ways. Perhaps data is leaking from one customer to another or perhaps stale data is being displayed. There are possible security problems there as well, of course. So testing is important. And in my experience, it's been something that many people avoid doing because it's perceived as being hard or painful. So I'm hopeful that I can show you that it doesn't need to be painful. Can look as simple as this. You can do black box testing or white box testing. I'm starting with a simple case, which is black box testing. So what we're doing is very simple. We're asserting something. We have a custom assertion method. The assertion method is making a request and is asserting that there is a certain title. We're doing the request and we're checking that the title is in fact there. Simple enough, right? So in our actual test method, what we do is we first check that we make a request and the title is the expected one, the initial one. Then we load a node. We change the title and we save the node. And then we assert again and we now assert that the title has in fact been modified. Like maybe some of you are wondering, why is this even like a test? Well, the reason is that this is proving that this is working with instantaneous validation without you having to go and explicitly clear caches. How many of you have built sites that has had requirements for editors to manually clear pages? Like that's a super common thing. Okay, only like a dozen or so. That's interesting. Many people are like, eh, interesting. I guess because many Drupal 7 sites are always clearing things. In any case, this makes sure for the content cache, this is an example with content, but it's also true for configuration, things like block placement, panels and so on. Anything that is related to rendering, anything that might eventually impact rendering should have test coverage like this because it's proving without a doubt and you will know if there is a regression that things are updated right immediately when they have happened. So this is black box testing. It's just checking I make a change. Okay, it shows up without any extra measures. If we go a little bit further, there's white box testing. So we have the same sort of logic. So we have again an assertion method, but this time it has additional parameters, dynamic page cache and page cache. It's still receiving the title as well. What we're doing now is we're still asserting the initial title is there, but we're also asserting that dynamic page cache initially is a miss, page cache is initially a miss. But then if we make another request, dynamic page cache is a hit and page cache is a hit. This looks like super annoyingly simple. Yes, I know. But still it's just meant to convey how you go about testing these things. And I hope this is convincing enough that it isn't as complex as you might think it is. So again, initially both miss, then both hit, then we change the content. We have again two misses, but we do have the correct title, but then afterwards we have hits again. So we basically are each time priming the caches and then we are using the caches. That's all there is to it. Similarly for cache contexts, again black box testing, we're again asserting a thing. We're asserting that something is visible. We're again retrieving a page and we're asserting that both as the admin and the anonymous user, we are able to see this title. So true as in it is visible for the admin user, true it is visible for the anonymous user. So in this assertion method, what we're doing is we're logging in as the administrator. We are making that request and asserting that the title is visible we're logging out, which means we're anonymous and we're making an assertion that the title is again visible. So initially it's visible for both admin users and anonymous users. Then we unpublish the node and suddenly it's only visible for admin users anymore and not for anonymous users. That's sort of testing the different variations and testing the real world schemes or the real world situations that you will encounter. That is necessary to have strong assurances that you are caching things correctly and you're not divulging information, you're not serving stale data. And as you can see, it doesn't need to be complex. It can be literally as simple as this. And these tests, like I didn't omit anything, these are complete tests. And again here, it's possible to do white box testing where we are again looking at dynamic page cache and page cache. But in this case, because now we have an admin user. An admin user is a logged in user, which means they have a session, which means that page cache, which is the second parameter for a third thing, or sorry, the third parameter, for the admin user, that means that page cache should always not be there. Like there shouldn't be a ex-drupal cache header because as an admin user, you have a session, which means that you should never have a page cache entry. So this is also verifying that we're not accidentally creating entries in the page cache for the anonymous user, or as the anonymous user, which would allow the information to be divulged to anonymous users. This is perhaps a bit dry, but I think it is valuable to know. So testing. It's important because otherwise, things may be working in very unexpected ways. Even if you have test coverage, if it's not testing these variations in edge cases, you are in for potential trouble. So with that, are there any further questions? Questions about big buy, questions about dynamic page cache, render cache, everything was clear. Okay, thank you.