Module wither::migration [] [src]

Interface for schema migrations.

As your system evolves over time, you may find yourself needing to evolve the data in your databases along with your models. An IntervalMigration is a great place to start & should be defined in your Model implementation.

// snip ...

// Define any migrations which your model needs in this method.
// You should never have to remove these from your source code.
fn migrations() -> Vec<Box<wither::Migration>> {
    return vec![
        Box::new(wither::IntervalMigration{
            name: String::from("remove-oldfield"),
            // NOTE: use a logical time here. A day after your deployment date, or the like.
            threshold: chrono::Utc.ymd(2100, 1, 1).and_hms(1, 0, 0),
            filter: doc!{"oldfield": doc!{"$exists": true}},
            set: None,
            unset: Some(doc!{"oldfield": ""}),
        }),
    ];
}

// snip ...

Remember, MongoDB is not a SQL based system. There is no true database level schema enforcement. IntervalMigrations bridge this gap quite nicely.

Models defined in this system use serde, and as such, it is quite likely that no explicit schema migration is needed for changes to your model. Often times, field defaults can be used and no additional overhead would be required.

With that said, schema migrations in this system:

  • are defined in Rust code. Allowing them to live as child elements of your data models.
  • are executed per model, whenever Model::sync is called — which should be once per system life cycle, early on at boottime. When dealing with an API service, this should occur before the API begins handling traffic.
  • require no downtime to perform.
  • require minimal configuration. The logic you use directly in your model for connecting to the backend is used for the migrations system as well.
  • require no imperative logic. Simply declare your filter, $set & $unset documents, and the rest will be taken care of.

An important question which you should be asking at this point is "Well, how is this going to work at scale?". This is an excellent question, of course. The answer is that it depends on how you write your migrations. Here are a few pointers & a few notes to help you succeed.

  • be sure that the queries used by your migrations are covered. Just add some new indexes to your Model::indexes implementation to be sure. Indexes will always be synced by Model::sync before migrations are executed for this reason.
  • when you are dealing with massive amounts of data, and every document needs to be touched, indexing still matters! Especially when using an IntervalMigration, as you may be under heavy write load, and new documents will potentially be introduced having the old schema after the first service performs the migration. Schema convergence will only take place after all service instances have been updated & have executed their migrations.

Currently, the following migration types are available. If there is a new migration "type" which you find yourself in need of, please open an issue!

Structs

IntervalMigration

A migration type which allows execution until the specifed threshold date. Then will no-op.

Traits

Migration

A trait definition for objects which can be used to manage schema migrations.