Get database version in Rails

For one of my applications, I recently created a status report page which shows crucial information for debugging, including software versions, service connectivity and filesystem path writability. One of the items was a database version string, and the standard ActiveRecord interface does not support this concept. While version methods usually exist in the connection adapters, they are usually protected/private and have different naming conventions.

As the application can be run against multiple backends as well as on MRI or JRuby, I needed to check what adapter was in use, then query the database:

adapter = ActiveRecord::Base.connection.adapter_name
sql = case adapter
  when 'MSSQL'
    'SELECT @@VERSION'
  when 'MySQL', 'Mysql2', 'PostgreSQL'
    'SELECT VERSION()'
  when 'OracleEnhanced'
    'SELECT * FROM V$VERSION'
  when 'SQLite'
    'SELECT SQLITE_VERSION()'
end
ActiveRecord::Base.connection.select_value(sql)

Optimising index view performance

I recently came across a scenario where my index views in Rails were taking a long time to render when there were large data sets. The problem stemmed having a lot of data presented that were from models/tables other than the primary model/table of interest.

For example, an “asset” index view may have the following columns:

  • Name (from asset.name)
  • Type (from asset.type.name)
  • Site (from asset.site.name)
  • Organisation (from asset.site.organisation.name)
  • System (from asset.system.name)
  • Status (from asset.status.name)

The first (naive) approach I used was to query using eager loading:

Asset.includes(:type, {site: :organisation}, :system, :status)
     .references(:type)
     .search_by('types.name', 'Pump')
     .order(:name).page(1).per_page(1000)

Using a data set of around 500,000 items, rack-mini-profiler indicated this took about 6.7 seconds to render the action with 5.6 seconds of that consumed by the SQL query.

When investigating, I noticed this was using a single query with LEFT OUTER JOINs. I then forced ActiveRecord to use separate queries to load the related models by using Rails 4’s preload instead of includes and used joins instead of references to force ActiveRecord to use a more performant INNER JOIN:

Asset.preload(:type, {site: :organisation}, :system, :status)
     .joins(:type)
     .search_by('types.name', 'Pump')
     .order(:name).page(1).per_page(1000)

This dropped the action render time to 1.7 seconds with 0.7 seconds consumed by the six SQL queries.

For my index views, I actually don’t need the entire object graph to be hydrated – I only really needed the names of the related object. So I tried using pluck, which returns an 2-dimensional array of values:

Asset.includes(:type, {site: :organisation}, :system, :status)
     .joins(:type).search_by('types.name', 'Pump')
     .order(:name).page(1).per_page(1000)
     .pluck(:name, 'types.name', 'sites.name', 'organisations.name', 'systems.name', 'statuses.name')

This actually increased the action render time to 2.1 seconds with 1.7 seconds consumed by ActiveRecord and the SQL query. The SQL query now uses LEFT OUTER JOINs again, which as we saw in the original case, is slow. But the differential between the total and SQL had decreased since ActiveRecord models did not have to be created.

While using the second approach would provide a significant speed boost without changing any code, I wondered if we could use a combination of the second and third approaches – use separate SQL queries instead of a single with LEFT OUTER JOINs and simply pass back an array and not have to hydrate ActiveRecord models.

The result: Preload Pluck.

On the same data set, Preload Pluck drops the action render time to 1.5 second with 0.7 seconds consumed by the SQL queries.

YMMV as the results above are for that particular data set, the queries I’ve used and the way my PostgreSQL database is setup.

Install Oracle XE 11g R2 on Ubuntu 12.04

I’ve recently had a task of enabling Oracle database support for a JRuby on Rails application. To set up an Oracle database for use on a development environment, there were two preferred options – using Oracle DB on a virtual machine or installing Oracle XE locally.

1. Oracle DB on a virtual machine
Oracle kindly provide several VirtualBox images for download – I used the Database App Development VM, which comes with Enterprise Edition and SQL Developer, and it’s straightforward to get this running. The only catch is to enable a second network adapter (either Bridged or Host-only) in the VirtualBox settings so the VM is externally addressable. From there, keep in mind the the instance name is orcl, and sys and system passwords are oracle.

2. Installing Oracle XE locally
Oracle XE is officially supported on Windows and Red Hat Linux-based systems (the Developer Days VMs are Oracle Linux). Debian Linux-based systems don’t seem to be officially supported, but there have been efforts on installing Oracle XE on Ubuntu (my particular interest). I primarily followed Manish’s guide for the majority of the install process, but had to bring forward the step of setting environment variables. This is because Oracle XE starts immediately after the configuration step (oracle-xe configure). I also referred to another blog for guidance on setting kernel parameters. Here are my modified installation steps:

Download Oracle Database Express Edition 11g R2

Unzip archive:

unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip

Install the required packages (note I omitted unixodbc as I didn’t require it):

sudo apt-get install alien libaio1

Convert the RPM package to a DEB package:

cd Disk1
sudo alien --scripts oracle-xe-11.2.0-1.0.x86_64.rpm

Create a /sbin/chkconfig file with the following contents (requires root):

#!/bin/bash
# Oracle 11gR2 XE installer chkconfig hack for Ubuntu
file=/etc/init.d/oracle-xe
if [[ ! `tail -n1 $file | grep INIT` ]]; then
echo >> $file
echo '### BEGIN INIT INFO' >> $file
echo '# Provides: OracleXE' >> $file
echo '# Required-Start: $remote_fs $syslog' >> $file
echo '# Required-Stop: $remote_fs $syslog' >> $file
echo '# Default-Start: 2 3 4 5' >> $file
echo '# Default-Stop: 0 1 6' >> $file
echo '# Short-Description: Oracle 11g Express Edition' >> $file
echo '### END INIT INFO' >> $file
fi
update-rc.d oracle-xe defaults 80 01

Set execute privileges for the file:

sudo chmod 755 /sbin/chkconfig

Create a /etc/sysctl.d/60-oracle.conf file with the following contents (requires root):

# Oracle 11g XE kernel parameters
fs.file-max=6815744
kernel.sem=250 32000 100 128
kernel.shmmax=1073741824
net.ipv4.ip_local_port_range=9000 65000

Load the new kernel parameters:

sudo service procps start

Run the following commands:

sudo ln -s /usr/bin/awk /bin/awk
sudo mkdir /var/lock/subsys
sudo touch /var/lock/subsys/listener

To avoid MEMORY_TARGET errors:

sudo rm -rf /dev/shm
sudo mkdir /dev/shm
sudo mount -t tmpfs shmfs -o size=2048m /dev/shm

Create a /etc/rc2.d/S01shm_load file with the following contents (requires root):

#!/bin/sh
case "$1" in
start) mkdir /var/lock/subsys 2>/dev/null
	   touch /var/lock/subsys/listener
	   rm /dev/shm 2>/dev/null
	   mkdir /dev/shm 2>/dev/null
	   mount -t tmpfs shmfs -o size=2048m /dev/shm ;;
*) echo error
   exit 1 ;;
esac

Set execute privileges for the file:

sudo chmod 755 /etc/rc2.d/S01shm_load

Install the Oracle XE package:

sudo dpkg --install oracle-xe_11.2.0-2_amd64.deb

Add the following environment variables to .bashrc:

export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe
export ORACLE_SID=XE
export NLS_LANG=`$ORACLE_HOME/bin/nls_lang.sh`
export ORACLE_BASE=/u01/app/oracle
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH
export PATH=$ORACLE_HOME/bin:$PATH

Reload the bash profile:

source ~/.bashrc

Run the Oracle XE configuration:

sudo /etc/init.d/oracle-xe configure

Oracle XE should now be installed.

If anything goes wrong during installation, to uninstall:

sudo -s
/etc/init.d/oracle-xe stop
ps -ef | grep oracle | grep -v grep | awk '{print $2}' | xargs kill
dpkg --purge oracle-xe
rm -r /u01
rm /etc/default/oracle-xe
update-rc.d -f oracle-xe remove

I’ve used these instructions on two Ubuntu environments (my own VM and an Amazon EC2 instance) and Oracle XE is running happily.