How to make Vagrant performance not suck
Vagrant is an invaluable tool for creating a standardized virtual environments that make it incredibly easy to bring on new developers. Instead of requiring users to install Postgres, Redis, Elasticsearch, etc to be able to run and develop on your app, you just give them 3 simple steps (hopefully they don't even need the first two):
- Download & install VirtualBox
- Download & install Vagrant
- run
vagrant up && vagrant ssh
from the project's folder
The problem I've run into over and over again is that about 1 out of every 3 people who go through this process end up complaining to me that running the app inside of their Vagrant box is painfully slow. The following is what I've learned from troubleshooting this issue over and over again.
Use the NFS, Luke
By default, VirtualBox shares files between the host and guest operating systems using its own built-in sharing mechanism. While this method works across all types of hosts, it's incredibly slow, at least on Unix hosts. The solution is to use NFS, which is much faster. How much faster, you ask? Simply turning on NFS doubled my Rails app's performance, based on number of requests it was able to serve over a 2 minute period. You can read more about the benchmark methodology and results at the end of this article.
To enable NFS, add the following to your Vagrantfile:
# Required for NFS to work, pick any local IP
config.vm.network :private_network, ip: '192.168.50.50'
# Use NFS for shared folders for better performance
config.vm.synced_folder '.', '/vagrant', nfs: true
Important caveat: most of this is not strictly Vagrant's fault; the blame lies with VirtualBox, if anything. However, I do wish the Vagrant documentation had a section on performance, because, while the information on NFS is in there, it seems fairly inconsequential until you actually benchmark and realize you're getting half the performance that you could be getting.
Use 1/4 system memory
Most folks just don't seem to bother with telling Virtualbox to use more than the default amount RAM, which is understandable because it's hard to come up with something that works regardless of different host systems. It took me a while, but I put together the following, which should set these values based on each individual host system:
config.vm.provider "virtualbox" do |v|
host = RbConfig::CONFIG['host_os']
# Give VM 1/4 system memory
if host =~ /darwin/
# sysctl returns Bytes and we need to convert to MB
mem = `sysctl -n hw.memsize`.to_i / 1024
elsif host =~ /linux/
# meminfo shows KB and we need to convert to MB
mem = `grep 'MemTotal' /proc/meminfo | sed -e 's/MemTotal://' -e 's/ kB//'`.to_i
elsif host =~ /mswin|mingw|cygwin/
# Windows code via https://github.com/rdsubhas/vagrant-faster
mem = `wmic computersystem Get TotalPhysicalMemory`.split[1].to_i / 1024
end
mem = mem / 1024 / 4
v.customize ["modifyvm", :id, "--memory", mem]
end
Update: I used to recommend upping the number of CPU cores used by Vagrant, but it has been shown several times that adding more virtual cpu cores to a Virtualbox VM actually decreases performance. Bummer.
Use vagrant package
The typical Vagrant box setup process involves downloading a base box (usually lucid64 or precise64) and installing the required packages with a provisioner like Puppet or Chef. I've found that there's little value in dealing with the hassle of writing the necessary provisioning scripts. It's much easier to fire up a base box, install everything manually, exit out of the ssh session, and then run vagrant package --output NAME
. This will give you a NAME.box
file that you can upload to your host of choice (I used S3) and add to your Vagrantfile like so:
config.vm.box = 'NAME'
config.vm.box_url = 'https://s3.amazonaws.com/BUCKET/vagrant/NAME.box'
This way, when vagrant up
runs, it will download the pre-provisioned box and fire it up immediately, rather than going through the provisioning process again.
Extra: NFS benchmarks
To benchmark, I used wrk, a great purpose-built HTTP benchmarking tool that I find much more pleasant to use than ab
. On a Mac it's a simple brew install wrk
away.
Vagrant using virtualbox sharing:
→ wrk -d120s http://localhost:3333/
Running 2m test @ http://localhost:3333/
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 49.14s 29.37s 1.17m 100.00%
Req/Sec 0.00 0.00 0.00 100.00%
12 requests in 2.00m, 490.39KB read
Socket errors: connect 0, read 0, write 0, timeout 588
Requests/sec: 0.10
Transfer/sec: 4.09KB
Running the app natively on my Macbook:
→ wrk -d120s http://localhost:3000/
Running 2m test @ http://localhost:3000/
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 40.04s 10.71s 47.93s 82.35%
Req/Sec 0.00 0.00 0.00 100.00%
27 requests in 2.00m, 1.10MB read
Socket errors: connect 0, read 0, write 0, timeout 573
Requests/sec: 0.22
Transfer/sec: 9.37KB
Vagrant using NFS sharing:
→ wrk -d120s http://localhost:3333/
Running 2m test @ http://localhost:3333/
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 41.46s 12.49s 1.03m 50.00%
Req/Sec 0.00 0.00 0.00 100.00%
24 requests in 2.00m, 0.96MB read
Socket errors: connect 0, read 0, write 0, timeout 576
Requests/sec: 0.20
Transfer/sec: 8.18KB
You might also be interested in these articles...
Startup Founder | Engineering Leader with an MBA