Background migrations
A data migration changing millions of rows can take
hours. As a background migration, it does not hold up the deployment: up
only registers it as a job and goes on with the following migrations, your
application starts, and runs the jobs in the background with executeWork.
exports.migrate = async db => {
await db.update('orders', { currency: 'EUR' }, { currency: null });
await db.delete('sessions', ['created < ?', ['2024-01-01']], {
mode: 'soft',
column: 'deleted_at'
});
};
exports._meta = {
version: 2,
type: 'dml',
background: true
};
Background migrations need db-migrate 1.4.0 and a driver with the migration lock. Since 1.4.1 the jobs pause while migrations run, see Jobs and migrations.
Running the jobs
$ db-migrate work --parallel 2 --pause 50 --watch
runs the jobs in a process of its own, e.g. a container next to your
application, with the options below as --parallel, --pause, --batch,
--watch, --interval and --job-timeout. SIGINT and SIGTERM let the
jobs stop after their current batch, they continue with the next run. It ends
with exit code 1 if jobs failed. db-migrate work needs db-migrate 1.6.0.
Or run them inside your application with the programmable API:
const DBMigrate = require('db-migrate');
const dbm = DBMigrate.getInstance(true);
const worker = dbm.executeWork({
parallel: 2,
pause: 50,
batch: 500,
watch: true
});
// on shutdown
await worker.stop();
| Option | Default | |
|---|---|---|
parallel |
1 | jobs run at once by this worker, each on connections of its own |
pause |
0 | ms to wait between two batches of a job, to keep the load low |
batch |
1000 | rows per batch, unless the instruction sets its own batch |
watch |
false | keep looking for new jobs, instead of ending once none is left |
interval |
5000 | ms between looking for new jobs |
timeout |
60000 | ms after which the job of a worker which stopped responding is taken over |
executeWork returns right away with { done, stop }. done resolves with
{ done, failed }, the names of the jobs done and the jobs failed with
their error, once no job is left, or after stop(). stop() lets the
running jobs stop after their current batch, they continue with the next run.
Several instances
Any number of instances can call executeWork at once, e.g. every instance
of your application. Each takes one job at a time per parallel, a job runs
on one instance only. The jobs are taken in the order of their migrations.
A worker renews its jobs while running them. If an instance dies, its jobs
are taken over by another instance after timeout and continued where they
stopped, like an interrupted run.
The worker taking over has to be running, start it with watch: true to keep
it looking.
Blocking jobs
Jobs run independently of each other. If a job has to be done before the jobs after it start, mark it as blocking:
exports._meta = {
version: 2,
type: 'dml',
background: true,
blocking: true
};
The jobs of later migrations wait until it is done.
The state of a job
A background migration counts as run once its job is done. Until then it is
running: up and check log it as running in the background and do not run
it again. Once done, it is reverted by down like any data migration.
If a job fails, it is rolled back like a failing data migration and marked as
failed, workers do not take it again. The next db-migrate up queues it again,
fix the migration first. A failing job with steps which can not be reverted is
not rolled back and continues after them.
Reverting
down takes the jobs as the latest migrations, they come first, before the
migrations recorded as run. A job running or stopped half way is paused,
the steps it executed so far are reverted, and the job is forgotten. A job
not started yet is only forgotten. Once done, a background migration is
reverted like any data migration.
Reverting a job restores the rows it changed so far, from its backup tables
or by its marks, in the foreground and while holding the migration lock. For
a large job this can take as long as running it did, and other migrations
and the jobs wait meanwhile. Since db-migrate 1.4.1, before down missed the
jobs.
Jobs and migrations
Migrations take precedence over the jobs. Whenever up, down or fix has
something to run, it pauses the jobs after taking the
migration lock: no job is taken anymore, the
running ones stop after their current batch, and the migrations wait for
them:
[INFO] [jobs] waiting for 2 background job(s) to pause: 20261009120000-orders, 20261009130000-sessions
Once the migrations are done and the lock is released, the workers continue the jobs where they stopped. So a migration never changes a table while a job works on it, and a deployment waits at most for the batches running at that moment.
The pause lasts as long as the migrating process holds the lock. If it dies,
the jobs continue once its lock is considered stale, after --lock-timeout.
A job whose worker does not respond for the lock timeout is not waited for,
it is continued by another worker later.