Author: Hooopo (Active contributor of Ruby China)
Editor: Ran Huang
An original version of this article was published on dev.to.
This tutorial, perhaps, is the first Rails + TiDB integration tutorial across the web. TiDB is an open-source, distributed SQL database that features horizontal scalability, high availability, and MySQL compatibility.
For beginners, integrating a complex ORM like ActiveRecord with TiDB could be difficult. Since there are few articles on the web touching this topic, I wrote this tutorial to help Rails users to get started with TiDB.
Build a local TiDB development environment
Before integrating with Rails, you need to deploy a TiDB cluster on your local machine. TiDB provides a smooth deployment experience using TiUP, a package manager for the TiDB ecosystem.
Install TiUP
Installing TiUP is pretty straightforward for either Darwin or Linux operating systems. All you need to do is a single line of command:
{{< copyable “shell-regular” >}}
curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
This will also add $HOME/.tiup/bin
in your PATH
environment variable, so you can use TiUP directly.
Spin up a local cluster
As a typical TiDB cluster requires multiple nodes, deploying it might be time-consuming and complicated. Therefore, TiUP offers playground
, a TiUP component that enables you to quickly build a local TiDB test environment.
The command is as simple as follows:
{{< copyable “shell-regular” >}}
tiup playground
You’ll see the output like this:
tiup playground
Starting component ``playground``: /Users/hooopo/.tiup/components/playground/v1.4.1/tiup-playground
Use the latest stable version: v5.0.0
Specify version manually: tiup playground <version>
The stable version: tiup playground v4.0.0
The nightly version: tiup playground nightly
Playground Bootstrapping...
Start pd instance
Start tikv instance
Start tidb instance
Waiting for tidb instances ready
127.0.0.1:4000 ... Done
Start tiflash instance
Waiting for tiflash instances ready
127.0.0.1:3930 ... Done
CLUSTER START SUCCESSFULLY, Enjoy it ^-^
To connect TiDB: mysql --host 127.0.0.1 --port 4000 -u root -p (no password)
To view the dashboard: http://127.0.0.1:2379/dashboard
To view the Prometheus: http://127.0.0.1:9090
To view the Grafana: http://127.0.0.1:3000
Congrats! Your local cluster is now up and running.
Access TiDB Dashboard
TiDB provides TiDB Dashboard, a Web UI for monitoring your cluster. It is already built into the PD component, so you can directly access it via http://127.0.0.1:2379/dashboard.
For more information on TiUP, see the official documentation.
Configure Rails for TiDB
Now that you have a running TiDB cluster, the next step is to integrate it with Rails.
Create a Rails project
Because TiDB is compatible with MySQL, you can create a Rails app configured for MySQL:
rails new myapp --database=mysql
Configure database.yml
There are two configurations you need to pay attention to in database.yml
:
- Set
port
to4000
. The local TiDB cluster uses4000
as the default port. - Set the database connection variable
tidb_enable_noop_functions
toON
. Rails needs to use theget_lock
function. It is not implemented in TiDB, but a workaround is available.
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
port: 4000
username: root
password:
host: 127.0.0.1
variables:
tidb_enable_noop_functions: ON
If you configure the database connection using the URI method, the configuration is similar:
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
url: <%= ENV.fetch("DB_URL") || "mysql2://root:pass@localhost:4000/myapp" %>
variables:
tidb_enable_noop_functions: ON
Configure primary key, auto-increment, unique-index
Create a table named users
:
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :email
t.string :password
t.string :username
t.timestamps
end
end
end
Add a unique index:
class AddUniqueIndexForEmail < ActiveRecord::Migration[6.1]
def change
add_index :users, :email, unique: true
end
end
Because TiDB is compatible with MySQL, the usage is almost identical to that of a standalone MySQL database. Compared with other distributed databases that are incompatible in terms of features like primary keys, auto-increment, and unique indexes, TiDB is much more easy to get started with because no extra handling is required.
Now you can take a look at the generated data table:
mysql> show create table users;
+ -------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
| users | CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
`created_at` datetime(6) NOT NULL,
`updated_at` datetime(6) NOT NULL,
PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
UNIQUE KEY `index_users_on_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001 |
+-------+------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------+
1 row in set (0.01 sec)
Add a patch for savepoints
The only obstacle to combining TiDB and ActiveRecord is that TiDB doesn’t support savepoints for now, so I wrote a simple patch to solve it:
# https://github.com/rails/rails/blob/6-1-stable/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L313
require 'active_record/connection_adapters/abstract/database_statements.rb'
module DisableSavepoint
def transaction(requires_new: nil, isolation: nil, joinable: true)
if requires_new
requires_new = nil
Rails.logger.warn "savepoint statement was used, but your db not support, ignored savepoint."
Rails.logger.warn caller
super(requires_new: requires_new, isolation: isolation, joinable: joinable)
else
super(requires_new: requires_new, isolation: isolation, joinable: joinable)
end
end
end
ActiveRecord::ConnectionAdapters::DatabaseStatements.send(:prepend, DisableSavepoint)
In Rails, savepoints are introduced only when the transaction passes true
to the parameter requires_new
. When that happens, this patch will change the value of requires_new
to nil
and output logs for migration.
From my experience, most Rails projects don’t use savepoints very often, so migration wouldn’t be much of a problem if needed. A savepoint is introduced when you perform migration, but if there’s no concurrent migration, removing it won’t bring unexpected consequences.
References
TiDB Cloud Dedicated
TiDB Cloudのエンタープライズ版。
専用VPC上に構築された専有DBaaSでAWSとGoogle Cloudで利用可能。
TiDB Cloud Serverless
TiDB Cloudのライト版。
TiDBの機能をフルマネージド環境で使用でき無料かつお客様の裁量で利用開始。