Better Database Migrations in Postgres
craigkerstiens.com
craigkerstiens.com
This stems from my work years ago with databases in on-premise products. Customers would modify the database schema, causing migration "up" scripts to fail, and it would be very difficult (and manually intensive) to baseline everything again and get the database back to a working state. Even though modifying the schema was clearly laid out as "not supported", it would still be something we'd have to fix, because ultimately they (and their account reps, etc) still need the product to work.
We used DBGhost, and then had custom pre- and post-deployment scripts to do certain types of changes, such as renaming a column (`if exists old_column { add new column; copy old to new; drop old_column; }`), or adding expensive indexes. One of the best parts is all it stores in source control is all the CREATE scripts for database objects (and the custom pre/post deployment scripts). Pull requests would let you trivially see a new column or index being added.
Compared to the pain of creating and maintaining up/down scripts and the long-term problem where deploying a new instance takes thousands of migration steps (or risking the inital CREATE scripts not matching what happens after all migrations), doing a schema sync was significantly simpler in nearly every respect.
I've been looking for something similar for Postgres and MySQL/MariaDB without any luck, and it really surprises me there's not more interest in doing migrations this way.
It also appears to make the projects (in visual studio) unbelievably slow.
Then the dacpacs get shipped with the application, along with a zipped SqlPackage.exe (and accompanying libraries) to apply them.
It's been a lot of work and we've had to handle a lot of corner cases through pre-deployment scripts and SqlPackage CLI options, but I haven't seen a 'why does this customer have an [Address] column that's the wrong length and set to nullable???' or 'this report is super slow -> index is missing' ticket since.
You most certainly do not have to make them manually, that can be delegated to a build server. You can also use the dacpac to automatically run integration test and it can run some rudimentary upgrade tests vs previous versions of the dacpac.
I'm also a strong proponent of idempotent database updates, and prefer those over classic migrations wherever possible.
Some experience from PostgreSQL (with several years of experience in various applications):
While this approach works pretty well for idempotent changes such as "add column if not exists", it is more tricky when data content is changed by a migration. Although seldom, this alone justified classic migrations, which I always had to use in addition to idempotent upgrades. But I try to keep that part as small as possible.
However, the latter issue might be solved by disciplined usage of names. That is, never reuse or "clean up" column names, table names, index names, view names, function names.
A nice fit into idempotent upgrades is "create or replace function" for database functions. However, there is a caveat that you can't replace it if you change the return type. (Changing the argument types is mostly safe, because then it is a different function for PostgreSQL.) You might be tempted to solve this via "drop function if exists" followed by "create function", but then you need "drop ... cascade", which destroys all views (and perhaps indexes!) that depend on it. Again, the correct solution here is to create a new function with a different name. (And drop old one only at the very end, when everything else is switched to the new one.)
One final note: Always put each migration into a database transaction. And for idempotent updates, put the whole thing into a huge transaction. So when anything goes wrong, nothing happened. You can fix your script and simply try again, without having to cleanup any intermediate mess. This is obviously important on production systems, but also very, very handy during development. For the same reason, while writing a classic migration, always put a "ROLLBACK" at the end. Remove it only when you are fully satisfied with the results.
PostgreSQL is especially strong here, because all DDL actions (alter table, etc.) are transaction safe and can easily be rolled back.
https://www.postgresql.org/docs/current/static/mvcc-intro.ht...
I also have used (with mysql) liquibase with preconditions to script db migrations with conditional logic so that deployment can deal with variations in target db environments, conditionally apply ddl, fail midscript while allowing restart and rollback gracefully.
If you get it right, it's really easy to manage the DB schema structure and commits to your source repository are very legible (because there's one file per entity being modified), much better than interleaving all changes in a single stream IMO.
Particular highlights:
- Their annotated SQL format allows you to create a file per table that contains a first "changeset" to create the table and subsequent changesets for each alteration
- The "runOnChange" option allows idempotent, non-data-destroying parts of your schema such as view and function definitions to be kept as individual files and modified in place (Liquibase uses a hash of the definition to decide whether to rerun it when migrating)
- The "includeAll" tag lets you collect together the scripts in directories ("/views", "/tables", "/functions", etc.) and have them automatically picked up and run from a single "schema.xml" file in the root.
> I've been looking for something similar for Postgres and MySQL/MariaDB without any luck
For MySQL, I wrote an open source tool to do this: http://github.com/skeema/skeema -- it gives you a repo-of-CREATE-statements approach to schema management, like you describe.
Skeema's CLI is inspired by Git (in terms of paradigm for subcommands) and the MySQL client (in terms of option-handling), so if you know how to use those tools, it's a cinch to learn. Basically you `skeema init` to initially populate your CREATE statements (one file per table) from a db instance. You can then change those files locally and run `skeema diff` to view auto-generated DDL, or `skeema push` to actually execute it. Or if you make "out of band" changes directly to the db -- such as renames, or just someone doing something manually for whatever reason -- then you can `skeema pull` to update the files accordingly.
Skeema's configuration supports online schema change tools, service discovery, sharding, various safety options, etc. I also hope to add integration with GitHub API at some point. That can provide a really nice "self service" model for schema management at scale.
I presented on this exact topic last week at PostgresOpen 2017, and have written a schema diff tool for Postgres that supports this approach (https://github.com/djrobstep/migra).
I'd be curious to know if this is what you are getting at, and if it would solve your problem?
skeema (suggested above by evanelias) and square/shift are two management tools for MySQL/MariaDB.
On the lower level, I'm authoring GitHub's gh-ost, an online schema migration tool, and we have Ruby on Rails + scripts automation around that. Described in https://www.percona.com/live/17/sessions/automating-schema-c... .
I suspect no matter what solution you'd use, you will always need to break migrations into parts that are not fully automated. e.g. when dropping a column you'd first deploy code that ignores said column, only then run the migration to actually drop the column.
You define you're schema and it figures out what tonsonbased onbyour current schema.
[1] http://www.databasesoup.com/2015/08/lock-polling-script-for-...
Might I add a little warning from experience.
The "gradually" part is important to get right, since in postgres updates actually create new tuples and leave dead tuples behind, if you update too much at once two things might happen: you increase disk usage fast (worth considering how much room you have to work and adjust before if needed) and increase the amount of dead tuples fast.
I have experienced that having too much dead tuples on a table might impact the query planner and it may stop using some index completely before autovacuum does its job. Depending on the circumstance this may severely impact performance if an important query suddenly starts Seq Scanning a huge table.
This blog post has great practical details to consider before running such updates: https://www.citusdata.com/blog/2016/11/04/autovacuum-not-the... In particular at the time we had to adjust the "autovacuum_vacuum_scale_factor" for the table in order for the autovacuum process to kick in much sooner. Also there is a valuable query there to inspect how much dead tuples you have.
(I'm not able to open your link for some reason :-\)
add_index(table, fields_to_index, algorithm: :concurrently)I don't like that you have to toggle your "Active database" rather than just opening a new window with a new connection to a different database. If that annoys me much more then I plan to try SquirrelSQL and then retry Postage.
I never understood the hate that pgAdmin3 received, I liked it a lot. V4 though is a mess.
I use it to organize my scripts which I need regularly. I can also put them on a network/shared drive so that others can access it.
I don't know if pgAdmin allows it, but in DBeaver, I can switch between Grid and Text view to copy paste data into email in a nicely tabulated format, besides of course, being able to export a result set out into CSV etc.
Will give dbeaver a try.
A decent alternative I use is SQL workbench/j (not related to MySQL) which leverages jdbc to connect to any database and has a long feature list. In my experience, it is on-par with dbeaver.
> skrause: BigSQL maintains an LTS release that stays compatible with the most recent Postgres versions: https://www.openscg.com/bigsql/pgadmin3/
The discussion mentions a number of additional alternatives. Also: Postage itself is looking for a new maintainer.
1. Regular migrations (located in db/migrate)
2. Post-deployment migrations (located in db/post_migrate)
Post-deployment migrations are mostly used for removing things (e.g. columns) and correcting data (e.g. bad data caused by a bug for which we first need to deploy the code fix). The code for this is also super simple:
# Just dump this in config/initializers
unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
path = Rails.root.join('db', 'post_migrate').to_s
Rails.application.config.paths['db/migrate'] << path
ActiveRecord::Migrator.migrations_paths << path
end
By default Rails will include both directories so running `rake db:migrate` will result in all migrations being executed as usual. By setting an environment flag you can opt-out of the post-deployment migrations. This allows us to deploy GitLab.com essentially as follows:1. Deploy code to a deployment host (not used for requests and such)
2. Run migrations excluding the post-deployment ones
3. Deploy code everywhere
4. Re-run migrations, this time including the post-deployment migrations
We don't use anything like strong migrations, instead we just documented what requires downtime or not, how to work around that (and what methods to use), etc. Some more info on this can be found here:
* https://docs.gitlab.com/ee/development/what_requires_downtim...
* https://docs.gitlab.com/ee/update/README.html#upgrading-with...
* https://docs.gitlab.com/ee/development/post_deployment_migra...
https://www.braintreepayments.com/blog/safe-operations-for-h...
Has anyone been collating large-scale SQL best practices into a book or similar? I've found high-level overviews such as this https://github.com/donnemartin/system-design-primer, but lacks specifics to scaling SQL...I've only seen disparate blog posts like the one above.
I find that, for tables, if I write anonymous "DO" blocks in a single script to manage both the initial creation of the table and any later deltas, I get a very satisfactory version control history. This is a bit different than the recommended approach (if I'm not mistaken).
I just wish it wasn't written in perl with 10,000 cpan dependencies which may or may not build in a given circumstance.
DbUp, upon which Piggy is based, is a popular option in the .NET space: https://dbup.github.io/.
(Neither attempt the protections that I think strong_migrations is offering - TIL!)
One strong point of Flyway, besides being "just SQL", is the repeatable migrations, you can update your stored procedures, views and table documentation in the same version controlled source files without making a new copy in a new migration file for each change.
There is nothing even close enough in the nodejs world and we wanted to manage our dB properly.
Incidentally, three other nodejs startups that I told this to, also started using Alembic for their migrations.
We're happy users of alembic. In general we use it to do the initial grunt work and then manually edit the migrations / split into phases ourselves. For some stuff that Alembic doesn't handle very well (like check constraints) we've extended Alembic to handle those automatically in the way we like.
Does alembic have anything similar? this would be useful for Python projects
* create a new schema in a table.new
* install update triggers on table.old to write the same content into table.new
* backfill table.new from table.old
* swap table.new and table.old
I don't work for GitHub but we had the same problems using pt-online-schema-change independently of their issues (lots of locking contention affecting the app negatively). We're finally moving to gh-ost for our large/risky migrations and so far it's amazing.
So what we basically have is a script that runs migration SQL files in order within a transaction and then uses COPY to seed the database from a folder of CSV files. Triggered by a shell script which itself can be run via yarn/npm.
It is glorious. I write a nice DSL called SQL for my database and there are never any surprises. If I want to write brittle migrations, idempotent migrations, transactional migrations, or data migrations, those are all on me. Anything more than this leads to needless debugging the tools and has never resulted in more clear or concise code.