Tag Archives: VirtualBox

How to improve Symfony2 performance when running from shared folder on VM

Quite recently we, meaning PrepLounge, the startup I’m CTO at, got started on a new project and I chose Symfony as the framework to work with.

After reading the docs and running through the, fairly simply, install/setup steps I ran into a problem: Performance was really bad. A single request to the default controller (no changes where made to the AppBundle) took up to 8 seconds. This was unacceptable.

I googled about Symfony being slow and followed all the steps to improve the performance like:

  • Use bootstrap files
  • Enable APC for autoloading
  • Use composer’s class map functionality

Which is pretty much the same that’s listed on Symfony’s own site about performance improvements. And I even disabled Xdebug (on my dev system mind you, something unthinkable until now) in hopes that this might be the culprit. But all to no avail. Sure, every improvement shaved off a couple hundred milliseconds, but the best result I got was about 2 seconds per request. Which was still unacceptable.

I then dug deeper into the Symfony docs, trying to figure out how all the caching works and after reading how much file reading/writing is done for each request I started to suspect a completely different reason for the weak performance: My VM I use development.

My VM is a VirtualBox with Ubuntu 14.04 as guest system running on my MacBook Pro with OSX 10.11.1 as host. For all previous projects, the code always resided on the host and the VM gets access to it via shared folders.

And that’s exactly the problem. Apparently file reads/writes in shared folders are very slow compared to reads/write on the local file system. I had no idea. Sure, I figure there’d be some impact, but not that much.

Anyway, now that I had my suspicions I made some changes to my setup.

Previous setup:

  • Shared folder at /media/sf_project
  • Symlink from /media/sf_project to /opt/www/development/project
  • DocumentRoot at /opt/www/development/project/web

New setup

  • Shared folder at /media/sf_project
  • Actual copy of /media/sf_project at /opt/www/development/project
    • Updated every minute per rsync
  • DocumentRoot at /opt/www/development/project/web

And that did it!! Requests are blazing fast and take less than 100ms!

The only problem is, that the fastest a cronjob can run is once per minute. Which is probably fine in most cases, but for me as a developer to wait up to one minute for my changes to take effect is a nightmare! Especially when you are debugging or editing your CSS and only make small changes which you quickly want to check.

So I wrote a quick script that does exactly what the cronjob does: Run rsync to sync the folders and then set permissions. And then I updated the cron to run the script instead of having duplicate code. DRY ftw ;)

Here’s the script:

#!/bin/bash

if [ "$1" == "" ]; then
    echo "Syncing project files..."
    sudo rsync -rptz --stats --exclude 'app/logs/*' --exclude 'vendor/*' --exclude 'app/cache/*' /media/sf_project/* /opt/www/development/project
    sudo rsync -rptz --stats /opt/www/development/project/app/logs /media/sf_project
    sudo cp /opt/www/development/project/composer.json /media/sf_project
    sudo cp /opt/www/development/project/composer.lock /media/sf_project
    echo "Setting permissions..."
else
    sudo rsync -rptz --exclude 'app/logs/*' --exclude 'vendor/*' --exclude 'app/cache/*' /media/sf_project/* /opt/www/development/project
    sudo rsync -rptz /opt/www/development/project/app/logs /media/sf_project
    sudo cp /opt/www/development/project/composer.json /media/sf_project
    sudo cp /opt/www/development/project/composer.lock /media/sf_project
fi

sudo chown -R www-data:www-data /opt/www/development/project
sudo chmod -R 775 /opt/www/development/project

I called it “project-sync”, put it in /usr/local/bin and made it executable

sudo chmod +x /usr/local/bin/sync-project

Now, when calling it you can either simply use

project-sync silent

(actually any value as first parameter will trigger the silent mode) or, if you want more verbose output and info about what happend, simply run

project-sync

.

UPDATE:

I extended the cron to
a) ignore a couple of files I don’t need to copy since they don’t change locally (vendor, logs and cache files) and

b) sync a couple of files back into my local copy that I want to commit/need for debugging like composer-related files and the logs

I hope this is helpful to someone besides me :)