この記事はdev.toで発表されたものです。
このチュートリアルは、RailsとTiDBの統合に関する、おそらくWebで初めてのチュートリアルです。TiDBは、水平的なスケーラビリティ、高可用性、MySQLとの互換性を特徴とする、オープンソースの分散SQLデータベースです。
ビギナーにとって、ActiveRecordのような複雑なORMをTiDBに統合するのは難しい可能性があります。Web上にはこのトピックを扱っている記事がほとんどないことから、私はRailsユーザーによるTiDB導入を支援するために、このチュートリアルを執筆しました。
TiDB開発環境をローカルで構築する
Railsとの統合に先立って、TiDBクラスタをローカルマシンにデプロイする必要があります。TiDBでは、TiDBエコシステム用のパッケージマネージャーであるTiUPによって、デプロイを円滑に進められるようになっています。
TiUPのインストール
TiUPのインストールは、Darwin、Linuxのどちらのオペレーティングシステムからも簡単に行うことができます。次のコマンドを実行するだけです。
$ curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
これによって$HOME/.tiup/bin
がPATH
環境変数に追加され、TiUPを直接使用できるようになります。
ローカルクラスタの確立
一般的にTiDBクラスタは複数のノードを必要とするため、デプロイが複雑で時間がかかる場合があります。そのためTiUPは、TiDBのテスト環境をローカルですばやく構築できる、TiUPコンポーネントのplayground
を用意しています。
このコマンドは次のようにシンプルなものです。
$ tiup playground
次のように出力されます。
$ 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
これでローカルクラスタが確立されました。
TiDB Dashboardへのアクセス
TiDBには、クラスタを監視するためのWeb UI、TiDB Dashboardが用意されています。これはすでにPDコンポーネントに組み込まれており、http://127.0.0.1:2379/dashboardから直接アクセスできます。

TiUPの詳細については、公式ドキュメントをご覧ください。
RailsをTiDB用に設定する
TiDBクラスタを確立したところで、次にこれをRailsと統合します。
Railsプロジェクトの作成
TiDBはMySQLとの互換性があるため、MySQL用に設定されたRailsアプリを作成できます。
$ rails new myapp --database=mysql
database.yml
の設定
database.yml
では次の2つの重要な設定があります。
port
を4000
に設定します。ローカルのTiDBクラスタは、4000
をデフォルトのポートとして使用します。- データベース接続変数
tidb_enable_noop_functions
をON
に設定します。Railsは、TiDBではデフォルトで無効になっているget_lock
関数を使用する必要があります。
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
URIメソッドを使用してデータベース接続を設定する場合も、設定方法は同様です。
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
プライマリキー、自動インクリメント、固有インデックスの設定
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
固有インデックスを追加します。
class AddUniqueIndexForEmail < ActiveRecord::Migration[6.1] def change add_index :users, :email, unique: true end end
TiDBはMySQLとの互換性があるため、使用方法はスタンドアロンのMySQLデータベースとほとんど同じです。プライマリキー、自動インクリメント、固有インデックスなどの機能について互換性がない他の分散データベースと比較して、追加の処理が不要なTiDBは導入が非常に容易です。
ここまでで、次のようなデータテーブルが生成されます。
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)
セーブポイントへのパッチの追加
TiDBとActiveRecordを結合するうえで1つだけ障害になるのが、現時点ではTiDBではセーブポイントがサポートされていないことです。そこで、これを解決する簡単なパッチを作成しました。
# 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)
Railsでセーブポイントを使用するのは、トランザクションがtrue
をパラメータrequires_new
に渡す場合に限られます。その場合は、このパッチによってrequires_new
の値がnil
に変更され、移行のためのログが出力されます。
私の経験では、Railsプロジェクトでセーブポイントが頻繁に使用されることはほとんどないため、移行が必要になった場合でも大きな問題はありません。移行を行う場合はセーブポイントを使用しますが、並行して実行する移行がなければ、セーブポイントを削除しても予期しない結果になることはありません。