32afbd0d15
Pregenerate the database schema we should use for most tests. Currently, Cockroach is slow with regards to migration and it's better if it happens in as few transactions as possible. This reduces test time from ~21min to ~15min. Change-Id: Ife8117053e6b9ecf3c93fe63677edf15d4d7c254 |
||
---|---|---|
.. | ||
postgres.v103.sql | ||
postgres.v104.sql | ||
postgres.v105.sql | ||
postgres.v106.sql | ||
postgres.v107.sql | ||
postgres.v108.sql | ||
postgres.v109.sql | ||
postgres.v110.sql | ||
postgres.v111.sql | ||
postgres.v112.sql | ||
postgres.v113.sql | ||
postgres.v114.sql | ||
postgres.v115.sql | ||
postgres.v116.sql | ||
postgres.v117.sql | ||
postgres.v118.sql | ||
postgres.v119.sql | ||
postgres.v120.sql | ||
postgres.v121.sql | ||
postgres.v122.sql | ||
postgres.v123.sql | ||
postgres.v124.sql | ||
postgres.v125.sql | ||
postgres.v126.sql | ||
postgres.v127.sql | ||
postgres.v128.sql | ||
postgres.v129.sql | ||
postgres.v130.sql | ||
postgres.v131.sql | ||
postgres.v132.sql | ||
postgres.v133.sql | ||
postgres.v134.sql | ||
postgres.v135.sql | ||
postgres.v136.sql | ||
postgres.v137.sql | ||
postgres.v138.sql | ||
postgres.v139.sql | ||
postgres.v140.sql | ||
postgres.v141.sql | ||
postgres.v142.sql | ||
postgres.v143.sql | ||
postgres.v144.sql | ||
postgres.v145.sql | ||
postgres.v146.sql | ||
postgres.v147.sql | ||
README.md |
Migrations and You
This document is best read in the voice of one of those 50s instructional videos. You know the ones, like this one. If you want a sound track to go with it, I would recommend playing this in the background.
Mechanics
In order to understand the right way to add a new migration with associated tests, a basic understanding of how the tests run and what they check is required. The migration tests work by having
- A single database kept for the duration of the test that the individual migrations are run on one at a time in order
- An expected snapshot for the database for every single step
- A sql file for each snapshot to generate it
For a given step, what happens is
- The
-- OLD DATA --
section is just run on the database being migrated - The main section and
-- NEW DATA --
sections are run on the snapshot - The migration is run against the main database
- The
-- NEW DATA --
section is run on the database
Then, the snapshot and current database state are compared for schema and data differences.
How to Create a Migration
With that basic overview out of the way, the steps to create a new migration are
- Create an empty
postgres.vN.sql
file in this folder. - Copy the
satellitedb.dbx.pgx.sql
file from thesatellitedb/dbx
folder. This ensures that the snapshot does not drift from the dbx sql file. We bootstrap tests from the dbx output, so the correctness of our tests depends on them matching. - Copy the
INSERT
statements from the end of the previous migration. These lines are after theCREATE INDEX
lines but before any-- NEW DATA --
or-- OLD DATA --
section. - Copy the
-- NEW DATA --
statements from the end of the previous migration into the main section. They are no longer-- NEW DATA --
. You should only need to copyINSERT
statements.
Depending on what your migration is doing, you should then do one of these:
- If your migration is creating new tables, add an
INSERT
for it in the-- NEW DATA --
section. This ensures that future migrations don't break data in that table. Our test coverage depends on theseINSERT
s existing and being complete. Help avoid problems in production, now. Smokey the Bear says only you can prevent production fires. - If you are updating data in old tables or changing the table schema
a. and there is previous data (which there should be, by point 1), change the existing row in the main section to be the new updated value.
b. and there is no previous data, add an insert into the
-- OLD DATA --
section as well as a corresponding updated row like in point a. Unfortunately, someone got away with adding a table without adding a row, but no longer. The tests must forever grow. - Anything more complicated, look at the mechanics section again to see if there's a way to solve your problem. If it is unclear (it probably is), just ask for help. Maybe the brains of multiple people can figure out something to do.
Best Practices and Common Mistakes
-
Don't do in two migrations what can be done in one. The more migrations we have, the longer test times are, and the more often we may have to collapse them. Unless you have a good reason to do two migrations, just do one.
-
There is almost no reason to have an
UPDATE
statement in a sql snapshot file. Each snapshot is run independently, so you should just adjust theINSERT
statement to reflect the rows you expect to exist, and leave theUPDATE
to the migration. This helps test that the migration runs and does what we expect. One exception is when the migration does not have deterministic output. For example,{ DB: db.DB, Description: "Backfill vetted_at with time.now for nodes that have been vetted already (aka nodes that have been audited 100 times)", Version: 99, Action: migrate.SQL{ `UPDATE nodes SET vetted_at = date_trunc('day', now() at time zone 'utc') at time zone 'utc' WHERE total_audit_count >= 100;`, }, },
The above migration sets a value to the current day. This can be solved in one of two ways:
- Have each of the
INSERT
statements for the matching nodes in the main section usedate_trunc('day', now() at time zone 'utc') at time zone 'utc'
for thevetted_at
column - Have an
UPDATE "nodes" SET vetted_at = 'fixed date' where id = 'expected id'
in the-- NEW DATA --
section (so that it runs in both the snapshot, and after the migration has run) and update the main section'sINSERT
for that node. Future snapshot files do not need to retain theUPDATE
in that case, and theINSERT
statements can just use the fixed date for the future.
See migration 99 for the specifics, where it chose option 2.
- Have each of the
-
Cockroach does schema changes asynchronously with regards to a transaction. This means if you need to add a column and fill it with some data, then these need to have them in separate migrations steps with using
SeparateTx
:{ DB: db.DB, Description: "Add project bandwidth limit", Version: 999, Action: migrate.SQL{ `ALTER TABLE projects ADD COLUMN bandwidth_limit`, `UPDATE projects SET bandwidth_limit = usage_limit`, }, },
Will fail with column "bandwidth_limit" is missing. To make it work, it needs to be written as:
{ DB: db.DB, Description: "add separate bandwidth column", Version: 107, Action: migrate.SQL{ `ALTER TABLE projects ADD COLUMN bandwidth_limit bigint NOT NULL DEFAULT 0;`, }, }, { DB: db.DB, Description: "backfill bandwidth column with previous limits", Version: 108, SeparateTx: true, Action: migrate.SQL{ `UPDATE projects SET bandwidth_limit = usage_limit;`, }, },
-
Removing a DEFAULT value for a column can be tricky. Old values inserted in the snapshot files may not specify every column and relying on those defaults. In the migration that removes the DEFAULT value, you must also change any INSERT statements to include any unspecified columns, setting them to the dropped DEFAULT. This is because the main database will have inserted them while they had the DEFAULT, but the new snapshot will not be inserting while the columns have the DEFAULT.