One year of automatic DB migrations from Git
abe-winter.github.io
abe-winter.github.io
I also found that the use-case which most of these migration tools optimize for (being somewhat independent of specific SQL dialects) rarely works in practice, except maybe if you stick to the smallest common denominator of all SQL dialects. And that again is wasteful IMO as not using a lot of the functionality that's present e.g. in Postgres can make your life a lot harder.
A few years back I switched to plain SQL based migrations (using a lightweight library called DbUp to execute them) - I couldn't be happier with it!
What better tool to express database changes with than SQL? I also like the transparency it brings you - the files are right there for you to read. And I'm a stickler for formatting and convention, so it's great being able to write SQL files that include comments and use my preferred formatting.
This has both the pro and con of your app being quite tightly coupled with the database.
I mean, this is the case any time your app talks directly to a database
Which is in most cases desirable, but for example if you want to use database features that SQLAlchemy/Alembic don't (yet) support it can get a bit messy and/or require some manual plumbing to keep getting the migrations you want.
Personally, I would start writing the abstraction as soon as I plan to have more than two apps.
I settled on flyway after migrating to postgres a few years ago and haven't looked back. The "this is great" moment came after we automated snapshot and restore of per-dev environments in the release pipelines - this allowed us to roll out the last released version of the product with either a clean database (fast) or a snapshot (slow) to our own individual database in only a few minutes. As a result we could test flyway migrations without the grief caused by breaking (or reverting) a shared 'dev' database.
Since moving on from that team/company, and run head first into dacpacs again, I've found it increasingly difficult to sell my past approach. Few people are genuine data experts, few still DBAs and almost no one is responsible for cross cutting concerns etc. I can't help but think empowering devs to automate migrations, even if it's a hackity SQL parser, is part of the solution and I'd love to see this take off.
(It makes use of skeema, which allows you to track your schema in a declarative way vs. ALTER TABLE statements.)
automig diffs the SQL files directly without looking at a live DB
pros & cons to both approaches
In Skeema, you have directories of *.sql files, where each directory defines the desired state of a single "logical schema". A config file in each dir allows you to map that logical schema to one or more live databases, possibly in different environments (prod, stage, etc) and/or possibly different shards in the same environment.
Most commonly, users will want to "push" the filesystem state to the databases, e.g. generate and run DDL that brings the database up to the desired state expressed by the filesystem. But users can also "pull" from a live database, which does the reverse: modifies the filesystem to look like the current state of a live database, essentially doing a schema dump. So the source of truth depends on the direction of the operation.
Skeema also uses the live database as a SQL parser. Instead of needing to parse every type of CREATE statement accurately across many different versions/dialects of MySQL, Percona Server, MariaDB, (and maybe someday Aurora etc), Skeema runs the statements in a temporary location and then introspects the result from information_schema. This avoids an entire possible class of bugs around parser inaccuracies.
That all said, I do wholeheartedly agree that generating DDL from git alone is both useful and really cool! I actually built a "database CI" product around this concept as a GitHub app last June: https://www.skeema.io/ci/
and agree re parsing -- turned out to be a huge pain. I think someone bundled the postgres parser for python; I wish this were true for every dialect but even so, there are versions to consider.
If you care about testability - the best way to test your migrations is by testing them directly against the schema they'll actually be applied to.
What if your tool is not the only party making schema changes to the database? What if there was a bug in a previous migration and the database is in a different state than you're assuming? The only way to check for problems like this is to check your actual production state.
I think the solution to this is to not have a schema.sql. Build your development/testing database by running the same migrations you have/will run in production.
There's still the issue of ORM parity, which admittedly is a bit awkward. I try to limit my ORM definitions to the minimal necessary for the ORM to run. If I want a true picture of what the table looks like, I look to the database directly.
Why not:
- Have an ORM definition/schema.sql explicitly defined
- Continually test that production_schema+pending_changes=goal_schema
This way, no chain is required: The only scripts you need to keep hanging around are any pending changes yet to be applied. And you can mostly autogenerate any changes required, so the need to cobble together scripts manually is much reduced.
In practice it's hard to avoid having incremental migrations, as you need a way to evolve your production schema. Your approach would be awesome of course but I don't know any tool that would be able to robustly come up with a migration by comparing a database schema and a schema specification. Often it's not even possible to derive the necessary changes using such an approach: If you e.g. change the name of a column in your schema.sql, how can the tool know if you want to remove the original column and add a new one instead or simply rename and possibly type-cast it? If you have multiple changes like that it only gets worse. So I'm not very optimistic about that approach.
I think Rails has a combination - migration files + schema file that serves a source of truth https://edgeguides.rubyonrails.org/active_record_migrations..... It solves a problem of a cold start, but I'm not sure it can generate a migration from the changes to it.
in practice the rollback use case isn't tested super well and, for data integrity reasons, it doesn't drop tables -- you'll run into trouble if you try to rollback across an added table
Anyway though, your broader point is absolutely correct, re: data migrations typically involve complex custom/company-specific tooling at scale. And it's also difficult to map data migrations to a declarative tool in a way that feels intuitive.
I haven't written schema migrations by hand on a regular basis in like 12+ years, the very start of my career, so they were probably not even new back then. Am I just living in a bubble where those tools, while certainly not perfect, are heavily relied upon, whereas others prefer to do it (semi-)manually to have full control? How is the state of these tools in different stacks / environments?
Granted, I'm not a software developer by trade; just a sysadmin who occasionally uses programming to solve problems. I imagine if I had to deal with actual software development I'd end up learning one of those tools eventually.
This.
Above should be the goal of every database backed application framework. A really well balanced post (author is super modest).
>> Dialect support is no picnic.
Are you doing SQL statement parsing here for diff of pg ? This can lead to lot of work if you want to add other databases.
@awinter-py : have a look at our approach to schema migrations - we hand a GUI DB application to devs and it generates schema migrations as you change database. The goal is still the same - backend devs should never write schema migrations.
Even when state-aware migrations are needed they could be better, e.g. 'on creating this column, value = other column * 2' rather than in an ordered sequence.
I find writing the alter table commands fairly trivial compared to the data migration that comes along with it.
https://www.postgresql.org/docs/current/information-schema.h...
Postgres also has its own schema that is basically manipulated directly by the C runtime: