-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check DB schema version at startup #1272
Conversation
b2e096a
to
1e1a9fa
Compare
Question for reviewers: what do we think about baking the list of supported schema versions into the binary versus making it a config option? I think I favor the former (which is what I've implemented here) because I think whether a given version of Janus can work with a given schema version is a static property of the code, not a configuration decision. And doing it this way means we don't have to wrangle another configuration option in all our deploys. But maybe we'll be glad during some future incident to be able to force Janus to try running on an arbitrary schema version we didn't know about at the time of compiling Janus? |
@@ -3,6 +3,7 @@ | |||
# tests, and is not intended for production use. | |||
database: | |||
url: postgres://postgres@127.0.0.1:5432/postgres | |||
check_schema_version: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: why do we need to set check_schema_version
to false
in various interop binaries? Is the schema populated in a different way there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because the interop binaries use janus_aggregator::binary_utils::janus_main
, just like the "real" Janus components, and that function is responsible for instantiating a Datastore
before any of the specific application's code gets to run. The interop binaries must opt out of schema version checks so that they can get to their own code (the function pointer invoked by janus_main
), which will apply the migration scripts in the container image.
@@ -61,6 +61,10 @@ pub mod test_util; | |||
|
|||
// TODO(#196): retry network-related & other transient failures once we know what they look like | |||
|
|||
/// List of schema versions that this version of Janus can safely run on. If any other schema | |||
/// version is seen, [`Datastore::new`] fails. | |||
const SUPPORTED_SCHEMA_VERSIONS: &[i64] = &[20230405185602, 20230417204528]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's easy to see when we'd add a new version to SUPPORTED_SCHEMA_VERSIONS
(presumably, when we're creating a new DB migration, and we'd be reminded to do so because tests would begin failing until we do so).
But what's the process for removing a version from this list: how do we evaluate when it's correct to remove an item? more importantly, what reminds us to remove versions?
(somewhat separately from this, I wonder if it would be useful to have an env var or other configuration knob that controls which DB schema version is used for ephemeral datastores. this could be a useful tool, but not a complete solution, in evaluating if a given schema version still works with a given revision of the code.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's easy to see when we'd add a new version to
SUPPORTED_SCHEMA_VERSIONS
(presumably, when we're creating a new DB migration, and we'd be reminded to do so because tests would begin failing until we do so).But what's the process for removing a version from this list: how do we evaluate when it's correct to remove an item? more importantly, what reminds us to remove versions?
With things as they stand in this PR, it's up to the person making a change to remove schema versions from the supported list. That does mean that nothing will remind us or force us to drop unsupported schemas. What would be really nice -- and I think this also speaks to your idea about configuring what schema version tests uses -- is for tests that involve the datastore to automatically be run against all the schema versions that are listed in this array. I think what we'd need is a macro for tests that use storage. The macro would generate code to instantiate an ephemeral datastore with the appropriate schema version, and it could also stamp out multiple tests, each one using a different supported version.
Then we'd get real assurance from the tests that Janus functions with all the schema versions we claim are supported, and it'd be completely obvious when an older schema is not supported: you'd see a plain test failure, and then could decide whether to drop that schema version or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro should be a proc-macro in function attribute position, as declarative macros would get in the way of code formatting or IDE features. We could replace tokio::test
macro calls with it, and add a Datastore
function argument to test functions with the new macro. We should probably pass in the SUPPORTED_SCHEMA_VERSIONS
identifier to the attribute macro, rather than try to fight macro hygiene.
In order to properly sequence deployment of new schema versions, I think we'll need a db-next
folder, akin to Boulder. We'd run sqlx migrate add
with db-next
as the source directory. Tests would dynamically choose subsets of this folder, depending on different values in SUPPORTED_SCHEMA_VERSIONS
. Then, we can land a new migration, while testing against it, and release and deploy the addition to SUPPORTED_SCHEMA_VERSIONS
before we put the new migration in our production bucket with the following release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aggregator_core/src/datastore.rs
Outdated
/// The version of the DB schema found in `_sqlx_migrations` is not supported by this version of | ||
/// Janus. | ||
#[error("unsupported schema version {version} / {description}")] | ||
UnsupportedSchemaVersion { version: i64, description: String }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be a separately-discriminated error? It's hard to imagine we'll ever do anything with this error other than report it to a human being.
If we don't think we'll make program-logic decisions based on this error discriminant, let's introduce an Other
value that wraps an anyhow::Error
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case we may as well use Error::DbState
and represent the error as a String
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works, but my point is: I'm not sure we need a separate DbState
error discriminant, either -- we generate a number of DbState
errors, but nothing ever consumes a DbState
error to make a program-logic decision, so DbState
should ideally also be Other
.
I don't think we need to clean up existing error discriminants, but there's no reason to introduce additional unnecessary discriminants, either.
[I'm not married to the idea of using anyhow::Error
as the internal representation--String
would work too. I guess I see the advantage of anyhow::Error
as having a nice API & providing error-wrapping.]
I'm OK baking in a static list of versions, as well as a boolean flag to disable version-checking entirely, as is implemented in this PR. |
Creating a `Datastore` now checks the version of the most recently applied migration script against an array of supported versions compiled into Janus. If the current schema is not supported, `Datastore::new` returns an error. Also adds a configuration option to disable this new feature. This is needed because the Janus interop binaries apply schema migrations at startup, after being handed control by `janus_main`. Resolves #1241
47264b0
to
9c1457a
Compare
Creating a
Datastore
now checks the version of the most recently applied migration script against an array of supported versions compiled into Janus. If the current schema is not supported,Datastore::new
returns an error.Resolves #1241