GitHub's online schema migration for MySQL
github.com
github.com
- The ability to control a running migration is crucial. We have pretty predictable load, and we generally run long-running migrations during off-peak hours. If a migration runs longer than we were expecting and might run into peak hours, we can pause the migration and have the migration not impact users.
- hooks make it trivial to integrate with other tools. Right now it reports to slack, but if we used it more, we'd likely hook it up to real monitoring infrastructure.
- there's a lot of default behavior that we want. I'd recommend regular users wrap their best practices in another script and not call gh-ost directly. It's nice to not worry about good defaults for e.g. throttling, or worrying about whether ghost is hooked up to some kind of external monitoring.
How is this typically done? Have a version number in the db? Have the app examine the schema with every transaction? Have the app assume old/new schema optimistically, and if that fails rollback and try with alt schema? Something else?
The first point depends on the mechanism used to keep up with changes to the original table. You can’t fully pause migrations on pt-online-schema-change for example, as it leverages triggers for that part.
From my phone so sorry if too brief, gh-ost’s docs are great and would tell the whole story.
I understand that at GitHub's scale, foreign keys might be more of a hassle than what they are worth, but for a smallish company that values data integrity over scale and uptime, this is not an acceptable choice.
Worse, foreign keys from other tables to the one that is being changed would need to be updated as well, blocking those tables in turn.
This is wrong on a couple levels. First it doesn't copy the whole table: https://dev.mysql.com/doc/refman/5.6/en/innodb-create-index-...
However, it can take a while if MySQL is evaluating the consistency. But you can disable that with `SET FOREIGN_KEY_CHECKS = 0` which turns it into a metadata change (nearly instantaneous).
You still will need to check for violations, but you can do that in a more friendly-to-load manner, and of course will need to deal with any violations manually.
But that strategy is a good middle ground to all-or-nothing FKs.
Edit: Whoops, looks like I was wrong on the table-copy part. Per "Otherwise, only the COPY algorithm is supported." So it does copy the data when `FOREIGN_KEY_CHECKS=1` (the default)
It is true that it is not on our roadmap to implement FK support for gh-ost (see https://github.com/github/gh-ost/issues/331), but if anyone wishes to contribute support for FK we're grateful. We've had more complex contributions coming from the community and we're grateful for those.
It should be feasible to run `gh-ost` to ALTER a table that only has "child"-side constraints. It will be impossible to run `gh-ost` to ALTER a table that has "parent" side constraints.
Hope this clarifies.
It's not about load, it's about locks and contention in the database caused by FK constraint enforcement. Extra read queries will barely be noticeable compared to that.
What locks do you mean?
Also, pardon my further ignorance, but if you're not going to use foreign key constraints, what is the point of using a relational db? Why not just a fast key-value store for each index?
In the specfic case of MySQL, while still horrifying (I agree with you! but it is one of the things you some times have to do at scale), you can create the Foreign Key constraints but then disable their verification and periodically look for violations, as described here: https://www.percona.com/blog/2011/11/18/eventual-consistency...
FK checks also affect performance, of course. Where I work, we disable FKs on our bulk inserts but keep them enabled otherwise and also in tests; but our workload is different from the usual consumer web app, we have multi-million row inserts per user, and no more than 100 users or so per customer, who each get their own tenant DB.
The usual routes for data consistency between tables are batch clean up or in-app validation.
They're also a performance impact on large tables since inserts/deletes must make multiple trips to the tables/indexes. That's a growing operational hassle as tables grow larger.
This isn't sharding. This is vertical partitioning. Sharding is a type of horizontal partitioning.
Reference: https://en.m.wikipedia.org/wiki/Shard_(database_architecture...
At a past job where we had a complex MySQL setup, I set up a slack autoresponse to post "Just say no!" anytime someone mentioned foreign keys. :-)
It was recently open-sourced. Do take a look.
For the reader here: one thing to clarify here is that gh-ost performs schema migration via a data migration between two different tables and it does it via a very efficient way. Ghostferry on the other hand is general purpose data migration library that moves data between different databases, most likely different hosts. Frequently, both schema migration and data migrations are abbreviated as migrations and thus may cause some confusion. The domain of operation of Ghostferry do not necessarily overlap with gh-ost, as it would be very inefficient to use Ghostferry to implement gh-ost.
That said it is a very interesting project on its own as it has a lot of potential use cases. I don't want to hijack the thread any further than I already have so if anyone has any further questions, you can contact information and docs in the repo.
Sounds like this tool is doing something similar but avoiding the use of triggers for flexibility.
We dual wrote to both DBs while we copied the existing data to the new DB, then switched them over. I think we had less than 5 minutes of downtime all up.
most production systems are at least a few hundred gb, and the previously mentioned scaling problems from foreign keys and constraints are pretty nonexistent unless you're starting to push the boundaries of normal ACID DBs.
i.e. a few TB of data with at least thousands of queries per second and lots of writes/updates
Consider a table that you know will only have a few rows, but is referenced in one or more FK columns. If I use a 64bit integer when 32bit (or smaller!) is enough, I’m now using twice as much space for the FK column. If there are millions of rows, plus indexes, that can add up pretty fast.
Flynn.io uses the same kind of strategy; transaction log && async replication (https://flynn.io/docs/databases)
A little sad nanobox.io which one of my app running on has an inferior strategy; temporarily offline at the last sync moment (https://docs.nanobox.io/data-management/data-migrations-scal...)
Ultimately I’d always suggest the tool you are most familiar with if it’s doing a good enough job.
Doing anything meaningfully complex or mission-critical with either will always require care, attention, and understanding of how the database is doing its work. If you know MySQL internals particularly better, it may benefit you to focus your efforts there as modern MySQL is perfectly capable (decent online DDL support, decent native JSON support, etc).
If your team aren't experts with either, I'd invest my effort in learning Postgres.
Main reason we chose Postgres was that JSON fields have been around for a few years. We really like the Mongo feature-set, but aren't very happy with reliability. In every discussion about Mongo, people used to recommend Postgres instead.
To be honest, I don't like it. I'm not sure if it's bad design, or if it's just bad to mix relational databases with JSON, but I'm constantly battling to do things that I would find trivial in SQL.
I guess it really depends on your requirements though. I've found that JSONb is great for storing historical data and results, write-once sort of stuff. I've found it's not so good for storing objects that get modified, especially if a relation can change.
Would you recommend using Postgres for this usecase?
http://bitnine.net/blog-postgresql/postgresql-internals-json...
The GIN index is an inverted index, if you're expecting to query against several keys; alternatively if you have a large keyspace and no need to query outside a small number of properties, you could create individual hash or btree indexes for each one.
Postgres is good for this usecase, but as always, YMMV, consider alternatives/optimizations if your scale or write-volume dictate otherwise (e.g. sharding, Citusdb etc.)
As someone else pointed out, the reason so many similar tools exist for this task on mysql and there's no such tool for postgres is not that postgres isn't as popular.
The reason is that this problem is almost non-existent on postgres as many table alterations do not lock the table.
But you’re right, my comment is mostly pedantic, that Postgres implements alters better so these tools aren’t needed.
1. https://dev.mysql.com/doc/refman/5.7/en/json.html
2. https://dev.mysql.com/doc/refman/5.7/en/create-table-seconda...
(And as modern MySQL releases get better online DDL support, become less and less critical - though still useful for all of those edge cases where native lockless online DDLs can't work yet)
For this reason alone I think we'll continue to use schema-change tools on MySQL even if the server itself becomes better at those.
In the specific case of gh-ost, another good point is that migrations can be completely paused, which in MySQL is not true of online DDL.
Its pretty cool. Check it out as well.
https://www.percona.com/doc/percona-toolkit/LATEST/pt-online...
Also the linked post[3] in the readme hit us very close to home. We originally tried some of our migrations with pt-online-schema-change, which was great in theory but caused a lot of locking contention during the actual process.
I see many people hammering on the lack of foreign key support which is interesting to me. At some point, a database system grows to where relying on MySQL's Online DDL[4] "works" but not really with production load. I feel like a team knows when they need to bring in a tool like this.
The dev in me understands how wonderful FKs are for consistency. But the db-guy in me that has had to deal with locking issues recognizes FKs as a tradeoff, not dogma.
If you shy away from migrating your large or busy tables, or are scheduling frequent maintenance down times in order to migrate these tables, that's when gh-ost (and others) are appropriate to evaluate.
So for us it's not an immediate red flag that gh-ost doesn't support FKs. We just have to work around that limitation[5] because the alternatives are much worse.
For the record, we don't gh-ost all of our migrations. Only the ones that are deemed sufficiently large enough are gh-osted and those heuristics will change from team-to-team.
But as a guy who has had to deal with our database issues AND as a developer who doesn't want to be chained by a database design decision from a decade ago, I love the flexibility gh-ost gives us as we continue to grow.
[1] https://www.getharvest.com/
[2] https://dev.mysql.com/doc/refman/5.6/en/replication-features...
[3] https://dev.mysql.com/doc/refman/5.6/en/replication-features...
[4] https://dev.mysql.com/doc/refman/5.6/en/innodb-create-index-...
[5] https://github.com/github/gh-ost/issues/507#issuecomment-338...
Add a new column with a new index. Copy the old data over and change the code/add a trigger to keep them synchronised. Change the code to use the new column instead. Remove the sync code/trigger. Drop the old column/index. And you're potentially left with a weird name, because the purpose of the column didn't change, just the type.
Or you could use gh-ost and do it in one go, semi-automated without any code changes, and with the same column name.
You need to also realize that database server itself takes a load hit when it kicks off these operations on large tables. I'm not sure on PostgreSQL, but I know sometimes you can not immediately cancel these operations in the middle on MySQL (it also takes time to revert).
With gh-ost you have full control over how fast this process goes and can even pause/resume it if you're experiencing issues.
But let's say it IS instantly cancelled. You're also ignoring the fact that you will take the same hit on any replicas if this statement is successful.
Some production environments don't take kindly to having all replicas lagging as the replication thread is blocking on the DDL change. My team has the luxury of being able to have our entire production environment served by a single master server (though we avoid it as much as possible), but it won't be long before we outgrow that and require at least one up to date replica. Many teams are already in that situation and for that, gh-ost is a godsend.