ASCII Thoughts

Speed up your tests and save your SSD with mrd

mrd creates a virtual disk in memory, and runs an instance of MySQL on it. The main application is running tests: no matter how fast your hard drive is, it will never be as fast as RAM. Since test data is volatile anyway, why not use the fastest medium available. As an added bonus, because it prevents all these volatile writes from going to disk, it can increase the lifespan of your SSD.

While mrd is great, it's only great if you use it all the time. So in this article I'll share a few tips that have made using mrd a much more pleasant experience for me.

Installing it

Similar to our previous article on Mailcatcher, If you have a reasonably recent version of Linux or Mac OS X, you probably have Ruby installed. If not, you will have to install Ruby first. Once you have Ruby installed:

# If you are using the system's Ruby:
sudo gem install mrd

# If you use rbenv/rvm/etc:
gem install mrd

Running it

If you use rvm, rbenv or chruby, you will first need to create a wrapper script. I put mine in ~/.bin/launchd-mrd:

#!/bin/bash
export PATH="$PATH:/usr/local/bin"
source /usr/local/share/chruby/chruby.sh
chruby 2.1
mrd

Make the script executable:

chmod +x ~/.bin/launchd-mrd

Launch it:

~/.bin/launchd-mrd
# Created Ramdisk at /dev/disk4
# Formatted Ramdisk at /dev/disk4
# Mounted Ramdisk at /Volumes/MySQLRAMDisk
#
# [...]
#
# Starting MySQL server
# MySQL is now running.
# Configure you client to use the root user, no password, and the socket at '/Volumes/MySQLRAMDisk/mysql.sock'.
# Just close this terminal or press ^C when you no longer need it.

Great! It works perfectly. Let's now create a daemon to automatically start it on boot.

Starting it automatically

Create the startup script:

echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
  <dict>
    <key>Label</key>
    <string>com.$USER.mrd</string>
    <key>Program</key>
    <string>$HOME/.bin/launchd-mrd</string>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/tmp/com.$USER.mrd.err</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.$USER.mrd.out</string>
  </dict>
</plist>
" > ~/Library/LaunchAgents/com.$USER.mrd.plist

Launch it:

launchctl load ~/Library/LaunchAgents/com.$USER.mrd.plist

You should see the new shiny icon right on your desktop:

MySQL RAM disk screenshot

If you run into any trouble, look at the logs:

/tmp/com.$USER.mrd.out
/tmp/com.$USER.mrd.err

Configuring Rails

You can now open your config/database.yml and update the test section:

test:
  adapter: mysql2
  encoding: utf8
  collation: utf8_unicode_ci
  host: localhost
  database: NAME
  username: root
  password: 
  socket: /Volumes/MySQLRAMDisk/mysql.sock

Run the tests:

cd ~/project
RAILS_ENV=test bundle exec rake db:create
bundle exec rake db:test:prepare
bundle exec rake test
..............................................................
..............................................................
...

Yeah \o/ This works!

Automating the database creation

Wouldn't it be better if we could automate the database creation/preparation as well? After all, you might have multiple projects, and you don't want to have to recreate the test database each time, load the schema, etc... So let's automate all that.

Create a new script ~/.bin/prepare-test-dbs:

#!/bin/bash

# Load your preferred Ruby Version Manager
export PATH="$PATH:/usr/local/bin"
source /usr/local/share/chruby/chruby.sh
source /usr/local/share/chruby/auto.sh

# Wait until 'mrd' is actually loaded, and MySQL running
until [[ -S /Volumes/MySQLRAMDisk/mysql.sock ]]; do
  sleep 1
done

# Go through each project, and:
# - create the database
# - load the schema
# - clear logs/*
# - clear tmp/*
for dir in ~/projects/project1 \
           ~/projects/project2 \
           ~/projects/projectN; do
  cd "$dir"
  RAILS_ENV=test bundle exec rake db:create
  bundle exec rake db:test:prepare
  bundle exec rake log:clear
  bundle exec rake tmp:clear
done

Make it executable:

chmod +x ~/.bin/prepare-test-dbs

And create the startup script:

echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
  <dict>
    <key>Label</key>
    <string>com.$USER.mrd-prepare</string>
    <key>Program</key>
    <string>$HOME/.bin/prepare-test-dbs</string>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/tmp/com.$USER.mrd-prepare.err</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.$USER.mrd-prepare.out</string>
  </dict>
</plist>
" > ~/Library/LaunchAgents/com.$USER.mrd-prepare.plist

Now, each time your start your computer:

Run the tests:

cd ~/projects/project1
bundle exec rake test
..............................................................
..............................................................
...

You're all set!

That's it for today. Cheers ;)