Priming cache.app

Watch closely: our production site is super slow! It takes a few seconds to
load! What!? It's especially weird because, locally in the dev environment, it's
way faster: just a few hundred milliseconds!

On the homepage, we show the total number of videos and the total number of video
views. To get these, we first look inside a cache: we look for total_video_uploads_count
and total_video_views_count. If they are not in the cache, then we calculate
those and store them in the cache.

To calculate the number of videos, we call $this->countTotalVideoUploads():

134 lines src/AppBundle/Controller/DefaultController.php

... lines 1 - 9

classDefaultControllerextendsController

{

... lines 12 - 93

/**

* @return int

*/

privatefunctioncountTotalVideoUploads()

{

sleep(1); // simulating a long computation: waiting for 1s

$fakedCount = intval(date('Hms') . rand(1, 9));

return $fakedCount;

}

... lines 105 - 132

}

That's a private method in this controller. It generates a random number... but
has a sleep() in it! I added this to simulate a slow query. The countTotalVideoViews()also has a sleep():

134 lines src/AppBundle/Controller/DefaultController.php

... lines 1 - 9

classDefaultControllerextendsController

{

... lines 12 - 105

/**

* @return int

*/

privatefunctioncountTotalVideoViews()

{

sleep(1); // simulating a long computation: waiting for 1s

$fakedCount = intval(date('Hms') . rand(1, 9)) * 111;

return $fakedCount;

}

... lines 117 - 132

}

So why is our site so slow? Because I put a sleep() in our code! I'm sabotaging us!
But more importantly, for some reason, it seems like the cache system is failing.
Let's find out why!

Hello cache.app

First, look at the getAppCache() method:

134 lines src/AppBundle/Controller/DefaultController.php

... lines 1 - 9

classDefaultControllerextendsController

{

... lines 12 - 125

/**

* @return AdapterInterface

*/

privatefunctiongetAppCache()

{

return$this->get('cache.app');

}

}

To cache things, we're using a service called cache.app. This service is awesome.
We already know about the system.cache service: an internal service that's used
to cache things that make the site functional. The cache.app service is for us:
we can use it to cache whatever we want! And unlike system.cache, it is not
cleared on each deploy.

So why is this service failing? Because, by default, it tries to cache to the filesystem,
in a var/cache/prod/pools directory:

ls var/cache/prod/pools

On production, we know that this directory is not writable. So actually, I'm
surprised the site isn't broken! This service should not be able to write its cache!

Let's see what happens. Refresh! Huh... the site works... but it's slow. And
the web debug toolbar is reporting a few warnings. Click to see those.

Woh! There are two warnings:

Failed to save key total_video_uploads_count

and

Failed to save key total_video_views_count

Of course! If caching fails, it's not fatal... it just makes our site slow.
This is what's happening on production.

Let's fix the permissions for that directory:

sudo chmod -R 777 var/cache/dev/pools

Production Caching with Redis

So how can we fix this on production? We could make that directory writable, but
there's a much better way: change cache.app to not use the file system! We
already installed Redis during provision, so let's use that!

How? Open app/config/config.yml. Actually, use config_prod.yml, to only use this
in production. Add framework, cache and app set to cache.adapter.redis:

30 lines app/config/config_prod.yml

... lines 1 - 3

framework:

cache:

app:cache.adapter.redis

... lines 7 - 30

cache.adapter.redis is the id of a service that Symfony automatically makes
available. You can also use cache.adapter.filesystem - which is the default -
doctrine, apcu, memcached or create your own service. If you need to configure
Redis, use the default_redis_provider key under app, set to redis:// and then
your connection info:

When the deploy finishes... try it! The first refresh should be slow: it's creating
the cache. Yep... slow... Try again. Fast! Super fast! Our cache system is fixed!

Do Not Clear cache.app on Deploy

As we talked about earlier, the cache.system cache is effectively cleared on each
deploy automatically. But cache.app is not cleared on deploy... and that's good!
We're caching items that we do not want to automatically remove.

Actually... in Symfony 3.3, that's not true: when you run cache:clear, this does
empty the cache.app cache. This is actually a bug, and it's fixed in Symfony
3.4. If you need to fix it for Symfony 3.3, open app/config/services.yml and
override a core service:

41 lines app/config/services.yml

... lines 1 - 5

services:

... lines 7 - 36

# Prevents cache.app from being cleared on cache:clear

# this bug is fixed in Symfony 3.4

cache.app_clearer:

class:Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer

The details of this aren't important, and if you're using Symfony 3.4 or higher,
you don't need this.

Oh, and if you do want to clear cache.app, use:

bin/console cache:pool:clear cache.app

Leave a comment!

2018-02-27Victor Bocharsky

Yo Cesar,

Webpack Encore is going to be completely released in a few weeks, as you might notice we're working on it right now and trying to release a new video every day. And Webpack Encore course will be FREE for everyone! But if you're looking for buying something, take a look at paid Webpack course which is already completely released :p

Cheers!

2018-02-27Cesar

Thanks Victor. When are you calculating to finish the Webpack Encore course? I am going to buy it or sure.

2018-02-27Victor Bocharsky

Hey Cesar,

Do you mean watching Webpack course before Webpack Encore? Actually Encore is just a simple wrapper around Webpack which makes your work with Webpack library drastically simpler. So, you can start directly with Encore course, but if you need to dive deeper into Webpack - you need the Webpack course. However, I'd recommend to see Webpack course to understand Webpack and Webpack Encore better.

If you mean AsseticBundle - Webpack Encore is the tool which completely replaces this bundle and adds you more more features out of the box. So if you're going to use Encore - forget about AsseticBundle at all.

About HTTP cache: it depends on your project, if you have a simple website with a lot of public content (not user-specific pages or user-specific blocks on each page) - it makes sense. Otherwise it could be even overhead in some cases. Anyway, HTTP cache complicates things a lot and you need to start thinking about cache invalidation on deploys. I'd recommend you to look at Symfony Cache Component first that are enough in most cases and that is much simpler.

Cheers!

2018-02-26Cesar

Thanks Ryan for answering. That's exactly my issue with CSS and JS. I would like to buy your tutorial when it's complete. Do I need to watch first another tutorial before to enter in this one?. Also, meanwhile, do you recommend me to use ascetic or http-cache?

2018-02-26weaverryan

Hey Cesar!

Interesting! So.... the answer depends on what you mean exactly by "some of my users are still watching the previous version". On a PHP level, during deploy, 100% of the users are still using the original PHP code path. After deploy, 100% of the users are using the NEW PHP code path (as soon as the symbolic link changes, all requests use the new code). However, if you're referring to JavaScript or CSS, then it's more interesting :). If you make an update to a JS or CSS file and deploy, the new JS or CSS file will be deployed just like any other file. However, the user's browser might still use the old, *cached* version.

Is this your issue? If so, you need to use a cache busting strategy. I *highly* recommend using Webpack Encore, as it will handle cache busting / versioning automatically. We're creating a screencast about this right now - https://knpuniversity.com/s... - but you can find details here: https://symfony.com/doc/cur...

Cheers!

2018-02-26Cesar

Hi. I am using ansistrano to deploy my symfony app and it's working very well. However, sometimes when I release a new version, some of my users are still watching there previous version. I think this is related to the cache service. Do I need to do any configuration for that? I want that everybody can watch the new release immediately after the deploy. I hope you can help me.