In this presentation we introduce Drupal 8's Cache API, review its metadata settings and demonstrate D8's caching flexibility using a fun, real-life example. The fun part is in the form of tacos because they're the de facto currency of the HeyTaco! Slack integration, from which we will grab our cacheable data. The idea is to discuss the Cache API and give a practical example of Drupal 8 caching in action. Since this presentation is geared for beginners, there's added value for the audience in seeing the many moving parts that are used along the way!
Main Focus:
- Explain D8 Cache metadata settings
- keys
- context
- tags
- max-age
- Demonstrate how we can cache results differently for different users
- Stress the importance of using render arrays
Added Value:
- Build a custom module to create the custom block to be cached
- Grab data from a 3rd party API
- Use Twig to display the custom block's output
This presentation makes no assumptions and begins with the question "Why cache at all?" From that modest beginning we progress through all the above points and finally reach our Cache-tastic conclusion. The title is about caching and tacos, but the presentation is about so much more!
Transcript
Märt Matsoo: The title of my presentation from the actual brochure was Drupal 8 Cache API and Tacos, a delicious real world example. Let's get started.
First of all introduce myself. My name is Märt Matsoo. I'm originally from Canada, from Toronto, but nowadays I live in Estonia, that's in Eastern Europe. I got into Drupal about 10 years ago because I had moved to Estonia to work for the Estonian foreign ministry. I looked on Drupal as the content management system I selected at the time to build their website, which is why I had gone there and taken the job. I've been working with Drupal ever since, and since the summer of 2015, I've been working for Chromatic.
Who is Chromatic or what is Chromatic? Chromatic is a rather small 15-people strong currently, Drupal-focused agency that we have a lot of front end and DevOps experience and expertise as well. It's been around for over 10 years. It's distributed, so we don't have a head office, we don't have any office. People work from wherever they live, of course. We have two people, two Canadians, who happen to live in Europe, and the rest of us are stateside, ranging from Colorado to the East Coast.
Let's get into the presentation. As I said, [laughs] it's animation heavy. In my description, I said that I wasn't going to make any assumptions. I'm going to start from the very basics as to why we should cache at all. I like to think of an analogy of a Big Mac. Imagine how long it would take to get a Big Mac if you actually had to make it yourself. You go to McDonald's, you order it, you're out in about a minute or two and you've got your Big Mac.
What if you had to do it yourself? You would basically have to go to the supermarket, find all the right ingredients, take everything home, make the patty, make the special sauces, cut everything up. It's a difference of minutes. For me, I'm sure it would take hours to make an actual Big Mac. The analogy with a cache and a non-cache page is basically the same in my mind.
A cache page is something that's already pre-prepared, and when a request comes forward, you say, "Oh, we already have it ready. Let's show it to you," versus a non- cache page, which is basically we're asking the server to put it together much like a recipe would be and it's going to make all sorts of database calls, compile all the information and send it back to you. When I was preparing and actually building this solution, or whatever you want to call it that, the difference was milliseconds and seconds on my local environment.
When I would load a cache page, it took 281 milliseconds, versus almost three seconds for the non-cache version of the exact same simple page. Apparently, I haven't spoken with them myself, but you can find this on the internet. Amazon had done some research and they determined that for every 100 milliseconds of latency, they cost 1% in sales. It shows the importance of speed, and why you have to have some robust caching strategy as well so that your [unintelligible 00:03:28] can be happy.
Before we move into the tacos, I just do a quick mention of Drupal render arrays. Why? It's because render arrays are the building blocks of any Drupal page. I'm just going to briefly touch on them here, but one of our fellow Chromatic workers, Gus Childs, did a great presentation about render arrays at Drupal Con, New Orleans, two years ago. If you google Gus Childs render arrays, you'll find a great presentation all about them.
Render arrays were introduced in Drupal 7 and they allow a lot of flexibility in overriding, altering and extending parts of the page. If you look in Drupal 7 templates, this is where you see stuff like print render content. I hope you can see that. That content variable is basically the render array and the render function then can display the actual. It's important that our render array is the thing that render case develop knows to cache itself. This goes hand in hand and from DO it is of the utmost importance that you inform the render API of the cacheability of a render array.
This presentation was originally a blog post, called the Taco-Friendly Guide to Cache Metadata in Drupal 8. The reason I thought of writing a blog post was, at work as I'm sure many of you do, we use Slack and we were using Slack integration called HeyTaco. Does anyone know what this is? Has anyone used it and uses it? Great. It was surprisingly popular for our team. What a taco is, it's this integration that allows you to share taco emojis with your coworkers. If somebody does something great, you say, "Hey, I'm going to send them a virtual taco."
Basically, as you're writing out your taco emoji, it shows that. In that final screenshot there, Larry had sent me a taco for something that I had done. The HeyTaco bot, basically compiles the number of tacos that you've earned and it keeps track of how many you've sent to your coworkers. You can always send five a day. You can't go willy nilly. These things are worth something.
[laughter]
When you want to know how things are going, you can send the bot a message with the keyword, Leaderboard, and then it spits out the Leaderboard. I thought that would be interesting. I wonder if HeyTaco has an API, we could actually get our HeyTaco stats out into the real world and onto our website. We never actually put it onto the website, but this is a prototype, proof of concept so to speak.
I went to the HeyTaco website and sure enough, they do have an API. It was fairly simple to make a call with your unique team ID and it would spit back a JSON response, that you can then do whatever you want with. I started thinking, I'm going to build a custom module, and we're going to grab the information. That's where I got introduced also to the cache metadata and how you can play with it. I'm getting the API call and just throwing a quick piece of code from my custom module here, to the place to stick it in. It's just using guzzle and it just asks for the information point, HeyTaco and get the JSON response.
Here's the plan. First of all, what I did is I use drush core quick Drupal to spin up a quick, standalone version of Drupal. Does anybody use drush core quick Drupal? Just curious, familiar with it at all? This is a one-line command, I think it's just great [chuckles]. You can get a standalone version of Drupal running on its own web server using a SQL-like database and you can play around with any new modules that you haven't come across, and you're not selling any other development environment, that or maybe client environments or whatever. You can just be in any folder, run drush core quick Drupal, or the aliases QD, or my personal favorite is "cutie".
Internally, basically run drush QD, and then it takes about a minute. If you've done it already, and you have certain elements cached on your system, it takes literally seconds to spin up a totally vanilla self-contained version of Drupal that you can start playing with. I really recommend looking into it. It gives you the opportunity to say, I wonder if this module does this or that without actually having to go into a bigger development environment that you might have on your machine. That's a core for Drupal.
I also built a custom module called HeyTaco. That's just [laughs] what my module looks like in a folder system. That's going to create the custom block that we're going to display. The custom code that I showed, a couple of steps back is how we grab the information from the HeyTaco API, and then now we're getting into the actual cached block that gets produced. We're going to cache it differently for the different users in our system.
Just to have a little bit of fun, since I got the response from HeyTaco, we have three partners at Chromatic and because we want to keep them happy, I'd add some tacos on to their totals before displaying it. They think they've gotten a lot of tacos, whereas they haven't gotten quite as many.
[laughter]
At the end, we also have a twig template that we're going to display the leaderboard on. One thing I like about this presentation is that it shows a lot of the moving parts of Drupal, and it was basically hoping to show things that maybe not everyone has come across as I've benefited in other presentations when someone does something that, "Oh, I didn't know you could do that," or, "I didn't know that existed."
Going back to the partner versus non-partner leaderboards. Here is a partner leaderboard, our partner Chris, for instance, who's in first place where it says, 486 tacos. He looks at this and Dave is another partner, he's in second place, and they can say, "Wow, we're pretty awesome." It's true, they are. We have Alana, who's one of our workers. You'll see there are asterisks next to the partners' totals. It says at the bottom, the partners' results are padded by 100 tacos. The point with this is also that all these versions of this one block are cached and they're cached differently for the different users that see them. It's using the cache metadata that allows this to happen. In my custom module, in the block plugin I created, I have a build function. This is basically creating the block that if you're in the admin UI, you're going to see the block is available to be placed anywhere on the layout of the page, just like any block that you would find at that page.
Now, we're going to start talking about the actual cache properties metadata. These are keys, contexts, tags, and max-age. Now, we're going to go through them one by one, and introduce them. Going back to the function, [unintelligible 00:10:57], you have the cache property. Then it's got its properties of keys, contexts, tags, and max- age. Let's go through them.
Keys. This is what identifies the thing I'm rendering. We're telling your bot. This basically like a primary key for it. Mine is called HeyTaco_block. Since HeyTaco is a unique custom name from my module, I was pretty sure it wasn't going to conflict with anything else in the system. It can be more than a single string. This example, you probably have difficulty seeing that but this is from the Views module. It's using actually ViewViewID DisplayDisplayID to make this unique name for it. Keys must only be set if the render array should be cached. In general, we want to cache as much as we can so you're generally going to be using having that key property. By adding the key property, you're telling Drupal, "Yes, we're going to have caching with this."
Contexts is the next one. Contexts tells Drupal that this is going to look different based on something. Does the representation of the thing I'm rendering vary for something. To me, this is the 'which', as in, which version of the block should be shown. In my case here, I'm using the user context, because we're going to show the block differently to different users. There are all sorts of different contexts you can use. Cookies, IP, theme, timezone, for instance, you could see if someone's at night, you can vary the look of it, versus during the day. Then if it's during the day, you can tell the cache to clean itself and give the daytime version of it.
There's more in your modules. If you will like, you can define your own. There's a list of the cache contexts that are available in the core services YAML file. It's a long list to use.
Now, tags. Tags are probably the most interesting. What does it depend upon, so that when that data changes, so should the representation. The tags is what tells Drupal that if something changes, you have to invalidate the cache and get a fresh version of it. In my case, we're using a value of user_list. Now, when I was developing this-- Sorry. I'll just go back and say, the tags have to be a string without any spaces in them. It's generally in the form of entity type and entity ID. They can be in sets. If you see there, it has User3, User4, User5. When I was initially developing this, I didn't know that there was a value of user_list to be used. The caching wasn't working the way I wanted. Then I found out that, okay, I actually have to pass in an array with all the user IDs that we're using. That was an extra step and it felt a little bit like way too much manual work to build an array of all the users and then pass it into the tags property.
Again, mentioning Gus, he was reviewing my blog post, and he said, "There's something called node_list. I wonder if there's something called user_list that you could use instead." I tried it, and it did. User_list is just a short way of telling Drupal that if a user changes something in their profile, invalidate the cache.
Just as an example, here's a screenshot of a leaderboard, and it has my username "mart". Hi Märt. Then when I change my username to Märt Matsoo then that would be in the tag to say for the event and the tag knew to now invalidate that cache and start building a new version, a fresh version.
Max-age. This is fairly self-explanatory. How long should the cache be valid for? The maximum amount of time to cache this rendering. It's measured in seconds. Here, I've got 3600, which is one hour. It defaults to cache permanent. Basically, Drupal wants by default to cache it for as long as possible. You can determine how long until it should invalidate itself and build a fresh version.
A little bit of helpful info from drupal.org. The cache, the contexts, the tags, and the max-age must always be set, because they affect the cacheability of the entire response, and they bubble. The parents i.e. the containing page of my HeyTaco_block in this case, automatically receives those instructions too. It knows that it has now a stale copy of itself, and it should update itself.
Just to remind you that the cache keys must only be set if the render array should be cached. Basically, if you're going to put the keys property in there, you're telling Drupal, "Yes, I want to cache it." Then finally, when we talk about theming, this part from my build function theme, HeyTaco_block. Underneath that, you see there's results, partner asterisks, blurb, display name. Those are the variables that I'll be passing to my template.
In my HeyTaco module file, which is where I have my hook theme implementation, this is where I set these variable names. In my files, you see that the naming convention, HeyTaco_block, has to match the name of the twig template, except the underscore is replaced with a dash, and it's in the templates directory. That will get picked up automatically to be used as the template for this custom block that I've created. Basically, the twig template looks like that, and it's creating the leaderboard block that looks like this. If you can see, at the top of the leaderboard is NNH2. Then if the display name can be rendered, and it's not empty, it says, "Hi" with your username. Then it loops through the result and displays them. Then you have the partner asterisks blur at the bottom if it's not empty. That was about it.
In cache, as the conclusion. The cache metadata keys, contexts, tags, and max-age that render arrays in the cache property go hand in hand. You can't really build a render array without thinking about the caching strategies for it and telling Drupal how you want to cache it. Going back to the drush core quick drupal, it is really helpful in playing around with it. I found that developing this blog post and the presentation was just really, really useful just to be able to get your hands dirty in the code and see what different settings did with the cache output.
Then my actual sample code for this is up on our Chromatic HQ, GitHub, repo. I'll have these slides put up at some point. With all the links and the references, you'll be able to see them from there. That's it. Thank you very much.
[applause]
If you want to ask any questions, I can try to answer them. Please.
Speaker: When you talk about contexts, you use user_list, you're not going to delete cache when the number of tacos changes on a user. It could not be both?
Märt Matsoo: That's actually a good question. The question was, using user_list as a tag, technically not the context but to change or invalidate the cache when the tacos change. I hadn't actually looked at it from that point of view, because our tacos could be changing every minute. If you notice, that's what I had for the max-age. Then all of the blocks would refresh themselves with the new values. I wasn't keying off the taco number. In fact, I'm wondering if I could even tell it without some custom code. Right now, I'm leaning towards no. [chuckles]
Speaker: [unintelligible 00:20:04]?
Märt Matsoo: Yes, the question was, if you clear the cache through core at the address CC or CR would that invalidate this cache? Yes, that'll clean out all the caches.
Speaker: The block system inter-core that block interface class has methods or cache context, cache text, et cetera, but I see that you put the information like right in the render array, what's the benefit of using one of those over the other?
Märt Matsoo: That's a good question that I can't definitively answer. You're talking about when you're letting Drupal, you do something like get cacheable tags, methods like that, and it produces them itself. I'm not sure because I didn't look at that part of it. I was going by the cache API, the documentation that I found and looked at the examples there that all seemed to have, rather than having methods pass it in and build the tags and the context for you or the times, at least. I did it all by hand. That's something I would have to look into.
Anything else? All right. Thank you very much for your attention. [unintelligible 00:21:32].
[applause]
If you do have a question you can also message me on Twitter and then I'll do my best to actually get an answer to you. I'm better at Googling than standing on stage.
[00:21:49]
[END OF AUDIO]