Ruby on Rails on YugabyteDB
2020-07-13
RubyDBYugabyteDB 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.
https://docs.yugabyte.com/latest/deploy/docker/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, 0.0.0.0:7000->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, 0.0.0.0:5433->5433/tcp, 0.0.0.0:6380->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-2.0.5.7-b0)
Type "help" for help.
yugabyte=# alter role yugabyte with password 'password';
ALTER ROLE
Then, we can connect to the YugabyteDB instance just as connecting to PostgreSQL.
$ psql -Uyugabyte -h127.0.0.1 -p5433 -W
Password:
psql (12.3, server 11.2-YB-2.0.5.7-b0)
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;
CREATE DATABASE
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-2.0.5.7-b0)
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 6.0.3.2
$ 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
development:
<<: *default
database: yuga_rails_development
username: <%= ENV.fetch("PGUSER") %>
password: <%= ENV.fetch("PGPASSWORD") %>
host: 127.0.0.1
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
t.timestamps
end
end
end
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 6.0.3.2)
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}@example.com", 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: "user-0@example.com", 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 mem_tracker.cc:250] MemTracker: hard memory limit is 1.651742 GB
I0623 13:42:41.867004 45 mem_tracker.cc:252] MemTracker: soft memory limit is 1.403981 GB
I0623 13:42:41.945003 45 table_creator.cc:261] Created table system_redis.redis of type REDIS_TABLE_TYPE
I0623 13:42:41.945096 45 yb-admin_client.cc:465] Table 'system_redis.redis' created.
$ redis-cli -p 6379 # can connect to Yedis from redis-cli
127.0.0.1:6379> set key value
OK
127.0.0.1:6379> get key
"value"
127.0.0.1:6379> del key
(integer) 1
127.0.0.1:6379> get key
(nil)
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.
gemfile
+gem 'redis', '~> 4.0' +gem 'redis-rails'
- Update config file to specify
:redis_store
ascache_store
in Rails./config/environments/development.rb+ 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 6.0.3.2)
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: "user-0@example.com", 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: "user-0@example.com", name: "user-0", gender: 0, created_at: "2020-06-23 13:34:12", updated_at: "2020-06-23 13:34:12">
irb(main):004:0> Rails.cache.read("User.first")
=> #<User id: 1, email: "user-0@example.com", 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
irb(main):006:0> Rails.cache.read("User.first")
=> nil
Yay!
Summary
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.