Dowser.Migrator (Dowser.Client v0.5.0)

View Source

Runs the migrations (Dowser.Migration) of a repo, from Mix — mix dowser.migrate, mix dowser.rollback — or from a release, where Mix isn't available:

defmodule MyApp.Release do
  def migrate do
    Application.ensure_all_started(:dowser_client)
    Dowser.Migrator.run(MyApp.Search, :up, all: true, reindex: true)
  end
end

Where migrations live

In priv/dowser/migrations/ of the repo's application, or in the :migrations_path the repo is configured with, relative to the application's directory:

config :my_app, MyApp.Search, migrations_path: "priv/search/migrations"

What a run does

It takes the repo's migration lock — one migrator at a time — then plans the whole run before sending any change: each index's migrations are folded together, checked against the index's mapping, settings and version, so a migration that can't run fails with nothing changed (Dowser.Migration.Error).

The migrations it runs are recorded in the dowser_schema_migrations index.

An index that must be copied — a field modified, renamed or removed, a static setting — is copied into a new index only with reindex: true:

  1. writes to the index are blocked (write_block: false to skip it), so a document written during the copy can't be lost: the write fails instead, for the application to retry;
  2. the new index is created, and the documents copied into it — through the script of the migrations' field operations;
  3. the alias moves onto it;
  4. the old index stays, writes blocked, for a rollback to move the alias back — unless delete_previous: true.

If the copy fails, it is undone: the new index deleted, the old one unblocked. If even that fails, or the node running it dies, the lock stays with what the copy had changed, and every run refuses to start until recover/2 (mix dowser.recover) has undone it.

Rolling back

A rollback runs down/0, or change/0 inverted. An index the rolled-back migrations had copied moves back onto its previous index when it still exists — no copy, but the documents written since the migration aren't in it. Otherwise it is copied again (reindex: true), the operations reverted: a removed field's values are lost.

Summary

Types

A migration: its version, name and module (once loaded).

Functions

A plan from plan/3 as text: each index, then the requests sent for it.

repo's migrations — from its files and its records — with whether each has run: {:up | :down, version, name}, by version. A migration recorded without a file has the name "** FILE NOT FOUND **".

Where repo's migration files are: its :migrations_path, or priv/dowser/migrations, in its application's directory.

Plans a run as run/3 would — same options — without changing anything or taking the lock: the migrations it would run, and the requests it would send. format_plan/1 prints them.

Undoes what a run left behind when it stopped in the middle of copying an index — the node died, or undoing the failed copy failed — and releases the lock

Runs repo's pending migrations (:up) or rolls back the last ones (:down), returning the versions it ran.

Types

migration()

@type migration() :: %{version: integer(), name: String.t(), module: module() | nil}

A migration: its version, name and module (once loaded).

Functions

format_plan(arg)

@spec format_plan({[migration()], Dowser.Migration.Planner.plan()}) :: String.t()

A plan from plan/3 as text: each index, then the requests sent for it.

migrations(repo, opts \\ [])

@spec migrations(Dowser.Repo.t(), keyword()) :: [{:up | :down, integer(), String.t()}]

repo's migrations — from its files and its records — with whether each has run: {:up | :down, version, name}, by version. A migration recorded without a file has the name "** FILE NOT FOUND **".

migrations_path(repo)

@spec migrations_path(Dowser.Repo.t()) :: String.t()

Where repo's migration files are: its :migrations_path, or priv/dowser/migrations, in its application's directory.

plan(repo, direction, opts \\ [])

@spec plan(Dowser.Repo.t(), :up | :down, keyword()) ::
  {[migration()], Dowser.Migration.Planner.plan()}

Plans a run as run/3 would — same options — without changing anything or taking the lock: the migrations it would run, and the requests it would send. format_plan/1 prints them.

recover(repo, opts \\ [])

@spec recover(Dowser.Repo.t(), keyword()) :: {:ok, term()} | {:error, term()}

Undoes what a run left behind when it stopped in the middle of copying an index — the node died, or undoing the failed copy failed — and releases the lock:

  • the alias hadn't moved: the new index is deleted, the old one unblocked; returns {:ok, {:undone, index}};
  • the alias had moved: the copy is kept — as if the run had finished it, whose next run records the migrations; returns {:ok, {:kept, index}}.

Returns {:ok, :released} for a lock with no copy in progress, {:ok, :unlocked} without a lock.

run(repo, direction, opts \\ [])

@spec run(Dowser.Repo.t(), :up | :down, keyword()) :: [integer()]

Runs repo's pending migrations (:up) or rolls back the last ones (:down), returning the versions it ran.

Options:

  • :all — every pending migration (the default up), or every one run (down).
  • :step — the next/last n (down defaults to step: 1).
  • :to — up to and including that version (up), down to and including it (down).
  • :reindex — allow copying indices (default false): without it, a migration needing a copy aborts the run before it changes anything.
  • :write_block — block writes to an index while it is copied (default true).
  • :delete_previous — delete the index a copy replaced (default false).
  • :poll_interval — how often a copy's progress is checked, in milliseconds (default 1000).
  • :log — false to log nothing.
  • :migrations_path — where the migration files are.
  • :migrations — the migrations themselves, as {version, module}, rather than files.

Raises a Dowser.Migration.Error when the run can't be planned or a step fails, a Dowser.Migration.IrreversibleError when a rollback can't be.