Conversation
|
|
@simolus3 @stevensJourney It's not urgent to get this out, but I'd like to get your input on this approach for managing storage versions, in preparation for changes we'd need for incremental reprocessing. |
stevensJourney
left a comment
There was a problem hiding this comment.
Overall I think this approach makes sense and looks good to me.
| expires_at: Date; | ||
| } | null; | ||
|
|
||
| storage_version?: number; |
There was a problem hiding this comment.
I initially thought this might not need to be optional, due to it being set in the migrations. But I assume we can't really guarantee that all migrations have actually been executed in some circumstances - like self-hosted environments, or is there another reason for declaring it as optional?
There was a problem hiding this comment.
Initially this was from before having a migration. Now, it's mostly a case of we're not guaranteed that the migration has run, and having the fallback is simple to implement.
| export const LEGACY_STORAGE_VERSION = 1; | ||
| export const CURRENT_STORAGE_VERSION = 2; | ||
|
|
||
| export const STORAGE_VERSIONS: Record<number, StorageConfig | undefined> = { |
There was a problem hiding this comment.
nit: It looks like this is mapping the storage version to the corresponding StorageConfig. Maybe we could call this STORAGE_VERSION_CONFIGS.
| const storageConfig = STORAGE_VERSIONS[this.storage_version]; | ||
| if (storageConfig == null) { | ||
| throw new ServiceError( | ||
| ErrorCode.PSYNC_S1403, |
There was a problem hiding this comment.
What would the flow be if a user did downgrade the service and this has been reached?
Would we always recommend performing a sync rules change when downgrading the service? Otherwise, it seems like a downgrade would essentially take-down the instance for both replication and api services (if I understand this correctly)?
There was a problem hiding this comment.
Correct - a downgrade would take down the instance. I feel that's better than attempting to continue, which could result in obscure errors or even silent consistency issues.
I added a section in the PR description on the available downgrade options.
14f6741 to
d6f27ab
Compare
simolus3
left a comment
There was a problem hiding this comment.
My main question is whether we perhaps want to define "common" storage versions in a shared package to reduce duplication once we implement this for Postgres.
Some aspects of storage versions (like storing checksums as long values in this case) are specific to the bucket storage implementation, but others (versioned bucket ids) are essentially a "version of the sync service used in this deployment" field that could be shared.
Let's say we had something like this in service-core:
/**
* Changes that can be enabled when deploying new sync configurations, but must be preserved for existing deployments.
*/
export interface CommonStorageConfig {
/**
* Whether versioned bucket names are automatically enabled.
*
* If this is false, bucket names may still be versioned depending on the sync config.
*/
versionedBuckets: boolean;
}
export const COMMON_STORAGE_CONFIG_LEGACY: CommonStorageConfig = Object.freeze({versionedBuckets: false});
export const COMMON_STORAGE_CONFIG_V1: CommonStorageConfig = Object.freeze({versionedBuckets: true});In module-mongodb-storage, each StorageConfig would then have a common: CommonStorageConfig field pointing to the corresponding constant defined in service-core.
This would allow us to make CommonStorageConfig a field on the PersistedSyncRulesContent interface (Postgres would unconditionally use COMMON_STORAGE_CONFIG_LEGACY for now). I'm mainly suggesting this in anticipation of #498, but since we will end up having something like the versionedBuckets option across all storage implementations anyway, it feels right to put that into a shared package.
| * Hydrate the sync rule definitions with persisted state into runnable sync rules. | ||
| * | ||
| * @param params.hydrationState Transforms bucket ids based on persisted state. May omit for tests. | ||
| * @param params.hydrationState Transforms bucket ids based on persisted state. |
There was a problem hiding this comment.
Maybe also mention that the compatibility option is not checked in this method.
| import { DateTimeValue, SqlSyncRules, TimeValuePrecision, toSyncRulesValue } from '../../src/index.js'; | ||
|
|
||
| import { versionedHydrationState } from '../../src/HydrationState.js'; | ||
| import { DEFAULT_HYDRATION_STATE, versionedHydrationState } from '../../src/HydrationState.js'; |
There was a problem hiding this comment.
We should remove versioned_bucket_ids: false from the streams can disable new format test and remove the can use versioned bucket ids test, these tests are no longer relevant now that the compatibility option is ignored.
Maybe we could replace them with tests asserting that the option is marked as enabled via expect(rules.compatibility.isEnabled(CompatibilityOption.versionedBucketIds)) depending on YAML since we're using that property when loading sync rules from storage.
I'm starting to think a common storage version sequence can help. We'll likely expose the storage version to the developer, since they need to be aware of that for certain upgrades or downgrades. And it would make documentation a lot simpler if we can refer to "storage version 5" instead of "storage version 5 for mongodb, 3 for postgres". The same could apply to the cloud dashboard, where the developer should not have to care whether it's a mongodb or postgres storage version that they're seeing/specifying. I don't think it matters than much that certain storage version features are only applicable on one of the implementations. We can always bump the storage version for both, even if it only really affects one of them. One implication in practice is we do something like the "versioned bucket names" change only for MongoDB here, we'd either have Postgres not support that storage version yet, or we need to be more pro-active in adding support for Postgres as well. I'll see if I can update the PR to use common storage versions, and use it for Postgres as well. |
This introduces a "storage_version" config, initially for MongoDB storage. If this works well, we can extend the same concept to Postgres storage.
The basic idea is to move away from migrations that need to be run upfront, to a storage version that is specific to a sync rules version. So when upgrading the service version, there is no need to run large migrations on existing data. Instead, when you deploy a new sync rules version, the new storage version is used for that.
Pros:
Cons:
This initial implementation uses the storage version for two features:
These are fairly minor storage changes to start with. However, the plan is to use this for incremental reprocessing (#468), which may introduce much larger storage changes.
Collections
There are no actual changes to collections here, but generally going forward, we'd have "static" collections (no change based on version), and "versioned" collections - where we'd use different collections based on the storage version, if affected by the storage version. This is not final - we can always introduce more changes based on storage_version, but this generally explains the expected changes.
Still TBD how we manage changes to static collections. For now we can keep on using migrations, but we may eventually replace that mechanism with something that can handle downgrades better.
Static collections:
Versioned collections:
Downgrades
A downgrade to a lower storage version would always need a sync rule reprocessing. In theory, this can happen before or after downgrading the service version (to one that doesn't support the latest storage version). So you generally have these options:
1. Don't upgrade storage version
Upgrade service version, but don't upgrade storage version. Can downgrade without issues.
The caveat is that storage version upgrade is currently automatic when you update the sync rules - can't opt out of that yet. We can add support for opting out in the future.
2. Downgrade storage before downgrading service.
3. Downgrade storage after downgrading service
4. Downgrading to lower migration version
Just for reference
Currently this blocks the migration process. Theoretically it's possible to down-migrate first using the newer service version, but I'm not sure that process actually works. Not supported at all on the cloud service at the moment - have to stop and start the instance, which re-creates the storage from scratch and causes downtime.
Depending on the actual migrations, there may be consistency issues in the process.
General comments
There is remaining work to implement to make downgrading possible without downtime. We'd have to think how exactly we expose this - for example config options, or an API. Either approach would likely require documenting storage version compatibility.
However, the current state with storage is no worse than we have with the migration system.