Schema changes should be lazy

David Strauss | February 18, 2009 | CouchDB, MySQL, Performance, Scalability

We’re in the middle of upgrading Drupal.org, and many of the longest-running upgrades involve schema changes. Unfortunately, MySQL with InnoDB has a very unfriendly method of doing schema changes: it rebuilds the table and blocks all writes until the new table is ready. A more sensible approach would be schema versioning that allows different parts of a table to have different schema versions. This would minimize blocking, allowing schema changes to happen without downtime.

David’s rules for efficient schema changes:

  • Don’t block: Take no immediate action when the admin requests the schema change, except for recording the change that needs to occur.
  • Be flexible: Have a versioning model in place that allows different rows to have different on-disk formats. This versioning may not be per-row, but it should be sufficiently high-resolution that upgrading the smallest unit does not cause troublesome lock delays on a live system.
  • Be lazy: Upgrade the on-disk data format as rows are written. Transform row formats as they are read.
  • Hide the mess: Despite having rows in various formats, the application should read from and write to the tables as if they are all in the latest format once the change occurs. Combined with the “be lazy” rule, this abstraction should be possible with minimal performance impact.
  • Be thorough: Actively upgrade on-disk data in the background during periods of low load. Tables should converge on having all rows in the latest format.
  • Stay correct and atomic: Check that the conversion will be successful on a current table snapshot (you’re using MVCC, right?) and any incoming writes that happen during the check. Before the atomic moment of schema change, the change should fail if any rows won’t convert, including the ones written during the check. After the atomic moment of schema change, writes should fail if they don’t match the new schema.
  • Play loose with indexes: Indexes are optimizations. A database shouldn’t block while adding, removing, or modifying indexes. There’s no need to. Just make sure an index, if used, is complete. Even MyISAM follows this rule. (Granted, the external InnoDB plugin handles this.)

Is there any free, open-source database that observes all of these rules? I know PostgreSQL has transactional DDL, which leads me to believe it implements something similar.

I’m aware there may be objections to delaying the atomic schema changeover until after tables have been checked because it doesn’t perfectly preserve transaction sequencing. But, there’s no sensible alternative. If we assume the schema change will succeed and immediately reject illegal writes, we risk false rejection of the writes if the schema change later fails. That would violate isolation and atomicity for the schema change, a far greater ACID sin. (Give penance to the Oracle?)

While the delay for the schema change in my model may be considerable, administrators would have a choice of taking the application offline (as they do now) or making the application aware of the upcoming schema change (which cannot easily happen now). Such application awareness could involve as little as a check each request to see if the change occurred, choosing alternate code paths for before and after. The next application update removes the old code path. Properly handled, there would be no downtime.

This “lazy schema change” model isn’t just applicable to relational behemoths. It maps well to document databases like CouchDB where records do not generally get cluster-wide updates, even as formats change. Document database systems can be aware of what changes need to occur when reading data in old formats, and they can update documents to the latest formats while reading and writing them.

Commenting on this Blog post is closed.

Comments

I know you guys are PHP guys and are afraid of getting your hands dirty with SQL, but here is the secret to painfree schema migrations:

  1. Create new schema
  2. Create triggers that migrate inserts/updates from old schema into new schema in realtime
  3. Insert all data from old schema into new schema.
  4. You now have both schemas mirrored and populating in realtime.
  5. In one transaction, rename old tables, rename new tables, and drop triggers.

Of course, if you are using sequential ints as your ID’s you are going to have a nightmarish time, but that isn’t schema migration making your life difficult, it was lack of forethought in DB design.

I know you guys are PHP guys and are afraid of getting your hands dirty with SQL

It’s more of a case of MySQL 5.0 not supporting trigger installation without super-user permissions. We build for what our users run. Attributing it to fear of SQL is a canard.

You’re correct that your approach works (I’ve used it enough to know), but it’s largely an application-level implementation of the strategy I outlined, and the argument in my post was that it ought to be in the database layer.

At first blush, your post sounds really complex. A simpler but lesser solution is for us to rewrite the update functions just before release so we do as few schema changes as possible. We are adding and dropping the same column in some cases. So that would really help during major updates.

The trigger solution does have a simplicity appeal for those few sites that can install triggers.

I’m mostly ranting about what databases like MySQL do under the hood. But you’re right, a good take-away from this is pitfalls to avoid when using a database with slow, blocking schema change operations.

Hmmm. This is a problem for updates in contrib modules too. And unlike core, we don’t expect sites to update them in bug chunks at a time (major version upgrade). So the “rewrite and consolidate just before release” is not as viable.

There’s a system called ChronicDB that can do it.

http://www.pubbs.net/pgsql/201001/27008/