Overbryd

Overbryd

# in my mix.exs

  def application do
    [
      mod: {My.Application, []},
      start_phases: [{:migrate, []}],
      extra_applications: [:logger, :runtime_tools]
    ]
  end
# in lib/my/application.ex

  def start_phase(:migrate, _, _) do
    Ecto.Migrator.with_repo(My.Repo, &Ecto.Migrator.run(&1, :up, all: true))
    :ok
  end

Works everytime™

Showing Posts 1 to 6

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Interesting!

I think the main downside compared to having the migrator in your supervision tree is that it doesn’t let you control where the migration happens relative to other items in your supervision tree. For example, we have basically:

[
        libcluster_child(),
        Sensetra.Endpoint,
        {Absinthe.Subscription, Sensetra.Endpoint},
        Sensetra.Repo,
        Sensetra.Repo.Migrator,
        Sensetra.Ingestion.Super,
        ... other children
        DeploymentNotifier
]

This is important because it allows the Ingestion.Super process to be sure that any database changes it relies on have definitely have happened by that point because the Migrator has run.

You’ll also note that I start the Endpoint pretty early. The way that works is that the /alive path returns true, but the /ready path returns false. This lets Kubernetes know that the pod is alive and running, but is not yet ready to receive traffic. It can take its time to run migrations, get the process tree up and running, and then the DeploymentNotifier child sets an application environment value such that /ready returns true.

13
Post #1
blatyo

blatyo

Conduit Core Team

One reason would be long running migrations, which effectively block each instance of your app from starting up.

Which is not necessarily to say don’t do it. But, be careful what you do there. Some migrations are better to run live, like stuff that transforms data, fills in a column with a default, etc.

Overbryd

Overbryd OP

Awesome answer, thank you.

Having a sensible split between /ready and /alive is a massive improvement and a great example on why to keep migrations situated in the supervision tree.

Overbryd

Overbryd OP

I tend to avoid doing long running migrations as a ORM-driven migration.

My approach there has always been to rebuild tables and once they are ready to swap them instantaneously. Doing that in the database itself, rather than coupling any application code to it.

I tend to think that the classical DBA approach sometimes has some value, as it is decoupling responsibilities from application development into database management.
And I do not think one should always hire a DBA, but just splitting those into separate tasks is usually de-escalating some heavier work on the database into its own domain. The application can keep running happily while the database is being worked on separately.

For example migrations in the supervision tree do not shield you from the problem, that your liveness-probe times out because of a long running migration.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yeah I also avoid long running migrations. Anything that could take a long time I always just find a backwards compatible way to do and then I do it via psql and let it take however long it needs while production carries on happily. Afterward I write an idempotent migration mostly to keep everyone’s dev / test environments up to date, and then deploy that, which should migrate instantly.

idi527

idi527

Hi @benwilson512, sorry for replying in an old thread, but I wonder what Sensetra.Repo.Migrator actually looks like, is it like a genserver that runs the migrations in its init?

I’m asking because I’ve tried a similar approach but with running the migrations in a task like

  def start(_type, _args) do
    migration = fn ->
      :timer.sleep(10000)
      IO.puts("Migrated")
    end

    readiness_notifier = fn ->
      IO.puts("ready to accept requests")
    end

    children = [
      Supervisor.child_spec({Task, migration}, id: :migration),
      Supervisor.child_spec({Task, readiness_notifier}, id: :readiness_notifier)
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: SupOrder.Supervisor]
    Supervisor.start_link(children, opts)
  end

and they ran (obviously in retrospect) concurrently:

> iex -S mix
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling 2 files (.ex)
Generated sup_order app
ready to accept requests
Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Migrated

So I guess it’s more like this

  defmodule Migration do
    use GenServer

    def start_link(opts) do
      GenServer.start_link(__MODULE__, opts)
    end

    def init(_opts) do
      :timer.sleep(10000)
      IO.puts("Migrated")
      :ignore
    end
  end

  def start(_type, _args) do
    readiness_notifier = fn ->
      IO.puts("ready to accept requests")
    end

    children = [
      Migration,
      Supervisor.child_spec({Task, readiness_notifier}, id: :readiness_notifier)
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: SupOrder.Supervisor]
    Supervisor.start_link(children, opts)
  end

which result in the correct order

> iex -S mix
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling 1 file (.ex)
Migrated
ready to accept requests
Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
— All posts loaded —

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
maennchen
:warning: Security advisory: Decimal DoS vulnerability A vulnerability has been published for decimal where very large exponents can cau...
New

Other Trending Topics Top

marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews