If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe best part about roles is they're shareable. There are a ton of third-party roles that you can download to give your playbook free stuff! I love free stuff!
Refresh the page in the "dev" environment. The page took over 2 seconds to load! Why? In DefaultController
, our app is trying to use Redis:
... lines 1 - 9 | |
class DefaultController extends Controller | |
{ | |
... lines 12 - 14 | |
public function indexAction() | |
{ | |
... lines 17 - 20 | |
// Redis cache | |
try { | |
if ($this->getRedisClient()->exists('total_video_uploads_count')) { | |
$totalVideoUploadsCount = $this->getRedisClient()->get('total_video_uploads_count'); | |
} else { | |
$totalVideoUploadsCount = $this->countTotalVideoUploads(); | |
$this->getRedisClient()->set('total_video_uploads_count', $totalVideoUploadsCount, 'ex', 60); // 60s | |
} | |
if ($this->getRedisClient()->exists('total_video_views_count')) { | |
$totalVideoViewsCount = $this->getRedisClient()->get('total_video_views_count'); | |
} else { | |
$totalVideoViewsCount = $this->countTotalVideoViews(); | |
$this->getRedisClient()->set('total_video_views_count', $totalVideoViewsCount, 'ex', 60); // 60s | |
} | |
} catch (ConnectionException $e) { | |
$totalVideoUploadsCount = $this->countTotalVideoUploads(); | |
$totalVideoViewsCount = $this->countTotalVideoViews(); | |
} | |
... lines 40 - 46 | |
} | |
... lines 48 - 109 | |
} |
But, it's not installed! So, this fails... and just for a good example, our code rescues thing but sleeps for 2 seconds to "fake" the page being really slow:
... lines 1 - 9 | |
class DefaultController extends Controller | |
{ | |
... lines 12 - 70 | |
/** | |
* @return int | |
*/ | |
private function countTotalVideoUploads() | |
{ | |
sleep(1); // simulating a long computation: waiting for 1s | |
$fakedCount = intval(date('Hms') . rand(1, 9)); | |
return $fakedCount; | |
} | |
/** | |
* @return int | |
*/ | |
private function countTotalVideoViews() | |
{ | |
sleep(1); // simulating a long computation: waiting for 1s | |
$fakedCount = intval(date('Hms') . rand(1, 9)) * 111; | |
return $fakedCount; | |
} | |
... lines 94 - 109 | |
} |
In other words, without Redis, our site is slow. But after we install it, the page should be super quick!
We already have all the skills needed to install Redis. But... maybe someone already did the work for us? Google for "Redis Ansible role". Hello DavidWittman/ansible-redis
! This role helps install Redis... and it looks fairly active. And check it out: it looks like our role, with templates
, vars
, handlers
and a few other things.
So... if we could download this into our project, we could activate the role and get free stuff! The way to do that is by using a command called ansible-galaxy
. Copy it! Then, find your terminal and paste!
ansible-galaxy install DavidWittman.redis
In my case, it's already installed.
By default, ansible-galaxy
downloads roles to a global directory. When you tell Ansible to load a role, it looks in your local directory but also looks in that global spot to find possible roles.
You could also download the role locally in your project. Add --help
to the command:
ansible-galaxy install DavidWittman.redis --help
The -p
option is the key! Downloading the role locally might be even better than downloading it globally. When it's in your project, you can commit it to your repository and manage its version.
With the role downloaded, all we need to do is activate it! Easy! Copy the role name. Under our roles
, I'll use a longer syntax: role: DavidWittman.redis
then become: true
:
- hosts: vb | |
... lines 3 - 35 | |
roles: | |
... line 37 | |
- role: DavidWittman.redis | |
become: true | |
... lines 40 - 173 |
If you tried the role, you'd find out you need that. We didn't need it for the nginx
role because we had the become: true
lines internally.
Ok team, run the entire playbook in the dev
environment:
ansible-playbook ansible/playbook.yml -i ansible/hosts.ini
While we're waiting for someone else to install Redis for us - thanks David - head back to the documentation. The main way to control how a role works is via variables, like redis_bind
in this example:
---
- hosts: redis01.example.com
vars:
- redis_bind: 127.0.0.1
roles:
- DavidWittman.redis
It's cool how it works: internally, the role sets some variables and then uses them. We, of course, can override them. Simple, but powerful.
There is one downside to third-party roles: they can add some serious bloat to your playbook. Yea, it's running a lot of tasks. Often, a role is designed to work on multiple operating systems and versions. Some of these tasks are determining facts about the environment, like what Ubuntu version we have or what utilities are available. The role is really flexible, but takes extra time to run.
And, this first execution will take a long time: it's installing Redis!
Finally... it gets into our stuff.
Ding! Let's try it! Refresh MooTube. Then, refresh again. Yes! 29 milliseconds! Amazing! So much faster!
This works because our application is already configured to look for Redis on localhost. So as soon as it was installed, our app picked it up.
Next, let's talk more about configuration and how you might control things like the Redis host or database password.
Hey Kirill,
Thank you for this tip! Could you also share more information how exactly you need to remove that protected-mode
param? In what config file?
Cheers!
Hi guys, why application running through 'app_dev.php' is much faster than 'app.php' (20ms vs 1000ms)?
Redis is working in both cases (just added some 'echo' in 'countTotalVideoViews' method to make sure that the value is taken from Redis)
Hey Дмитрий Политов!
Fascinating! That... obviously... should not be happening ;). Are you able to repeatedly make a request through app.php and see the slow performance? The first thing that occurred to me is that, unless you pre-warmed the cache, the first request through app.php (really, through either file) will build the cache, which is a very slow process.
Let me know!
Cheers!
Yes, everytime I make request through 'app.php' it takes about 1000 ms with warmed cache (and ~2000ms with cold cache), but running app through 'app_dev.php' is really fast - just about 20-30ms with warmed cache. Some kind of magic)
Hey Dmitriy,
That's very strange! Are you on Windows? What OS do you use? And how are you running the website? With new "symfony serve" command or with "bin/console server:run"? Could you try to remove var/cache/ folder completely and without warming up the cache go to the website? Is it much slower? The 2nd request is faster but still ~2000ms? Btw, could you try to turn your antivirus off and try again?
Cheers!
Hey, Victor, I'm on MacOS. For this course I use 'ubuntu/trusty64
' vagrant box. I have added virtual machine ip in my /etc/hosts
file and simply run <b>http://mootube.l/app.php</b> and <b>http://mootube.l/app_dev.php</b> in Chrome
Cache works in both cases (just add echo 'cache is empty
' in method, so the first time when I run app with cold cache I see this echo and after that I don't see it when refresh page). Just strange, why response from mootube.l/app.php is slower than mootube.l/app_dev.php
Hey Dmitriy,
Oh, you know what? I think I have an idea. IIRC we had this try-catch block:
try {
// ...
} catch (ConnectionException $e) {
$totalVideoUploadsCount = $this->countTotalVideoUploads();
$totalVideoViewsCount = $this->countTotalVideoViews();
}
So, if something is wrong and the Redis client throws a ConnectionException - you go to the catch() block and that's where both methods are called, i.e. that's where you wait for 2 seconds. That's the key I think! Please, double check that you don't get into the catch block in production, i.e. when request app.php. Not sure why exactly you may go there, you probably should debug it. Probably your configuration is different for the prod environment that's why Redis can't connect, or maybe some kind of permissions issue.
Cheers!
Hey Dmitriy,
Hm, if you have things are completely automated (I suppose you should so far), I'd recommend you to create another different vagrant box and try the same there. Probably it will fix the problem. Unfortunately, I don't see any obvious problems why it may happens, especially if you say the cache works in both cases :/
Cheers!
Hey guys,
I went back to chapter 25 because I want to use redis in my app to take it out the session. Do you recommend to install redis in the ec2 server or do I need to create another server in AWS Elasticache for example? My app have 150 users per day but not simultaneously. I would appreciate any advice.
Cheers!
Hey Cesar,
Hm, 150 users per day probably sounds ok even without Redis ;) Why do you want to use Redis in your application? What is the reason behind? I think you should not have any performance problems with your current load, so I suppose you just want to practice, right? Anyway, I think you can install Redis on the same ec2 instance where you run your project, it will cost you less, because it's easier to configure and you don't have to pay for AWS Elasticache. And in any case, you can move to AWS Elasticache later when you do not have enough resources on your ec2 server (not upgrade your EC2 instance to a new price plan).
Cheers!
Sorry Victor, I didn't explain you the reasons. I want to use redis because I need to take out the session of my code because:
- Everytime I deploy another version of my code, using Ansistrano, all my users are kicked out and they need lo login again. I was thinking to put the session in the shared folder but I was not sure of that.
- Thinking in long term, I will want to deploy my code in more than one server (autoscaling) and, as Ryan explain here https://knpuniversity.com/s..., I need to put the session out. I know my app won't have a lot of users but I want to ensure its availability.
What would be your recommendation for my case? I hope you can help me.
Hey Cesar,
Ah, ok, makes more sense.. but everything what I said is still relevant - you probably can save some money and launch Redis on the same EC2 instance. Well, on KnpU we keep session in DB, but we use AWS RDS to decouple data and session from the EC2 instance, that gives us some advantages, e.g. we can easily drop the EC2 instance, create another one and just link DB from it and nobody will lost their session and data. So, it's cheaper than using AWS Elasticache because you need a DB anyway. Well, if you want this feature i.e. when you can easily drop your EC2 instance and launch another one without loosing session, then you can consider storing session into AWS RDS or in AWS Elasticache. If you're not sure you need this feature - not a big deal, you can just do not overwrite path to session in config, i.e. use default PHP session storage that is somewhere internal in the Linux and in this case you won't lose session on every deploy anyway, but you will lose it on drop/launch EC2 instance. Btw, we're talking about it here: https://knpuniversity.com/s... - I recommend you to re-watch this video again. So the question is do you often to drop/launch instances on AWS? :) Probably your EC2 instance may work for years. So, here's some outcomes, but the choice is yours ;)
Cheers!
Thank you Victor. I also use AWS RDS because it was a security requirement in my company the decouple data from the EC2 so, it's a good idea to use it and save some money.
I was reading some blogs about it and some people say that MySQL could have less performance than Redis for storing session but I think in my case, it doesn't make a considerable difference.
I am going to follow this http://symfony.com/doc/3.4/... for the configuration
Hey Cesar,
> I was reading some blogs about it and some people say that MySQL could have less performance than Redis for storing session but I think in my case, it doesn't make a considerable difference.
Yes, that's true, Redis probably would be faster, but I think MySQL should be enough for your case. Anyway, you always can switch to Redis in the future when you start having performance issues.
And yeah, you found the right article in docs for it 👍
Cheers!
Just a thought. You should easily fit your data into 30 MB and therefore host it for free on Redis Cloud https://redislabs.com/redis...
Hey Dave,
Thank you for this tip! Good to know that Redis Cloud have 30 MB free plan, that's nice!
Cheers!
Following the tutorial and I got the message below, and it's like the play book stop at the "Install low-level utilities" task. Is it from apt not update? Thanks :
TASK [Install cow say's - it's probably important] ******************************************************************************************
ok: [192.168.33.10]
TASK [{{ item }}] ***************************************************************************************************************************
failed: [192.168.33.10] (item=zip) => {"failed": true, "item": "zip", "msg": "one of the following is required: package,upgrade,update_cache,deb,autoremove"}
failed: [192.168.33.10] (item=unzip) => {"failed": true, "item": "unzip", "msg": "one of the following is required: package,upgrade,update_cache,deb,autoremove"}
to retry, use: --limit @/home/sokphea/Documents/knpUniversity/Ansible/start/ansible/playbook.retry
PLAY RECAP **********************************************************************************************************************************
192.168.33.10 : ok=30 changed=2 unreachable=0 failed=1
Hey Chea,
Looks like you have a typo in your "Install low-level utilities" task, probably some indentation problems. As you can see, the "Install low-level utilities" task name isn't shown in your output. Could you show the code of it?
Cheers!
I did got an error with restating above and https://github.com/DavidWit... helped me to get rid of it.
Hey Nia,
Thanks for sharing it! So, you changed the default Redis port and that's why you saw the error, right?
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.12
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.0
"snc/redis-bundle": "^2.0", // 2.0.0
"predis/predis": "^1.1", // v1.1.1
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.8
"symfony/phpunit-bridge": "^3.0", // v3.1.4
"doctrine/data-fixtures": "^1.1", // 1.3.3
"hautelook/alice-bundle": "^1.3" // v1.4.1
}
}
If you faced the same issue as I me:
Failed to start Advanced key-value store
try to download the package locally and removeprotected-mode
param from the config files.