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.
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.
Thanks for the suggestion @riebeekn . It seems that you just work around Triplex entirely. I’ll give that a shot.
If you try switching to Triplex.all - I would be very curious to hear if you run into the same issue we have.
@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