-
Notifications
You must be signed in to change notification settings - Fork 40
[MongoDB Storage] Storage version #487
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
base: main
Are you sure you want to change the base?
Changes from all commits
86b8c00
7869fa5
8ba91e3
9ee46f2
f292250
df9e2aa
4cceebd
9f22f56
d6f27ab
f771dcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { migrations } from '@powersync/service-core'; | ||
| import * as storage from '../../../storage/storage-index.js'; | ||
| import { MongoStorageConfig } from '../../../types/types.js'; | ||
|
|
||
| export const up: migrations.PowerSyncMigrationFunction = async (context) => { | ||
| const { | ||
| service_context: { configuration } | ||
| } = context; | ||
| const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig); | ||
|
|
||
| try { | ||
| await db.sync_rules.updateMany( | ||
| { storage_version: { $exists: false } }, | ||
| { $set: { storage_version: storage.LEGACY_STORAGE_VERSION } } | ||
| ); | ||
| } finally { | ||
| await db.client.close(); | ||
| } | ||
| }; | ||
|
|
||
| export const down: migrations.PowerSyncMigrationFunction = async (context) => { | ||
| const { | ||
| service_context: { configuration } | ||
| } = context; | ||
|
|
||
| const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig); | ||
|
|
||
| try { | ||
| await db.sync_rules.updateMany( | ||
| { storage_version: storage.LEGACY_STORAGE_VERSION }, | ||
| { $unset: { storage_version: 1 } } | ||
| ); | ||
| } finally { | ||
| await db.client.close(); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,40 @@ | ||
| import { SyncConfigWithErrors, HydratedSyncRules, versionedHydrationState } from '@powersync/service-sync-rules'; | ||
| import { | ||
| CompatibilityOption, | ||
| HydratedSyncRules, | ||
| SyncConfigWithErrors, | ||
| versionedHydrationState | ||
| } from '@powersync/service-sync-rules'; | ||
|
|
||
| import { storage } from '@powersync/service-core'; | ||
| import { DEFAULT_HYDRATION_STATE, HydrationState } from '@powersync/service-sync-rules/src/HydrationState.js'; | ||
| import { StorageConfig } from './models.js'; | ||
|
|
||
| export class MongoPersistedSyncRules implements storage.PersistedSyncRules { | ||
| public readonly slot_name: string; | ||
| public readonly hydrationState: HydrationState; | ||
|
|
||
| constructor( | ||
| public readonly id: number, | ||
| public readonly sync_rules: SyncConfigWithErrors, | ||
| public readonly checkpoint_lsn: string | null, | ||
| slot_name: string | null | ||
| slot_name: string | null, | ||
| public readonly storageConfig: StorageConfig | ||
| ) { | ||
| this.slot_name = slot_name ?? `powersync_${id}`; | ||
|
|
||
| if ( | ||
| storageConfig.versionedBuckets || | ||
| this.sync_rules.config.compatibility.isEnabled(CompatibilityOption.versionedBucketIds) | ||
| ) { | ||
| // For new sync config versions (using the new storage version), we always enable versioned bucket names. | ||
| // For older versions, this depends on the compatibility option. | ||
| this.hydrationState = versionedHydrationState(this.id); | ||
| } else { | ||
| this.hydrationState = DEFAULT_HYDRATION_STATE; | ||
| } | ||
| } | ||
|
|
||
| hydratedSyncRules(): HydratedSyncRules { | ||
| return this.sync_rules.config.hydrate({ hydrationState: versionedHydrationState(this.id) }); | ||
| return this.sync_rules.config.hydrate({ hydrationState: this.hydrationState }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -204,8 +204,41 @@ export interface SyncRuleDocument { | |
| id: string; | ||
| expires_at: Date; | ||
| } | null; | ||
|
|
||
| storage_version?: number; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 interface StorageConfig { | ||
| /** | ||
| * When true, bucket_data.checksum is guaranteed to be persisted as a Long. | ||
| * | ||
| * When false, it could also have been persisted as an Int32 or Double, in which case it must be converted to | ||
| * a Long before summing. | ||
| */ | ||
| longChecksums: boolean; | ||
|
|
||
| /** | ||
| * 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 LEGACY_STORAGE_VERSION = 1; | ||
| export const CURRENT_STORAGE_VERSION = 2; | ||
|
|
||
| export const STORAGE_VERSION_CONFIG: Record<number, StorageConfig | undefined> = { | ||
| 1: { | ||
| longChecksums: false, | ||
| versionedBuckets: false | ||
| }, | ||
| 2: { | ||
| longChecksums: true, | ||
| versionedBuckets: false | ||
| } | ||
| }; | ||
|
|
||
| export interface CheckpointEventDocument { | ||
| _id: bson.ObjectId; | ||
| } | ||
|
|
||
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.