Blog

Thoughts from my daily grind

UUID v7 indexes with PostgreSQL & Ruby on Rails

Posted by Ziyan Junaideen |Published: 19 August 2023 |Category: Ruby on Rails
UUID v7 indexes with PG & RoR |

Twelve years ago, I transitioned from numeric primary keys into UUIDs. It's been a long time since I revisited UUIDs, and Kurtis Rainbolt-Greene, a colleague at Edge Payments Technologies, wanted to transit the application from UUID v4 to UUID v7. UUID v7 addresses many of the concerns I had with UUID v4. The procedure was painless.

This post details the steps I took to introduce UUID v7 to a Ruby on Rails project.

Why use UUID v7

The commonly used UUID format v4 does not possess a sense of adjacency as its data is random. This results in the inability to sort by the UUID column. In addition, using UUID v4 on primary keys results in database scattering.

The UUID v7, like ULID (Universally unique Lexicographically-sortable IDentifiers), uses a 48-bit epoch timestamp.

Configuring PosgreSQL

Out of the box, PG does not support UUID v7. The pg_uuidv7 extension by fboulnois introduces UUID v7 to Postgres.

Download the plugin and build it using make

git clone git@github.com:fboulnois/pg_uuidv7.git
cd pg_uuidv7
make

Then you must copy the shared object (.so dynamic library) file and the extension control file (.control) generated from running make.

cp pg_uuidv7.so "$(pg_config --pkglibdir)"
cp pg_uuidv7--1.1.sql pg_uuidv7.control "$(pg_config --sharedir)/extension"

Then you need to add pg_uuidv7 extension to the list of preloaded libraries. This can be done using the pg_conftool.

pg_conftool set shared_preload_libraries "pg_uuidv7"

If the pg_conftool is not available to you (probably it is the case if you are using ASDF like myself to manage PG versions), you can manually update the config. Uncomment shared_preload_libraries in the postgresql.conf file and then restart the PG server.

nvim  "$(pg_config --bindir)/../data/postgresql.conf"
# - Shared Library Preloading -

#local_preload_libraries = ''
#session_preload_libraries = ''
#shared_preload_libraries = ''  # (change requires restart)
shared_preload_libraries = 'pg_uuidv7'
pg_ctl restart

You can create the extension by running it. If you are rocking a Rails application, you don't have to do this step and we can create a migration for that.

psql -c "CREATE EXTENSION pg_uuidv7;"

Configuring Rails

I recommend starting by introducing the UUID7 gem. It will help us generate UUID v7 values on the Ruby side when wanted.

bundle add uuid7

Config generators to use UUIDs for primary keys. This will add id: :uuid for migrations.

config.generators do |g|
  g.orm(:active_record, primary_key_type: :uuid)
end

Create a migration to create the pg_uuidv7 extensions.

class AddExtensionsForUuid < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
    enable_extension 'pg_uuidv7' unless extension_enabled?('pg_uuidv7')
  end
end

Then when you create a table, you need to use the uuid_generate_v7() function introduced by the plugin.

class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users, id: :uuid, default: 'uuid_generate_v7()' do |t|
      t.string :full_name
    end
  end
end

In case you need to generate a UUIDv7 outside the DB, you can use UUID7.generate() instead of SecureRandom.uuid.

There is how ever one question I have. How do we convert UUID v4 indexes to v7. I don't see any harm not doing it. But I wonder what it would take to convert existing indexes to UUID v7 format.

About the Author

Ziyan Junaideen -

Ziyan is an expert Ruby on Rails web developer with 8 years of experience specializing in SaaS applications. He spends his free time he writes blogs, drawing on his iPad, shoots photos.

Comments