Elixir: Ectoのマイグレーションを自動化したり水平分割するためのライブラリYacto

gumitech

gumi TECH

Posted on November 27, 2018

Elixir: Ectoのマイグレーションを自動化したり水平分割するためのライブラリYacto

Ectoは、データベースを扱うとても便利なライブラリです。その使い勝手をさらによくするライブラリとしてYactoはつくられました。本稿は、Qiitaに公開されたドキュメント「[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った」にもとづいてご紹介します。

なお、ドキュメントは適宜更新されますので、本稿執筆時の情報であることを申し添えておきます。詳しくは、ドキュメントをご参照ください。

Yacto とは

Yactoは、Ectoに足りなかった部分をサポートするライブラリです。おもに、つぎの4つの機能があります。

  • マイグレーションファイルの自動生成
  • 別アプリケーションからのマイグレーションの利用
  • 水平分割したデータベースへのマイグレーション
  • 複数データベースにまたがるトランザクション(XAトランザクション)

マイグレーションファイルの自動生成

Yactoは、とくにマイグレーション周りがEctoと異なります。Yactoを使えば、Ectoのようにマイグレーションをわざわざ定義しなくても、スキーマからマイグレーションファイルが自動的に出力されるのです。

EctoとYactoのマイグレーションの違い

ライブラリ マイグレーション
Ecto スキーマとマイグレーションを別に定義
Yacto スキーマからマイグレーションファイルを自動的に出力

たとえば、lib/my_app/player.exにつぎのようなスキーマを定義したとしましょう。

defmodule MyApp.Player do
  use Yacto.Schema, dbname: :player

  schema @auto_source do
    # sharding key
    field :player_id, :string, meta: [null: false, size: 64]
    field :hp, :integer, default: 0, meta: [null: false]
    index :player_id, unique: true
  end
end
Enter fullscreen mode Exit fullscreen mode

ここでYactoにより、mix yacto.gen.migrationを実行します。すると、以下のようなマイグレーションファイルpriv/migrations/2017-11-22T045225_my_app.exsが出力されるのです。

defmodule MyApp.Migration20171122045225 do
  use Ecto.Migration

  def change(MyApp.Player) do
    create table("my_app_player")
    alter table("my_app_player") do
      add(:hp, :integer, [null: false, size: 64])
      add(:player_id, :string, [null: false])
    end
    create index("my_app_player", [:player_id], [name: "player_id_index", unique: true])
  end

  def change(_other) do
    :ok
  end

  def __migration_structures__() do
    [
      {MyApp.Player, %Yacto.Migration.Structure{fields: [:id, :player_id, :hp], meta: %{attrs: %{hp: %{null: false}, player_id: %{null: false, size: 64}}, indices: %{{[:player_id], [unique: true]} => true}}, source: "my_app_player", types: %{hp: :integer, id: :id, player_id: :string}}},
    ]
  end

  def __migration_version__() do
    20171122045225
  end
end
Enter fullscreen mode Exit fullscreen mode

あとはmix yacto.migrateを実行すれば、このマイグレーションファイルがデータベースに反映されます。もうマイグレーションファイルをいちいち書く必要はありません。

さらに、スキーマにフィールドが追加された場合は、差分だけをマイグレーションファイルに出力して、データベースに反映する機能も備わっています。

別アプリケーションからのマイグレーションの利用

先ほどの例のアプリケーションmy_appを、別のアプリケーションが利用することになったとします。すると、my_appはデータベースを使っているので、そのアプリケーション上でmy_appのためのマイグレーションが必要です。

Ectoでは、他のアプリケーションが必要としているマイグレーションを自分で書くか、それぞれのアプリケーションが指定するばらばらな方法でマイグレーションしなければなりません。

けれど、Yactoを使えば、config/config.exsを適切に書いて、my_appを利用するアプリケーションでつぎのコマンドさえ実行すればよいのです。これで、my_appのマイグレーションができます。つまり、Yactoを使っているアプリケーションでは、すべて同じ方法でマイグレーションができるということです。

mix yacto.migrate --app my_app
Enter fullscreen mode Exit fullscreen mode

水平分割したデータベースへのマイグレーション

たとえば、MyApp.Playerスキーマを水平分割した場合、このスキーマのマイグレーションファイルを複数のRepoに適用しなければなりません。これは、設定ファイルconfig/config.exsにつぎのように書くだけで済みます。

config :yacto, :databases,
  %{
    default: %{
      module: Yacto.DB.Single,
      repo: MyApp.Repo.Default,
    },
    player: %{
      module: Yacto.DB.Shard,
      repos: [MyApp.Repo.Player0, MyApp.Repo.Player1],
    },
  }
Enter fullscreen mode Exit fullscreen mode

MyApp.PlayerYacto.Schemaを使うuse/2には、つぎのようにdbname: :playerのオプションが定めてありました。この:playerが、MyApp.Playerの所属するRepoのグループ名です。

defmodule MyApp.Player do
  use Yacto.Schema, dbname: :player

  ...
Enter fullscreen mode Exit fullscreen mode

:playerRepoグループは、設定ファイルでMyApp.Repo.Player0MyApp.Repo.Player1のRepoを紐づけています。あとはmix yacto.migrateを実行すれば、MyApp.PlayerのマイグレーションファイルがMyApp.Repo.Player0MyApp.Repo.Player1に適用されるのです。

水平分割したデータベースを利用するときは、Yacto.DB.repo/2(あるいはschema.repo/1)を使ってRepoが取得できます。

repo = Yacto.DB.repo(:player, player_id)
MyApp.Player |> repo.all()
Enter fullscreen mode Exit fullscreen mode

複数データベースにまたがるトランザクション(XAトランザクション)

Yacto.transaction/2を使うと、複数のデータベースを指定してトランザクションが発行できます。

# 2つ以上のRepoが指定されているので XA トランザクションを発行する
Yacto.transaction([:default,
                   {:player, player_id1},
                   {:player, player_id2}], fn ->
  default_repo = Yacto.DB.repo(:default)
  player1_repo = Yacto.DB.repo(:player, player_id1)
  player2_repo = Yacto.DB.repo(:player, player_id2)

  # このあたりでデータベースを操作する
  ...

# ここですべてのXAトランザクションがコミットされる
end)
Enter fullscreen mode Exit fullscreen mode

つぎの3つのRepoに対してトランザクションが行われます。

  • :defaultRepoMyApp.Repo.Default
  • player_id1でシャーディングされたRepo
  • player_id2でシャーディングされたRepo

あとのふたつは、シャードキーによっては同じRepoになる可能性があるので、利用するRepoはふたつか3つのどちらかです。ふたつ以上のRepoを利用してトランザクションを開始する場合は、自動的にXAトランザクションになります。

その他

Yactoのおもな機能について、かいつまんでにご説明しました。さらに詳しくは、前出「[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った」をお読みください。Yactoのスキーマの定め方や使い方、および便利関数についても解説されています。

💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on November 27, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related