petitviolet blog

    Ruby on Rails on YugabyteDB



    YugabyteDB is "The Leading High-Performance Distributed SQL Database".

    It has been developed as an open source and called Distributed SQL.
    YugabyteDB is a Distributed SQL product, like Google Cloud Spanner which is a well-known global distributed databased developed by Google, but it has PostgreSQL compatibile interface, not like Spanner, we can connect to YugabyteDB via PostgreSQL driver. In addition to PostgreSQL interface, YugabyteDB also provides Redis interface called Yedis. Ruby on Rails applications usually depend on Redis as its cache layer and backgound workers storage, such as Sidekiq.

    I assumed using YugabyteDB as a backend storage layer of Ruby on Rails application perhaps is a great way to manage storages into one place.
    This post describes how to get started and how to use YugabyteDB as backend of Ruby on Rails applications.
    Example source codes are available at petitviolet/yuga-rails in GitHub.

    Getting Started with YugabyteDB

    Official document describes how to launch YugabyteDB instances using docker-compose.

    Copy and paste it to local docker-compose.yaml, then launch docker containers by docker-compose up -d as written in the document.

    $ docker-compose up -d
    $ docker-compose ps
        Name                   Command               State                                                                Ports
    yb-master-n1    /home/yugabyte/bin/yb-mast ...   Up      10100/tcp, 11000/tcp, 12000/tcp, 5433/tcp, 6379/tcp,>7000/tcp, 7100/tcp, 7200/tcp, 9000/tcp, 9042/tcp, 9100/tcp
    yb-tserver-n1   /home/yugabyte/bin/yb-tser ...   Up      10100/tcp, 11000/tcp, 12000/tcp,>5433/tcp,>6379/tcp, 7000/tcp, 7100/tcp, 7200/tcp,

    Initial Configurations

    As a preparation for the following sections, update credentials using ysqlsh in yb-tserver-n1 container.

    $ docker exec -it yb-tserver-n1 /home/yugabyte/bin/ysqlsh -h yb-tserver-n1
    ysqlsh (11.2-YB-
    Type "help" for help.
    yugabyte=# alter role yugabyte with password 'password';

    Then, we can connect to the YugabyteDB instance just as connecting to PostgreSQL.

    $ psql -Uyugabyte -h127.0.0.1 -p5433 -W
    psql (12.3, server 11.2-YB-
    Type "help" for help.

    In order to use YugabyteDB as backend database of a Rails application, create a database for the app in advance.

    yugabyte=# create database yuga_rails_development;
    yugabyte=# \l
                                          List of databases
              Name          |  Owner   | Encoding | Collate |    Ctype    |   Access privileges
     postgres               | postgres | UTF8     | C       | en_US.UTF-8 |
     system_platform        | postgres | UTF8     | C       | en_US.UTF-8 |
     template0              | postgres | UTF8     | C       | en_US.UTF-8 | =c/postgres          +
                            |          |          |         |             | postgres=CTc/postgres
     template1              | postgres | UTF8     | C       | en_US.UTF-8 | =c/postgres          +
                            |          |          |         |             | postgres=CTc/postgres
     yuga_rails_development | yugabyte | UTF8     | C       | en_US.UTF-8 |
     yugabyte               | postgres | UTF8     | C       | en_US.UTF-8 |
    (6 rows)
    yugabyte=# \c yuga_rails_development
    Password for user yugabyte:
    psql (12.3, server 11.2-YB-
    You are now connected to database "yuga_rails_development" as user "yugabyte".
    yuga_rails_development=# \d
    Did not find any relations.

    Now, it's ready to develop an application depending on the YugabyteDB schema. Setting user and password to environmental value will reduce our future typing efforts :)

    $ export PGUSER=yugabyte
    $ export PGPASSWORD=password

    Initialize Ruby on Rails application

    Create a new Rails app by using rails new command.

    $ ruby -v
    ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin18]
    $ rails -v
    $ rails new yuga-rails --database=postgresql --api

    After creating a new app, edit config/database.yml to configure connection settings to YugabyteDB.

    default: &default
        adapter: postgresql
        <<: *default
        database: yuga_rails_development
        username: <%= ENV.fetch("PGUSER") %>
        password: <%= ENV.fetch("PGPASSWORD") %>
        port: 5433

    No need to add additional database drivers, configurations, etc. to use YugabyteDB. It's ready to use YugabyteDB as a database of the Rails app!

    Manipulate YugabyteDB data through Rails app

    Defining Model using rails g command.

    $ bundle exec rails g model user email:string name:string gender:integer
    $ vim ./db/migrate/20200621050634_create_users.rb # edit
    class CreateUsers < ActiveRecord::Migration[6.0]
    def change
        create_table :users do |t|
        t.string :email, null: false, index: { unique: true }
        t.string :name, null: false
        t.integer :gender, null: false

    Then, apply database migration.

    $ bundle exec rails db:migrate
    == 20200621050634 CreateUsers: migrating ======================================
    -- create_table(:users)
    -> 3.6016s
    == 20200621050634 CreateUsers: migrated (3.6016s) =============================

    Launch Rails console to play with the app.

    $ bundle exec rails c
    Running via Spring preloader in process 20732
    Loading development environment (Rails
    irb(main):001:0> User.all
      User Load (11.4ms)  SELECT "users".* FROM "users" LIMIT $1  [["LIMIT", 11]]
    => #<ActiveRecord::Relation []>
    irb(main):002:0> 30.times.each { |i| User.create!(email: "user-#{i}", name: "user-#{i}", gender: i % 3) }
    => 30
    irb(main):003:0> User.count
       (23.2ms)  SELECT COUNT(*) FROM "users"
    => 30
    irb(main):004:0> User.first
      User Load (13.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
    => #<User id: 1, email: "", name: "user-0", gender: 0, created_at: "2020-06-23 13:34:12", updated_at: "2020-06-23 13:34:12">

    It's working expectedly, though it seems slow than expected.

    Using Yedis as a Cache of Rails

    To enable Redis API of YugabyteDB which called Yedis API, run this command that described in the document.

    $ docker exec -it yb-master-n1 /home/yugabyte/bin/yb-admin --master_addresses yb-master-n1:7100 setup_redis_table
    I0623 13:42:41.866884    45] MemTracker: hard memory limit is 1.651742 GB
    I0623 13:42:41.867004    45] MemTracker: soft memory limit is 1.403981 GB
    I0623 13:42:41.945003    45] Created table system_redis.redis of type REDIS_TABLE_TYPE
    I0623 13:42:41.945096    45] Table 'system_redis.redis' created.
    $ redis-cli -p 6379 # can connect to Yedis from redis-cli> set key value
    OK> get key
    "value"> del key
    (integer) 1> get key

    Now we can use YugabyteDB as Redis. Update Rails configuration to enable to use Redis as its cache store.

    • Install necessary gems to use Redis.
      +gem 'redis', '~> 4.0'
      +gem 'redis-rails'
    • Update config file to specify :redis_store as cache_store in Rails
      +  config.cache_store = :redis_store, 'redis://localhost:6379/0/yuga_rails_cache', { expires_in: 30.seconds }
      +  config.active_record.cache_versioning = false

    After these procedures, it's able to use Rails.cache like:

    $ bundle exec rails c
    Running via Spring preloader in process 68202
    Loading development environment (Rails
    irb(main):001:0> Rails.cache.class
    => ActiveSupport::Cache::RedisStore
    irb(main):002:0> Rails.cache.fetch("User.first") { User.first }
      User Load (32.9ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
    => #<User id: 1, email: "", name: "user-0", gender: 0, created_at: "2020-06-23 13:34:12", updated_at: "2020-06-23 13:34:12">
    irb(main):003:0> Rails.cache.fetch("User.first") { User.first }
    => #<User id: 1, email: "", name: "user-0", gender: 0, created_at: "2020-06-23 13:34:12", updated_at: "2020-06-23 13:34:12">
    => #<User id: 1, email: "", name: "user-0", gender: 0, created_at: "2020-06-23 13:34:12", updated_at: "2020-06-23 13:34:12">
    irb(main):005:0> sleep 30
    => 30
    => nil



    YugabyteDB offers both PostgreSQL and Redis interfaces which can be used as backend database layers of Ruby on Rails applications. Thus, it frees us from the hell of "polyglot" databases. In addition to these, it also offers Cassandra API called Ycql so that it's easy to use key-value store if needed. However, the performance of YugabyteDB looks slower than using PostgreSQL, so it seems mandatory to evaluate its performance before using it in production environments.