Managing database schema changes without downtime
samsaffron.com
samsaffron.com
> These defer drops will happen at least 30 minutes after the particular migration referenced ran (in the next migration cycle), giving us peace of mind that the new application code is in place.
Pretty much that's what I've seen before. You do series of deployments + migration scripts.
Usually :
- Migrate with backward-compatible modifications of the schema
- Release application logic that takes advantage of both migrated / to-be-deprecated tables
- Possible extra migration to sync the new / old tables.
- Release application logic that stops using the to-be-deprecated tables.
- Migrate with destructive modifications to remove the old table.
Usually that's a huge complicated process which might be replaced with one migration and a few minutes of downtime. Sadly some companies can't afford it, so they do such changes with weeks planing.
I usually vote for having some planned non-working-hours downtime.
Depending on the nature of the migration and the type of data and the type of service and how coupled everything is, it's impossible. Especially when you are dealing with financial data and transactions that you can't afford to screw up even a single one.
> I usually vote for having some planned non-working-hours downtime.
Yep. No other way around it. You tell your clients weeks/months ahead of the planned downtime. They'll understand and migrate in the middle of the night over a holiday weekend.
But this is in regards to major structural migrations, not simple schema updates or changes.
Erlang/OTP with relups is an example of a system that provides zero downtime online upgrade and rollback with zero downtime.
> depending on... how coupled everything is
If you're just saying here that some systems aren't currently able to migrate in a hitless way, rather than claiming that it would never be possible for some systems, then I fully agree. Hitless migrations require a lot of work, and probably aren't worth the cost for many applications/systems.
Relational databases are awesome, but it's much harder to achieve 100% uptime compared to schemaless, distributed, replicated data stores. You easily can take down your system with one innocuous-looking DDL migration that works great in test but grinds the machine to a halt on a production dataset.
https://www.mysql.com/customers/view/?id=750 http://highscalability.com/youtube-architecture
Additionally, that could cause a lot of headaches for your users if you’re continuously deploying. Then again, I kind of question what you’re doing if you’re constantly running DB migrations for a user application.
Nor is this where this trick ends. It is almost literally how Intel survived the supposed superior instructionsets of RISK back in the CISC days. They turned their implementation contract of CISC into essentially a service ocntract of "the computer will execute this for you." Then, took advantage of the misdirection and were able to get many of the RISK benefits in their computers.
Obviously, this can burn you at an extreme. That fun quote of another layer of abstraction, and all of that. I would be surprised if there is a prescriptive "this is how you do it" that is applicable to everyone. To note, if you only have your company as a client of either service or schema, it might be faster to just make the change in one shot.
Let that sink in. The 5 step process outlined above requires manpower to execute. Don't ignore that.
Haven't heard, please elaborate.
[1] https://medium.com/airbnb-engineering/how-we-partitioned-air...
In many situations taking downtime for it is more expensive for development. When taking downtime, often you need to be really damn sure it works beforehand so you don't take unnecessary down time. Having to roll back and try again later after fixing something (and eating more downtime) can be a bit embarrassing. When you are doing migrations without downtime you can take your time between each step and add additional validations that might be impractical when trying to minimize downtime.
If you don't need to minimize downtime then maybe it doesn't really matter but that situation seems rare from my perspective.
This is absolutely true, and the main reason for this is that the tooling sucks. Actually testing that your migration will result in your intended database state is difficult and thus people simply don't do it.
I've attempted to remedy this situation by writing a diff tool for Postgres, so you can confirm your migrations will result in a matching schema, and autogenerate more correct migration scripts.
I also talked more about this topic at last year's PostgresOpen conference: https://www.youtube.com/watch?v=xr498W8oMRo
1) No deleting or alternating columns needed by any production code. Need something different? Create a new column. After weeks of this column not being needed, we use 2) to delete it - but it isn't a priority.
2) Adding/Modifying columns just use pt-schema-change. I have seen this tool transform billion row tables - it also supports slave master.
We have two migration directories: db/migrate and db/post_migrate. If a migration adds something (e.g. a table or a column) or migrates data that existing code can deal with (e.g. populating a new table) then it goes in db/migrate. If a migration removes or updates something that first requires a code deploy, it goes in db/post_migrate. We combine this with a variety of helpers in various places to allow for zero downtime upgrades (if you're using PostgreSQL). For example, to remove a column we take these steps:
1. Deploy a new version of the code that ignores the column we will drop (this is a matter of adding `ignore_column :column_name` in the right class).
2. Run a post-deployment migration that removes said column.
3. In the next release we can remove the `ignore_column` line (we require that users upgrade at most one minor version for online upgrades).
The migrations in db/post_migrate are executed by default but you can opt-out by setting an environment variable. In case of GitLab.com this results in the following deployment procedure:
1. Deploy code with this variable set (so we don't run the post-deployment migrations)
2. Re-run `rake db:migrate` on a particular host without setting this environment variable.
For big data migrations we use Sidekiq to run them in the background. This removes the need for a deployment procedure taking hours, though it comes with some additional complexity.
More information about this can be found at the following places:
1. https://docs.gitlab.com/ee/update/README.html#upgrading-with...
2. https://docs.gitlab.com/ee/development/what_requires_downtim...
3. https://docs.gitlab.com/ee/development/background_migrations...
1. Run predeploy migration in db/migrate to remove the unique constraint 2. Deploy new version of code that ignores the column 3. Run post-deploy migration that removes column 4. Remove the part of code that ignores the column
Failure to do step 1 would result in multiple rows being created with null values, which would cause errors for all but one (or zero) insert. The edge case, however, is a little more subtle. Between steps 1 and 2, there's a period where the database constraint doesn't exist, but the old code still expects the constraint. Validating uniqueness in the code can mitigate this, but doesn't ensure consistency.
Both solutions suck in some way compared to the downtime required schema changes as far as simplicity is concerned, but they let you maintain consistency throughout the change at least and let you bail out midway if needed
On that note, I'm glad all but a single system I maintain is only used by employees - just schedule a maintenance window after-hours and I'm free to take things down for a couple hours if needed.
You can solve these issues with some creative migration paths both at the app level and at the database level, but those changes aren't always trivial, and may not necessarily fit in the pre-deploy post-deploy migration strategy.
This may also not be an issue for you. You may be willing to accept a brief period of time where an error is unlikely, but technically possible. Personally, most of the time this is acceptable to me, but I always find it important to think about these scenarios in case something does happen.
Unfortunately zero-downtime schema changes are even more complex than suggested here. Although the expand-contract method as described in the post is a good approach to tackling this problem, the mere act of altering a database table that is in active use is a dangerous one. I've already found that some trivial operations such as adding a new column to an existing table can block database clients from reading from that table through full table locks for the duration of the schema operation [2].
In many cases it's safer to create a new table, copy data over from the old table to the new table, and switch clients over. However this introduces a whole new set of problems: keeping data in sync between tables, "fixing" foreign key constraints, etc.
If there are others researching/building tooling for this problem, I'd love to hear from you.
[0] http://github.com/quantumdb/quantumdb
[1] https://speakerdeck.com/michaeldejong/icse-17-zero-downtime-...
[2] http://blog.minicom.nl/blog/2015/04/03/revisiting-profiling-...
If your business is online schema changes, then inventing it yourself makes sense. Otherwise you are likely throwing money away to create an inferior product.
There are tools that "replay" data changes though: - FB's OSC (https://www.facebook.com/notes/mysql-at-facebook/online-sche...) - GH's gh-ost (https://github.com/github/gh-ost)
[1] https://www.percona.com/doc/percona-toolkit/LATEST/pt-online...
[0] http://code.openark.org/forge/openark-kit [1] https://github.com/github/gh-ost
As an aside, Liquibase also effectively has a paid tier - it's just that it's marketed on a different web-site than the OSS version (http://www.datical.com/liquibase/).
I abuse Liquibase to generate sql for me by diffing a reference db to create the changeset.
Will a rollback not be comparing your new schema against your old schema and then apply that changeset? Take into account this might/will drop data.
But looking at flywaydb undo docs[0] I don't see it address anything more then the schema?
[0]: https://flywaydb.org/documentation/command/undo
edit: for those interested I recently moved my shell script solution to python[1].
I was personally not able to use it yet but I really like the idea behind it. It is probably not the ideal choice in general due to its append only nature ever increasing the consumed storage space and due to its high degree of normalization which may or may not have a negative impact on performance when implemented on top of a rational database depending on the nature of your queries.
But if you do not have to deal with excessive amounts of data, if you expect a lot of schema evolution, if you need traceability of all changes for auditing purposes or such, then using anchor modeling might be worth a shoot.
[1] https://en.wikipedia.org/wiki/Anchor_modeling
Also, do you do anything to protect yourself against migrations written at time t_0 and then run much later at time t_n? I've seen a lot of problems there when migrations use application code (e.g. ActiveRecord models), and then that code changes. (My solution is to never call model code from migrations, but there are other ways.) This isn't really specific to your article I guess, but does your approach making managing that harder? Easier?
Does having that details table help at all when you have migrations on a long-lived branch that is merged after other migrations have been added? Or is the solution there just to rebase the branch and test before merging it in?
EDIT: I think this blog post by the Google SRE folks is great reading for people thinking about migrations and deployment:
https://cloudplatform.googleblog.com/2017/03/reliable-releas...
I've never been comfortable with rolling back migrations in production, but their plan changed my mind that it can be done safely. Is your approach compatible with theirs?
Of course sometimes you do still need to which is where strategies like this come in.
Sadly this is more of a pain in Django, since it does not write default values to the database. To be able to safely run the migration first, one must manually rewrite the SQL for the migration. [1]
[1]: http://pankrat.github.io/2015/django-migrations-without-down...
This really comes down to knowing the boundaries of what changes could cause timeouts and working around them as best you can. However, some edge cases could cause downtime.
This exact same compatibility problem exists at other boundaries of your app - in particular, the web/backend interface.