PostgreSQL 9.4 in RAM HOWTO

15 Nov 2015

First, we need a RAM disk we can use. Happily, on Ubuntu, the /run dir is a tmpfs mount, so let's use that!

$ su -
# mkdir -p /run/pgsql-9.4.5-data
# chown -R postgres:postgres /run/pgsql-9.4.5-data
# chmod 700 /run/pgsql-9.4.5-data
# su - postgres
$ /usr/local/pgsql-9.4.5/bin/initdb \
    --pgdata=/run/pgsql-9.4.5-data \
    --encoding=UTF8 \
    --no-locale

Normally, we would edit postgresql.conf to tune our PostgreSQL instance to run optimally with our compter's resources. However, all data in /run/pgsql-9.4.5-data will be lost on the next reboot. So instead, let's specify all the settings at the command-line.

Here are the settings that we want to look at modifying from their defaults:

First, turn off fsync; it makes no sense on a ramdisk:

#fsync = on  ==>  fsync = off

Consider turning all of these memory settings down to their minimums, seeing as our whole dataset is already cached in RAM anyway:

shared_buffers = 128MB ==> shared_buffers = 128kB
#temp_buffers = 8MB ==> temp_buffers = 800kB
#work_mem = 4MB ==> work_mem = 64kB
#maintenance_work_mem = 64MB ==> maintenance_work_mem = 1024kB

You should't be using WAL anyway (use unlogged tables) but try tuning this down to the minimum, too, except for checkpoint_segments, which may as well be as high as possible in case you accidentally use WAL:

#wal_buffers = -1 ==> wal_buffers = 32kB
#checkpoint_segments = 3 ==> checkpoint_segments = 64

Obviously, random_page_cost is exactly the same as seq_page_cost in RAM, but additionally, they are the same as cpu_tuple_cost:

#seq_page_cost = 1.0 ==> seq_page_cost = 0.01
#random_page_cost = 4.0 ==> random_page_cost = 0.01

Try tuning down effective_cache_size, seeing as you should encourage direct reads from ramdisk:

#effective_cache_size = 4GB ==> effective_cache_size = 64kB

So here's what our command line looks like to start up postgres:

/usr/local/pgsql-9.4.5/bin/postgres \
    -D /run/pgsql-9.4.5-data \
    --fsync=off \
    --shared-buffers=128kB \
    --temp-buffers=800kB \
    --work-mem=64kB \
    --maintenance-work-mem=1024kB \
    --wal-buffers=32kB \
    --checkpoint-segments=64 \
    --seq-page-cost=0.01 \
    --random-page-cost=0.01 \
    --effective-cache-size=64kB

Seeing as this is essentially a temporary database, we may as well use the built-in postgres database and postgres user instead of going through the bother of creating a different user and database.

We need to load our schema, so we can do that now.

# gvim /usr/local/pgsql-9.4.5/bin/ramdb.sql
-- Note that this might be run multiple times on the same
-- database, so make it idempotent.
-- Also, use unlogged tables to bypass the WAL to increase performance
create unlogged table if not exists sessions (
    id uuid constraint session_pk primary key not null,
    data text not null);
# chmod +x /usr/local/pgsql-9.4.5/bin/ramdb.sql
# chown postgres:postgres /usr/local/pgsql-9.4.5/bin/ramdb.sql

And here's how we load it in:

/usr/local/pgsql-9.4.5/bin/psql \
    -X \
    -U postgres \
    -d postgres \
    -f /usr/local/pgsql-9.4.5/bin/ramdb.sql

Now we can log in and start using our in-memory PostgreSQL:

/usr/local/pgsql-9.4.5/bin/psql \
    -U postgres \
    -d postgres

systemd script

We can take all that we have learned and turn it into a systemd script, like so:

# cd /etc/systemd/system
# vi pg-ram.service
[Unit]
Description=PostgreSQL 9.4.5 in RAM
# This unit can only run after the network is up and running
# (that is, the network target has run)
After=network.target

[Service]
# PostgreSQL is a traditional UNIX daemon that forks a child,
# and the initial process exits
Type=forking
# Wait 120 seconds on startup and shutdown to consider the process
# correctly started up or shut down.
TimeoutSec=120
# The UNIX user and group to execute PostgreSQL as
User=postgres
Group=postgres

# Set the PGROOT environmental variable for PostgreSQL
Environment=PGROOT=/run/pgsql-9.4.5-data

# If StandardOutput= or StandardError= are set to syslog, journal or kmsg,
# prefix log lines with "postgres"
SyslogIdentifier=postgres

# Let systemd know where PostgreSQL keeps its pid file
PIDFile=/run/pgsql-9.4.5-data/postmaster.pid

# IMPORTANT! Run ExecStartPre with root-permissions
# so that the mkdir command on /run will succeed
# (won't succeed as user postgres)
PermissionsStartOnly=true

# Set up everything before PostgreSQL starts
ExecStartPre= /bin/mkdir -p /run/pgsql-9.4.5-data
ExecStartPre= /bin/chown -R postgres:postgres /run/pgsql-9.4.5-data
ExecStartPre= /bin/chmod 700 /run/pgsql-9.4.5-data
# Important: the leading minus sign means ignore errors; in this case,
# initdb may have already been run on a previous start, and will error
# out trying to re-create a database cluster dir that already exists.
ExecStartPre=-/usr/bin/sudo -u postgres \
        /usr/local/pgsql-9.4.5/bin/initdb \
            --pgdata=/run/pgsql-9.4.5-data \
            --encoding=UTF8 \
            --no-locale

# After PostgreSQL starts, load the database schema
ExecStartPost= /usr/local/pgsql-9.4.5/bin/psql \
    -X \
    -U postgres \
    -d postgres \
    -f /usr/local/pgsql-9.4.5/bin/ramdb.sql

# Start PostgreSQL in RAM
ExecStart= /usr/local/pgsql-9.4.5/bin/pg_ctl \
    -s -D ${PGROOT} start -w -t 120 \
    -o "--fsync=off --shared-buffers=128kB \
    --temp-buffers=800kB --work-mem=64kB  \
    --maintenance-work-mem=1024kB --wal-buffers=32kB \
    --checkpoint-segments=64 --seq-page-cost=0.01  \
    --random-page-cost=0.01 --effective-cache-size=64kB"

# Reload PostgreSQL in RAM
ExecReload=/usr/local/pgsql-9.4.5/bin/pg_ctl -s -D ${PGROOT} reload

# Stop PostgreSQL in RAM
ExecStop=  /usr/local/pgsql-9.4.5/bin/pg_ctl -s -D ${PGROOT} stop -m fast

# Use the lowest allowable setting for the OOM killer; this should
# actually disable the OOM killer for PostgreSQL
OOMScoreAdjust=-1000

[Install]
# This unit is part of target multi-user
WantedBy=multi-user.target

Let's test:

# systemctl daemon-reload
# systemctl start pg-ram
# systemctl status pg-ram
# systemctl stop pg-ram
# systemctl status pg-ram

Yup: all good!