haizop

haizop

How to run Triplex tenant migrations when deploying via mix release?

Scenario

We are using Triplex to manage our multi-schema Postges DB. We deploy using Mix releases, thus Mix is not available and we cannot migrate using the Triplex Mix tasks. We run our public schema migrations via a similar Release module to the one recommended in the Phoenix documentation. To that basic structure, we added an additional migrate_tenants/0 function:

defmodule MyApp.Release do
  @app :my_app

  def migrate do
    load_app()

    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  def migrate_tenants do
    load_app()

    for repo <- repos() do
      for tenant <- Triplex.all(repo) do
        Triplex.migrate(tenant, repo)
      end
    end
  end

  defp repos do
    Application.fetch_env!(@app, :ecto_repos)
  end

  defp load_app do
    Application.load(@app)
  end
end

Problem

The migrate_tenants function succeeds when run via a remote IEx session or rpc, but fails when run via eval, ie bin/my_app eval "MyApp.Release.migrate_tenants()". This is in contrast to the migrate function. The error observed is the following:

** (RuntimeError) could not lookup Ecto repo MyApp.Repo because it was not started or it does not exist
    lib/ecto/repo/registry.ex:19: Ecto.Repo.Registry.lookup/1
    lib/ecto/adapter.ex:127: Ecto.Adapter.lookup_meta/1
    lib/ecto/adapters/sql.ex:404: Ecto.Adapters.SQL.query/4
    lib/ecto/adapters/sql.ex:362: Ecto.Adapters.SQL.query!/4
    lib/triplex.ex:289: Triplex.all/1
    (myapp 0.1.0) lib/myapp/release.ex:21: anonymous fn/2 in MyApp.Release.migrate_tenants/0
    (elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
    (myapp 0.1.0) lib/myapp/release.ex:20: MyApp.Release.migrate_tenants/0

Questions

  • Is it possible to migrate tenants using Triplex via eval on a release?
  • Is there a known root cause for this issue?
  • Is there a recommended approach for using Triplex with Mix releases in general?

Note: I posted this as an issue on the Triplex repo several weeks ago, but there have been no responses thus far.

Most Liked

riebeekn

riebeekn

This is what we’re doing for our release module:

def migrate do
  {:ok, _} = Application.ensure_all_started(:my_app)

  migrate_public_schema()
  migrate_tenant_schemas()
end

defp migrate_public_schema do
  path = Application.app_dir(:my_app, "priv/repo/migrations")
  Migrator.run(MyApp.Repo, path, :up, all: true)
end

defp migrate_tenant_schemas do
  path = Application.app_dir(:my_app, "priv/repo/tenant_migrations")

  Accounts.list_tenants()
  |> Enum.each(&Migrator.run(MyApp.Repo, path, :up, all: true, prefix: &1.prefix))
end

Wasn’t aware of the Triplex.all function, I could probably replace the call to our Accounts module with that, but anyway using the Migrator.run function instead of Triplex.migrate has worked for us.

haizop

haizop

@riebeekn - This is what I have working now. Thanks again for steering me in the right direction.

defmodule MyApp.Release do
  @moduledoc """
  Used for executing DB release tasks when run in production without Mix
  installed.
  """

  alias Ecto.Migrator
  alias MyApp.Repo
  alias MyApp.Tenants

  @app :my_app

  def migrate_public_schema do
    load_app()

    {:ok, _, _} = Migrator.with_repo(Repo, &Migrator.run(&1, :up, all: true))
  end

  def migrate_tenant_schemas do
    load_app()

    Migrator.with_repo(Repo, fn repo ->
      Tenants.list_tenants()
      |> Enum.each(
        &Migrator.run(repo, tenant_migrations_path(), :up, all: true, prefix: &1.schema_name)
      )
    end)
  end

  def rollback_public_schema(version) do
    load_app()

    {:ok, _, _} = Migrator.with_repo(Repo, &Migrator.run(&1, :down, to: version))
  end

  def rollback_tenant_schemas(version) do
    load_app()

    Migrator.with_repo(Repo, fn repo ->
      Tenants.list_tenants()
      |> Enum.each(
        &Migrator.run(repo, tenant_migrations_path(), :down, to: version, prefix: &1.schema_name)
      )
    end)
  end

  defp tenant_migrations_path() do
    Triplex.migrations_path(Repo)
  end

  defp load_app do
    Application.load(@app)
  end
end

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New

We're in Beta

About us Mission Statement