For those of you using MySQL database server on Mac OS X Leopard while developing for Ruby or Ruby on Rails, you might have run into an issue with the mysql rubygem installing OK, but not actually loading properly in irb, nor completing unit tests successfully.
If you’ve seen errors such as:
>> require 'mysql'
LoadError: dlopen(/Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle, 9): Library not loaded: /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib
Referenced from: /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle
Reason: image not found - /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:32:in `require'
Or perhaps you’ve tried to run test.rb from the mysql ruby library install and ran into an error like this:
$ ruby test.rb
./mysql.bundle: dlopen(./mysql.bundle, 9): Library not loaded: /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib (LoadError)
Referenced from: /Library/Ruby/Gems/1.8/gems/mysql-2.7/mysql.bundle
Reason: image not found - ./mysql.bundle from test.rb:5
The fix to the mysql ruby library gem installation was provided by jhclouse on the RailsForum site. I’ve edited the fix to work with Mac OS X Leopard’s pre-installed version of Ruby:
sudo install_name_tool -change /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib /usr/local/mysql/lib/libmysqlclient.15.dylib /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle
The only difference between jhclouse’s fix and the one here is the location of the installed mysql rubygem. On OS X Leopard, ruby gems are installed in the /Library directory rather than /usr/local/lib.
Run the fix again in the main mysql ruby gem directory as there is another mysql.bundle file that needs fixing. This bundle file is used by the unit test file test.rb.
sudo install_name_tool -change /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib /usr/local/mysql/lib/libmysqlclient.15.dylib /Library/Ruby/Gems/1.8/gems/mysql-2.7/mysql.bundle
To run the unit tests for the mysql ruby gem you need to supply some command line arguments to test.rb:
ruby test.rb -- localhost [username] [password]
Replace [username] [password] with valid credentials for your MySQL server and you should have a fairly successful run of the unit tests for the mysql ruby gem.
Now that you’ve got the mysql C bindings for ruby installed, lets double check that it’s actually being used.
If you have a rails project, start with webrick (script/server) and load up your test site in a browser. Do something within your site that you know touches the database.
Now find the process_id for ruby by running ‘top’ in the command line. Top will return a list of all process names and their process ids. Find ruby amongst the list. For me it happened to be 3343.
Then we use that process_id in a magical program called ‘lsof’:
PID COMMAND %CPU TIME #TH #PRTS #MREGS RPRVT RSHRD RSIZE VSIZE
3372 top 5.3% 0:01.39 1 18 29 824K 188K 1416K 18M
3343 ruby 0.3% 0:06.77 2 19 247 60M 188K 64M 85M
$ lsof -p 3343 | grep mysql
ruby 3343 ben txt REG 14,2 86288 813474 /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle
ruby 3343 ben txt REG 14,2 1967788 624481 /usr/local/mysql-5.0.45-osx10.4-i686/lib/libmysqlclient.15.dylib
lsof spits out the “list of open files” that are in use by a certain process, in this case the running ruby process that is serving our Rails site. In the above listing lsof shows that the freshly installed mysql ruby gem (mysql-2.7) that provides native C bindings for ruby to use when accessing MySQL, is currently being used. Sweet, we’re all good.
So is all this trouble really worth it? Why not just use the built in ruby libraries to interface with MySQL? If your Rails site touches a database, and most likely it does, you’ll see a 2-5x decrease in rendering time due to database calls. We’re only talking milliseconds, but it’s still a 2-5x boost in efficiency so it’s definitely worth it if you’re optimizing your Rails site. Whether it should be at the top of your tweaking to-do list depends on how optimized everything else is.
My Rails site in the testing environment was constantly generating on average about 30% of its page rendering time due to database activity. The following is hardly a definitive test, but is printed here simply for interests sake.
For an initial site load with the MySQL query cache reset, my home page rendering time looked like this before the C MySQL ruby binding installation:
Completed in 1.47017 (0 reqs/sec) | Rendering: 0.10060 (6%) | DB: 1.18992 (80%) | 200 OK [http://localhost.com/]
After, resetting the query cache in MySQL, clearing the browser cache and restarting webrick, the initial site home page load after installing the C bindings for MySQL via the ruby gem:
Completed in 0.70598 (1 reqs/sec) | Rendering: 0.08854 (12%) | DB: 0.56506 (80%) | 200 OK [http://localhost.com/]
The time to load was cut in half and the only thing that changed was the way ActiveRecord accesses the MySQL database.
Please note that if you’ve been using Mac Ports, this fix may need tweaking on your part. The problem and the fix are pretty straight forward once you know about the install_name_tool program. The mysql ruby gem is simply looking in the wrong place for a MySQL library that it needs to interface with. If you look closely at the first two directory paths you’ll notice that the first has an extra “/mysql” in between “/lib” and “/libmysqlclient.15.dylib”. Remove that extra “/mysql” folder from the directory path and you’re golden.