LoginSignup
6
6

More than 3 years have passed since last update.

ActiveResourceとマルチDBのどちらを使うべきか?

Last updated at Posted at 2021-04-02

ActiveResourceとrails6以降複数DB接続機能のメリットとデメリットをご紹介していきます。
どちらを使うべきかはそれぞれのメリットデメリットにみて、ご自身で検討してください。

  • ActiveResourceのとは
  • ActiveResourceの使い方
    • Rails間でリソースのCRUD操作をする
    • Prefix(接頭辞)を指定する
    • Validationを設定する
  • ActiveResourceのメリット・デメリット
    • メリット
    • デメリット
  • Rails6で追加された複数DB接続
    • 複数DBを使ってみる
    • どのように複数DBへ接続するのか
  • primary/replica
    • コネクションの自動切り替え
    • primary/replicaの挙動の確認
  • 複数DB接続できるようになったメリット・デメリット
    • メリット
    • デメリット

ActiveResourceとは

ActiveResourceは、Rails2.0〜3.x に搭載されていた機能です。
Rails4.0 からは搭載されていませんが、Gemを追加することで利用
可能です。

複数のRailsアプリを繋ぐことができ、互いのモデルをActiveRecordのように扱うことが可能になります。

ActiveResourceの使い方

ActiveResourceを始めるための準備はとても簡単です。
gemである、activeresourceを追加するだけで利用が可能になります。

https://github.com/rails/activeresource

アプリAとアプリBがあり、アプリAからアプリBのリソースを取得する実装を例にとって解説します。

前提として、

  • アプリAには、ActiveResource::Baseを継承したTaskクラスを作成しておきます。(ControllerやRouteの定義は不要)
app/models/task.rb
class Task < ActiveResource::Base
end
  • アプリBにはTaskというモデルをScaffoldで生成し、ローカルホスト3001番で起動している状態です。

Scaffoldについての詳細はこちら

https://railsguides.jp/command_line.html#rails-generate

Rails間でリソースのCRUD操作をする

アプリAからアプリBに接続してリソースの取得や更新などを実行したい場合は、アプリAのTaskクラスに以下のよう記述を追加します。

class Task < ActiveResource::Base
  self.site = 'http://localhost:3001'
end

設定が完了したら、通常のActiveRecordのようリソースの作成、取得、更新、削除といったCRUD操作が可能になります。

$ bundle exec rails c
# リソースの生成
> Task.create(title: "作成")
# インスタンスを生成して保存
> task = Task.new(title: "作成")
> task.save
# リソースの取得
> Task.all
# 単一リソースの取得
> Task.find(n)
# 単一リソースの更新
> Task.find(n).update_attributes(title: "更新")
# 単一リソースの削除
> Task.find(n).destroy

Prefix(接頭辞)を指定する

以下のようなルーティングを定義している場合、通常通り、モデル名.find(n)などとして、リソースの取得が可能ですが、

config/routes.rb
Rails.application.routes.draw do
  resources :tasks
end

namespaceなどが入ってたりする場合には、取得する側(アプリA)でPrefixを指定する必要があります。

config/routes.rb
Rails.application.routes.draw do
  namespace "api" do
    namespace "v1" do
      resources :tasks
    end
  end
end
app/models/task.rb
class Task < ActiveResource::Base
  self.site = 'http://localhost:3001'
  self.prefix = '/api/v1/'
end
# Task.idが 1 のデータを取得
> Task.find(1)
> #<Task:0x00007fc2a8d01ca0 @attributes={"id"=>1, "title"=>"こんちわ!", "description"=>"ほげほげ", "is_done"=>true, "created_at"=>"2021-03-25T08:49:31.627Z", "updated_at"=>"2021-03-25T08:49:31.627Z", "url"=>"http://localhost:3001/api/v1/tasks/1.json"}

返ってきたデータのurlをみてみると

"url"=>"http://localhost:3001/api/v1/tasks/1.json"

/api/v1/といったprefixが指定されています。

Validationを設定する

Validationも通常のRailsアプリケーション同様に使うことができます。
Taskのtitleが空であるかの検証を追加します。空であればエラーメッセージが返ります
。アプリBのTaskモデルに以下を追加します。

class Task < ActiveRecord::Base
  validates :title, presence: true
end

アプリAのrails consoleからtitleが空のインスタンスを生成して保存してみます。

irb(main) > task = Task.new(title: "")
irb(main) > task.save
=> false
irb(main) > task.errors.full_messages
=> ["title can't be blank"]

ActiveResourceのメリット・デメリット

メリット

  • 複数のRailsアプリケーションが疎結合になり、microserviceのようなアーキテクチャを構築することができ、サービス自体をスケールしやすい。

デメリット

  • 別アプリのDBからAPIに過剰にアクセスするため、パフォーマンスが落ちる。

参考記事URL

Rails6から追加された複数DB接続

アプリケーションの規模が大きくなってくると、データの数が膨大になり、DBのパフォーマンスも低下してしまいます。

そういった場合にDBをスケールさせる必要が出て来ますが、Rails6以前では複数DBが標準サポートがなされていなかったため、設定をするのが少し大変であったり、ActiveResourceといった機能を利用したりしていました。

Rails6からは複数DBを標準サポートするようになったため、DBのスケールさせることが容易になりました。

この記事を執筆している2021年4月の段階では、複数DB機能として、具体的なサポート内容は以下の4つのようです。

  • 複数の「primary」データベースと、それぞれに対応する1つの「replica」
  • モデルでのコネクション自動切り替え
  • HTTP verbや直近の書き込みに応じたprimaryとreplicaの自動スワップ
  • マルチプルデータベースの作成、削除、マイグレーション、やりとりを行うRailsタスク

RubyonRailsガイドより抜粋より抜粋

尚、DBを跨いでのテーブルのJOINや、一つテーブルを複数のDBのテーブルに分割して負荷分散させるシャーディングなどはまだサポートされていません。

複数DBを使ってみる

複数DBを使用できるようにするには、はじめにdatabase.ymlを編集します。

primaryとreplicaは同じデータを持つため、同じdatabase名にします。

replicaのDBに、replica: trueを指定してあげることでRails側でreplicaを認識してもらいます。

migration_pathを指定して、primary_databasesecondary_databaseのmigrationファイルのパスを分けます。

config/database.yml
development:
  primary:
    <<: *default
    database: primary_database
  primary_replica:
    <<: *default
    database: primary_database
    replica: true
  secondary:
    <<: *default
    database: secondary_database
    migrations_paths: db/secondary_migrate
  secondary_replica:
    <<: *default
    database: secondary_database
    replica: true

それぞれDBへ接続するための設定を追加します。
secondaryに関しては抽象クラスを作成する必要があります。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  # 書き込み、読み込み先の設定
  connects_to database: { writing: :primary, reading: :primary_replica }
end

# app/models/secondary_base.rb
class SecondaryBase < ApplicationRecord
  self.abstruct_class = true

  # 書き込み、読み込み先の設定
  connects_to database: { writing: :secondary, reading: :secondary_replica }
end

これで複数DBを扱う準備が整ったので、bundle exec rails -Tでコマンドを確認してみましょう

rails db:create                          # Creates the database from DATABASE_URL or config/database.yml for the current RAI...
rails db:create:primary                  # Create primary database for current environment
rails db:create:secondary                # Create secondary database for current environment
rails db:drop                            # Drops the database from DATABASE_URL or config/database.yml for the current RAILS...
rails db:drop:primary                    # Drop primary database for current environment
rails db:drop:secondary

primary_databaseとsecondary_databaseに対して操作できるコマンドが増えていると思います。

migrationファイルの生成

次にmigrationの設定をします。以下コマンドではsecondary_databaseのmigrationファイルを作成しています。

$ bundle exec rails g migration CreateAdmin name:string email:string --database secondary

--databaseオプションでどのDBのマイグレーションファイルが作成するのかを指定します。

実行ができたら、database.ymlにて指定した、migration_pathsのディレクトリが生成され、ディレクトリ配下に先ほど生成したmigrationファイルが配置されます。

migrationを実行する際にも、databaseを指定します。

$ bundle exec rails db:migrate:secondary

モデルファイルの生成

次にモデルファイル作成します。
Adminはsecondary_databaseからデータの読み書きを行うので、継承元をSecondaryBaseとします。

# class Admin < ApplicationRecord
class Admin < SecondaryBase # こちらに変更
end

複数DBの挙動の確認

実際にデータを追加してみて意図したDBへ登録されているか確認します。

Adminはsecondary_databaseに登録されるはずです。

# secondaryDBにデータを投入
> Admin.create(name: "sample", email: "sample@email.com")

# primaryDBを確認する
mysql > use primary_database;
mysql > SELECT * FROM admins;
ERROR XXX: Table 'primary_database.admins'....

# secondaryDBを確認する
mysql > use secondary_database;
mysql > SELECT * FROM admins:
+----+-------+------------------+----------------------------+----------------------------+
| id | name  | email            | created_at                 | updated_at                 |
+----+-------+------------------+----------------------------+----------------------------+
|  1 | admin | sample@email.com | 2021-03-26 11:54:32.952708 | 2021-03-26 11:54:32.952708 |
+----+-------+------------------+----------------------------+----------------------------+
1 row in set (0.00 sec)

これで正しいDBへ登録されていることが確認できました。

primary/replica

Rails6からは複数DBに加えて、primary/replicaというDBの負荷分散の仕組みも追加されました。

GETなど読み込み専用のDBとして replica(複製)へ、POST,PUT,PATCH,DELETEなどDBへの書き込みが伴うリクエストは primary(書込)へ接続します。

Railsはこのprimary、replicaをリクエストに応じて自動で切り替えることができます。

コネクションの自動切り替え

primary/replicaを自動で切り替えられるように設定します。

先述したように、primaryは書き込み専用(POST,PUT,PACTH,DELETE)であり、replicaは読み込み専用(GET)とするため、リクエストに応じて、接続先を変更する必要があります。

以下をapplication.rbに追加します。

config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session

primary/replicaの挙動の確認

次にリクエストに応じてprimary/replicaが切り替わるか確認します。

クエリのログにDBの接続状況を出力させたいのでgem arproxyをインストールし、設定をします。

# config/initialize/arproxy.rb
if Rails.env.development? || Rails.env.test?
  require mulitiple_database_connection_logger
  Arproxy.configure do |config|
    config.adapter = mysql2
    config.use MultipleDatabaseConnectionLogger
  end
  Arproxy.enable!
end

# lib/multiple_database_connection.rb
class MultipleDatabaseConnectionLogger < Arproxy::Base
  def execute(sql, name = nil)
   role = ActiveRecord::Base.current_role
   name = #{name} [#{role}]”
   super(sql, name)
  end
 end

実際にリクエストを送信して、レスポンスが確認できるように、routingcontrollerを、追加します。

Rails.application.routes.draw do
  resources :admins
end
class AdminsController < ApplicationController
  protect_from_forgery with: :null_session
  before_action :set_admin, only: %i[show update destroy]

  def index
    admins = Admin.all
    render json: { sataus: :ok, data: admins }
  end

  def show
    render json: { status: :ok, data: @admin }
  end

  def create
    @admin = Admin.create!(controller_params)
    render json: { status: :ok, data: @admin }
  end

  def update
    @admin.update(controller_params)
    render json: { status: :ok, data: @admin }
  end

  def destroy
    @admin.destroy
    render json: { status: :ok, message: "deleted admin" }
  end

  private

  def controller_params
    params.require(:admin).permit(:name, :email)
  end

  def id
    params[:id]
  end

  def set_admin
    @admin = Admin.find(id)
  end
end

ここまでできたら、curlコマンドでリクエストを送信して、read-writeが切り替わっているか確認してみます。

取得などのリクエストの場合は[reading]、書き込みや更新、削除リクエストの場合は[writing]になっているのがわかると思います。

  • リソースの一覧を取得

curl -X GET localhost:3000/admins

Admin Load [reading](3.4ms)
SELECT
    `admins`.*
FROM
    `admins`
  • リソースの作成

curl -X POST -d "admin[name]=hoge, admin[email]=hogehoge@email.com" localhost:3000/admins

Admin Create [writing](
    14.8ms
)
INSERT INTO `admins`(
    `name`,
    `created_at`,
    `updated_at`
)
VALUES(
    'hoge, admin[email]=hogehoge@email.com',
    'YYYY-MM-DD 09:23:59.939996',
    'YYYY-MM-DD 09:23:59.939996'
)
  • 単一リソースの取得

curl localhost:3000/admins/1

Admin Load [reading](1.9ms)
SELECT
    `admins`.*
FROM
    `admins`
WHERE
    `admins`.`id` = 1
LIMIT 1
  • リソースの更新

curl -X PUT -d “admin[name]=hoge” localhost:3000/admins/1

Admin Update [writing](24.8ms)
UPDATE
    `admins`
SET
    `admins`.`name` = 'hoge',
    `admins`.`updated_at` = 'YYYY-MM-DD 09:25:58.924513'
WHERE
    `admins`.`id` = 1
  • リソースの削除

curl -X DELETE localhost:3000/1

Admin Destroy [writing](6.8ms)
DELETE
FROM
    `admins`
WHERE
    `admins`.`id` = 1

複数DBのメリットデメリット

メリット

  • 一つのアプリケーションで膨大なデータを扱えるようになることや、primary-replicaを活用し、DBの仕事を分散させることで、DB自体のパフォーマンスを保ちやすい。

  • activeresourceを使う必要がないので、実装コストを抑えることができ、httpリクエストをしないので、サーバーのパフォーマンスを抑えることができる。

デメリット

  • DBを跨いだ テーブル同士のJOINができない。

記事引用元

6
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6