Database versioning best practices
enterprisecraftsmanship.com
enterprisecraftsmanship.com
I'm thrilled he mentioned that once you deploy a migration script, you can't ever change it. I wrote the same thing here [1]. But I see it happen all the time, and it guarantees that your schema gets out of sync with others'. If I've already run that script, Rails doesn't know it needs to run it again.
One thing he left out is that you should write migrations so they work not just on the current version of your code, but on future versions. I see this cause problems on projects again and again. For instance a feature branch with a migration from day 1 with `Permission.create!(name: "foo", value: "bar")`, and then a week later the developer decided to rename the Permission class to Setting. By the time the branch was merged into master, the migration was broken. My solution for this is that a database migration should never depend on the application code, but only use direct SQL.
[1] http://illuminatedcomputing.com/posts/2013/03/rules-for-rail...
Never changing past migration scripts should be a guideline, not necessarily a hard rule. With Rails, I generally prefer to use ActiveRecord in migration scripts (rather than raw SQL) for the purpose of keeping things more readable (again, guideline rather than hard rule). As you mentioned, sometimes changes in application code can break past migrations, hence the need to occasionally alter a past migration (e.g., placing parts of the migration in a conditional). With practice, this actually encourages one to write relatively "future-proof" migrations, anticipating possible future code changes and creating each migration with as few assumptions as possible.
At times, as in your example, a migration may alter data (not just structure), and sometimes old migrations alter data in an obsolete way that isn't really helpful anymore. Occasionally (especially if the migration in question is time-consuming), I'll revise an older migration, but I always keep it around (so the version still exists) and add comments to note what is being removed and why.
One thing that really helps is regularly running all migrations in sequence. My test suite would routinely run them all, and I never use a schema.rb to load a schema directly. Vagrant dev environments are always built using all past migrations. Doing that regularly helps ensure that the current database schema is always a result of repeatable deltas.
One more thing: I avoid migration rollbacks whenever possible. I've seen them cause more chaos than relief in already-stressful situations. In practice, this means (1) making sure the codebase before and after a migration can handle the database state before and after the migration, (2) writing migrations that allow you to easily restore the database to a consistent state if they fail midway through, and (3) whenever possible, fixing a migration and redeploying rather than attempting to roll back changes. A rollback is, after all, a migration itself, prone to its own set of potential bugs and failures.
class DoSomethingWithPermission < ActiveRecord::Migration
class Permission < ActiveRecord::Base; end
# ...
end
It protects you from all sorts of tomfoolery – changes to the class name, its relationships, its validations, etc. – and doesn't require any change in style.I also love the idea of migrations being forward-only, and deployed on their own (ie. without accompanying code changes). If you can pull it off, those constraints give rise to some nice properties: schema changes must work with existing deployed code, code changes must work with existing deployed schema, deploys can be done with zero downtime, botched schema changes can be fixed without rolling back code changes, etc. It is harder to do than the traditional style, requiring schema changes to be made in phases, and often involving triggers and/or views to keep things consistent, so its cost/benefit isn't necessarily a clear win.
http://railsguides.net/change-data-in-migrations-like-a-boss...
Written about 20 rails apps of varying levels of complexity, never been bitten by this approach. YMMV
Instead of storing a version as an integer, I strongly prefer naming each migration and storing the applied migrations in a database table.
Rather than relying on a single integer, I can simply write code that applies all the migrations which haven't yet been applied. This makes it much easier for the database to be modified in multiple concurrent branches without any merge pain.
At work we follow this practice and name our migrations something like "1-add-foo-table". If another dev makes a branch with a migration named "1-add-bar-table" then there's no conflict.
We also store our migrations as a directory rather than a single script. This lets us split things up into multiple files if needed. We also allow for both SQL and Perl (our main language) migration scripts in the directory, which is handy.
Finally, all migrations must be idempotent. In theory no migration should ever be run against the same database twice, but making our migrations idempotent is a little extra insurance.
For the curious, I wrote some Perl modules that help manage a system of migrations like what I just described:
* https://metacpan.org/release/Database-Migrator
* https://metacpan.org/release/Database-Migrator-mysql
* https://metacpan.org/release/Database-Migrator-Pg
The system is designed to be extensible so you can build on top of it, rather than a fully encapsulated tool.
There's also Sqitch (http://sqitch.org/). It's written in Perl but is entirely language-agnostic to the best of my knowledge.
For compiled languages, I strongly recommend embedding the scripts in your jar/assembly/dll. That way you can sign it, and make deployment much much easier.
Our tooling still requires an integer as the first part of the script name - but that's only used to determine overall execution order. The numbers can overlap and there can be gaps which helps with the branching problem.
I also agree that idempotent scripts are a key best practice.
We also persist a hash of the script in the database so that our tools can detect when a script has been modified after it was applied. This has helped catch and prevent a whole class of bugs.
That must be tough. Are you doing it by simply checking a "I've already done this flag"?
And then you make inserting a row named '3-add_customers_table' as part of the transaction for the migration. If this row already exists, the insert will fail, aborting the entire transaction.
Or something like that...
E.g. adding a column: IF NOT EXISTS ... ALTER TABLE ... ADD ...;
In Python, sqlalchemy-migrate does this wrong and Alembic does this right. I worked on a big project that used sqlalchemy-migrate, and this caused us pain. It's difficult for a large project to change database versioning systems, so it's important to pick a good one from the start.
One day recently I pulled that project off the shelf and decided to see if I could get it running for nostalgia's sake:
SGS Server starting...
Expected database version: 57.
Found database version: 0.
Applying DB change script: 1...success.
Applying DB change script: 2...success.
(...)
Database updated.
SGS Server running.
After seeing my old project set itself up automatically with no grief, I felt an enormous amount of pride. I only wish my day job would implement a similar system.
* A database so large that even a minimally empty one cannot be created from scratch in less than 10-15 minutes. This creates problems for CI, integration testing, etc.
* The developers spent a good chunk of the late 90s/2000s writing Oracle PL/SQL code; hundreds of packages and thousands of stored procedures with oodles of business logic.
* We store reports, pdf attachments and other documents etc all in the DB as well.
* Since we put so much stuff on the database, small problems and schema fudges tend to creep in over the years, which makes every customer database a little bit different.
* Oracle licensing can be very unkind and the upper management mandate that we can't use the oracle XE version even in development/testing.
We ended up using a combination of Flyway for schema changes, hand-rolled scripts to apply stored procedures and packages, and we had to roll a database provisioning pool as-a-service for developers, and it's still a massively janky and fragile setup. We really need better tooling for this.
When we ran into this issue we would take period snapshots of the schema / data dump so that it could be recreated rapidly at a certain point. For example, we would create a DB creation and data insert script at version 2.0, then update scripts would be applied starting at 2.1, 2.2 etc.
You are certainly using the wrong technology then; at a previous job I was easily spinning up 3T Oracle databases in a few minutes using COW clones on the storage array attached to VMs. They were only good for a few hundred M of changes, but that was plenty for testing.
Next time you find yourself Jonesing for the latest features and someone offers you dev & test for free, just say no.
You're allowed to have a development database to "create one prototype" with a very loose description of what that is. It also "limits the use ... to one person .. and one server". As for testing - "all programs used in a test environment must be licensed".
http://www.oracle.com/us/corporate/pricing/databaselicensing...
I've developed a hypothetical approach for versioning of stored procedures within the model described here as part of Alembic, known as "replaceable objects": http://alembic.readthedocs.org/en/latest/cookbook.html#repla...
We've found that maintaining a separate "changelog" file for each major release that collects the history of changes for that release and subsequent minor releases is easiest. We also use the Jira ticket # associated with a schema change as the ID of a changeSet, so we can tie changes to feature requests, bug reports, etc.
What I want is a DSL that is a platform-agnostic DSL: terse, typechecked, possibly "compiled" to some IR, with basics of semantic analysis, with support for (or at least awareness of) the quirks of the specific underlying platform.
For schema, the approach recommended by the OP seems sensible - check diff scripts into VC and consider them immutable (see also http://www.depesz.com/2010/08/22/versioning/ for a lightweight Postgres approach).
For stored procs etc, I think you need an approach much more akin to normal code - here you want to be able to leverage your VCS just as you do with normal code - so for these you want to consider them mutable.
One of the best presentations I've seen on this topic is:
http://www.slideshare.net/OleksiiKliukin/pgconf-us-2015-alte...
I would still recommend changing stored procedures through UPDATE scripts and having this change scripts immutable. (Ideally not just immutable but also idempotent and with forward and backward scripts to undo change.)
I'd recommend checking out the linked presentation above - for things like stored procs you have the option of installing multiple versions simultaneously (e.g. under different names or 'Schemas') - obviously you can't do that for the main schema itself. This is why I think a hybrid approach makes more sense.
I prefer to use two integers: major & minor version, semver style. Minor version gets incremented if old code can still use new DB (e.g. when adding a column). Major gets incremented when the change is not backwards-compatible (e.g. when renaming a column).
When you have to rollback a release not having to downgrade the DB can be a major time saver.
eg:
201509102039-add-this-to-table.sql
201510220840-add-that-to-table.sql
Say, you have app v.20150701 running in production. Today you deployed new version and it ran migration script against production DB. Two days later you discovered a critical issue in your new version and are forced to roll back to the old app. Now, your 20150710 app opens the database and notices that it has 20150811-oh-we-did-something-to-the-db.sql migration applied. Can it use the database or should it fail/downgrade the DB? How do you tell if all you have is a timestamp and description?
IMO this is more important than solving branch merges because it helps me when my production system is down and there's pressure to bring it back online ASAP. Merging branches can be done offline in the cozy comfort of my development environment.
PS. Of course, the above scenario might be more or less relevant depending on your development and deployment workflows.
The trouble we encounter is extending outside software to meet our needs, specifically Wordpress. WordPress is a better-than-average CMS but like many CMSes it is very developer-unfriendly. I need to be able to snapshot the database from a single WordPress installation and version it along with an installation of the several WordPress Plug-Ins I am developing for a specific client. This way I can keep a repository reflecting the current state of a single site (with a specific set of plug-ins activated, and specific configurations for each), an exact copy of which I want to eventually deploy in a production environment. I've seen plug-ins for this, but for at least in one specific case (VersionPress) it seems like it is only available through an overly expensive 'Early Access Program' - and it's been in Early Access for many months now, making it seem Vaporware-ish. Anyone know of a better, automated solution, other than dumping the MySQL database every time I make a change?
I help maintain a database versioning/migration tool that my team has been successfully using for years now: https://github.com/nkiraly/DBSteward
The idea is that instead of managing a schema + several migrations, you just store the current schema in XML, then generate a single migration script between any two versions of the database (or just build the whole thing from scratch). The ideal use-case during deployment is to checkout the existing deployed schema into a different directory, then diff against the current and apply the upgrade script.
I've always found most migration solutions to be wanting, and while this approach has its downsides (things like renames can be hairy, need to explicitly build in support for RDBMS features), I do like it a lot more than the standard sum-of-changes approach.
The tool is still very much a work in progress, written in terrible 10-year-old PHP, and desperately in need of a proper rewrite and modernization, but it is definitely stable, safe, and production ready.
Something else that we do is to have the schema/mandatory data in a separate git project. This has the added benefit of having a separate deployable artifact from the application itself.
So far it's working ok, although we've not automated, snapshot/update/refresh off the master yet. The master is planned to be periodically refreshed from ProdCopy.
Would be interested if anyone else is using Delphix and can comment or link to anything on it. I've been meaning to poke around the REST interface but haven't had time yet.
Delphix is terrific for having production-like data in your test environments ("like," because you'd better be masking sensitive information from prod before it gets written in test.
The issue we've had with Delphix is performance. It absolutely pounds on the storage system, and tends to suck up all available bandwidth and CPU that's made available to it. If you resource it properly, it's amazing.
At my workplace we operate a large-ish web service in our own datacenters (airline reservations). We're very happy with the way we version code: we've one single branch where changes arrives all over the clock; every second day this branch is deployed in a "pre-production" platform. Every week we take one of those versions, fork a release branch out of it, and make all necessary adjustments, i.e. a bunch of cherrypicks (changes arrived late but needed quickly) and possibly backouts (we found a bug and have no time for a fix):
a--b--c--d--e <--- what's in pre-prod
\
b*--e' <--- what I want in prod
b* = backout, e' = cherripick
Now, I really really would like to do the same for our DDL (database schema changes). But backouts and cherrypicks just don't work if you're versioning migrations, because you have to deal with what's already in the prod db (conversely, when you deploy software you just trash the old binary alltogether and replace it with the new one):backouts, like b* above: you would hand to your DBA the sequence of patches a,b,c,b* ,e' which makes no sense because you're asking to apply migration b and its reverse b* (you should say "just skip b", but version control doesn't work like that)
cherripicks, like e' above: good luck remembering, in two weeks from now, that you've already applied e' and don't need to apply e.
At the end of the day, we -do- version our changes with forward/backward migrations, but we always end up managing backups and cherrypicks by sticking notes on the wall (or on the team wiki), which smells bad. I admit I never tried any of the popular solutions for database version control (liquibase, flyway) and I don't know how they work. But I feel the ideal solution would be to version the whole schema (not schema diffs), and have tools that at any given moment can compare the schema I have in my VCS and what is actually on the platform, and generate the necessary "migration" on the fly.
EDIT: formatting
Currently we are using the Toad for SQL tool for producing and applying both schema and data diffs between our development environments, but it's a Windows only tool.
1. The simplest. Setup scripts for doing full backups and restores to/from S3. This is helpful for many reasons, but should also help your case. Diffs are an optimisation if your data is large enough, but I think many cases are easily small enough to just push and pull as a whole. Until you're at a significant number of gigs of data, I'd recommend trying this first.
2. Record your data in an append-only format (this doesn't require a different database, just a strict way of doing things). This means you can always get a full history of any bit of your data. I do this for some scraping work (recording lots of noisy/unreliable values, so not losing old versions is important). If your data is stored like this, pushing around diffs should be easy (grab everything with a created_at time after a certain point).
http://platform.qbix.com/guide/database
http://platform.qbix.com/guide/scripts
We use the actual database schema as the primary source of truth for the schema, and we generate the code for the models from it. We have scripts that upgrade the database schema for each plugin and app.
I'd use a tool for this, but then I would need to change how I deploy the apps given that usually, it's best to run the migrations and then update the codebase to prevent exceptions.
In case there are new columns, the best solution would be: execute the migrations (if there are new columns, make them NULL), deploy the code, fill the new columns via SQL or code, if the new columns must be not-NULL, change the table definition and make the columns not-NULL.
Given the complexity I find better to do it by hand.
Doesn't this imply a automatic versioning with the rest of the code?
For example, I use Sequelize and create my tables via model classes, which I write like every other code in my app.
As a side note, I've been using Sequelize for a year now and it can often be pretty annoying. Both the query interface and the migrations are subpar when compared to Rails. For instance:
Model.find({ id: 1}) // or maybe just Model.find(1)!
vs. (the Sequelize syntax) Model.find({ where: { id: 1 } })
Forgetting the "where" above doesn't even raise an error, it just silently fails. Model.find(1)git doesn't support locking files. Then, what is the best way to meet this immutability requirement?