Dowser. Migration behaviour
(Dowser.Client v0.5.0)
View Source
A migration changes the indices of a repo, the way an Ecto.Migration
changes the tables of a database.
Migrations live in priv/dowser/migrations/ (see Dowser.Migrator for
another place), one per file, named after the UTC time they were generated
at — mix dowser.gen.migration create_events writes
20261104120000_create_events.exs. That timestamp is the migration's
version.
defmodule MyApp.Search.Migrations.CreateEvents do
use Dowser.Migration
def change do
create_index "events", %{
settings: %{number_of_shards: 3, number_of_replicas: replicas()},
mappings: %{
dynamic: :strict,
properties: %{
client_id: %{type: :keyword},
body: %{type: :text},
comments: %{type: :nested, properties: %{body: %{type: :text}}}
}
}
}
end
# No replicas on a development machine's single node.
defp replicas do
if Application.get_env(:my_app, :env) == :dev, do: 0, else: 1
end
endmix dowser.migrate runs the migrations not run yet, mix dowser.rollback
reverts the last one.
Indices are aliases
The name a migration gives — "events" — is always an alias. The index
behind it is named after the version that created it, events_20261104120000,
and its mapping records the version that last changed it in
_meta.dowser_version. A change the cluster can't make in place — a field
removed, renamed or retyped, a static setting — copies the documents into a
new index (events_<version>) and moves the alias onto it.
The separator between the name and the version is _ unless the index is
created with another — create_index "events", body, separator: "-" gives
events-20261104120000. It is the index's own: every later copy of it, and
the index a rollback recreates, uses it too.
Operations
The bodies are what the engine takes — the closest form to the final JSON — with atom or string keys. A migration is frozen history: give literal maps, or functions defined in the migration itself, rather than the application's own modules, which keep changing after the migration is written.
create_index/2— creates the index behind the alias.drop_index/2— deletes it.alter_index/2,3— changes its fields and settings, withadd/3,modify/3,rename/2,remove/2,set/3,unset/2andscript/2in its block.
Functions
Everything in a migration runs when the migration runs, not when it
compiles: any value can come from a function, so a migration can differ from
one cluster to another — locally and in production, from one repo or tenant
to the next. The function can depend on the repo being migrated — repo/0
returns it — or on your configuration. Don't use Mix.env/0: it doesn't
exist in a release; record the environment in your configuration instead
(config :my_app, env: config_env()).
change, up and down
change/0 describes the migration forward, and is reverted by running its
operations backwards, each one inverted. An operation that can't be inverted
without more information — removing a field without its definition, a
setting without from: — makes the rollback fail before anything is
changed, with a Dowser.Migration.IrreversibleError.
Or define up/0 and down/0; they are used as they are.
Copying documents
When an index is copied, each operation changing documents — rename/2,
remove/2, add/3 with a :default — becomes a step of one painless
script that dowser ships: the field names and values travel as the script's
parameters, never as script source. It walks each document's _source as
stored: a field inside a list of objects is changed in every object, at any
depth, a missing or null parent is skipped, and a field stored with a
dotted name ("address.zip": ...) is found too. script/2 adds your own
painless code between those steps.
Summary
Types
A field: its name, or the path to it through object and nested fields
([:address, :zip]).
A setting, relative to index (:number_of_replicas, or
[:analysis, :analyzer, :folded]); a leading :index is accepted.
Callbacks
The migration forward; reverted by inverting its operations.
The migration backward.
The migration forward.
Functions
Adds a field, given its mapping definition.
Changes the index behind the alias name with the operations in its block.
Creates the index behind the alias name, from body — the body of the
engine's create-index request (settings, mappings, aliases).
Deletes the index behind the alias name.
Changes a field's mapping definition; the documents are copied, so the
engine parses them again with the new one.
Removes a field; the documents are copied without it.
Renames a field, keeping it under the same parent; the documents are copied, their value moved to the new name.
The repo being migrated — the module, or {module, name} for a dynamic
instance. Only available while a migration runs.
Runs painless source on each document while it is copied — for what the
other operations don't do, a value converted, a field computed.
Sets a setting to value — a map for a group of settings.
Resets a setting to the engine's default.
Types
A field: its name, or the path to it through object and nested fields
([:address, :zip]).
A setting, relative to index (:number_of_replicas, or
[:analysis, :analyzer, :folded]); a leading :index is accepted.
Callbacks
Functions
Adds a field, given its mapping definition.
add :status, %{type: :keyword}
add [:address, :zip], %{type: :keyword}Its parent must exist. Added to the live index, unless default: is given:
then, the documents are copied and every object holding the field's parent
gets default where the field is missing. Reverted by removing it.
Changes the index behind the alias name with the operations in its block.
alter_index "events" do
add :status, %{type: :keyword}, default: "active"
rename [:comments, :body], :content
remove :legacy, %{type: :long}
set :number_of_replicas, 2, from: 1
endfrom: names the version the index must be at — the migration that last
changed it. Without it, the migrator works it out from the migrations it
has run.
Operations the engine applies in place — a field added without a default, a
dynamic setting — run on the live index. Any other — a field modified,
renamed, removed, added with a default, a static setting, a script — needs
the documents copied into a new index, which only happens when the migrator
is told to (mix dowser.migrate --reindex).
Creates the index behind the alias name, from body — the body of the
engine's create-index request (settings, mappings, aliases).
create_index "events", %{
settings: %{number_of_shards: 3},
mappings: %{dynamic: :strict, properties: %{title: %{type: :text}}}
}The index is named <name><separator><version>; its mapping's _meta gets
dowser_version. Reverted by drop_index/2.
Options:
:separator— between the name and the version (default"_"), e.g."-"forevents-20261104120000. It can't hold digits, uppercase letters, or a character index names don't allow. Every later copy of the index keeps it.
Deletes the index behind the alias name.
In change/0, give the index's body — as create_index/2 takes it — to
make it reversible: a rollback creates the index again, empty.
Changes a field's mapping definition; the documents are copied, so the
engine parses them again with the new one.
modify :group_id, %{type: :keyword}, from: %{type: :keyword, ignore_above: 256}from: is the definition it has: checked against the index's mapping before
anything changes, and needed to revert it.
Removes a field; the documents are copied without it.
remove :legacy, %{type: :long}Its definition is checked against the index's mapping before anything
changes, and makes it reversible — a rollback maps the field again, but
can't bring its values back.
Renames a field, keeping it under the same parent; the documents are copied, their value moved to the new name.
rename :body, :content
rename [:comments, :body], :contentA document already holding a field under the new name fails the copy rather than lose one of the two values. Reverted by renaming it back.
@spec repo() :: Dowser.Repo.t()
The repo being migrated — the module, or {module, name} for a dynamic
instance. Only available while a migration runs.
Runs painless source on each document while it is copied — for what the
other operations don't do, a value converted, a field computed.
script "ctx._source.amount = ctx._source.amount / 100.0",
reverse: "ctx._source.amount = (long) (ctx._source.amount * 100)"Options:
:params— the script's parameters, asparams.<name>; shared by all the scripts of one copy.:reverse— the painless source reverting it; without it, the script makeschange/0irreversible.
Sets a setting to value — a map for a group of settings.
set :number_of_replicas, 2, from: 1
set [:analysis, :analyzer, :folded], %{tokenizer: :standard, filter: [:asciifolding]}, from: nilA dynamic setting changes on the live index; a static one needs the documents copied.
from: is the value it has — nil for none — checked against the index
before anything changes, the migration aborting when they differ; and
needed to revert it.
Resets a setting to the engine's default.
unset :max_result_window, from: 50_000from: is the value it has, as for set/3.