caiyue1993 / IceCream

Sync Realm Database with CloudKit
MIT License
1.93k stars 244 forks source link

Versioning records #134

Open kitlangton opened 5 years ago

kitlangton commented 5 years ago

This one is more of a brainstorm. Here's the problem:

The Problem

  1. A user has my flashcard app (for example) installed on an iPad and iPhone.
  2. I release an update, allowing users to add images to cards.
  3. They update their iPhone app and add some images.
  4. They continue to use their iPad app, without updating.
  5. The iPad version successfully synchronizes the updated cards, but of course, does not pull the images out of the record, because it does not yet know about them.
  6. The iPad user makes updates to these cards, which synchronizes them back up to the server, effectively deleting the images.

One Possible Solution

I'm not sure the best way to fix this. It is clearly one of the more complicated problems, with regards to multidevice asynchronous syncing. There's a blog post here https://www.toptal.com/ios/sync-data-across-devices-with-cloudkit, which suggests adding a version to each record.

If our local app encounters a higher version than it knows how to deal with—suggesting that the user has updated the app elsewhere—then we can abort all synchronization until the user updates their version of the app.

@dbmrq @caiyue1993 Have either of you under this problem before? Any thoughts? :)

dbmrq commented 5 years ago

Nice catch! The versioning idea sounds good to me.

ianbradbury-bizzibody commented 5 years ago

I have seen this versioning method before - but for the life of me I can not remember where.

Anyhow. I think adding a version number is extremely sensible and will be a common issue - it's not an edge case. Thinking about myself I know we have 4 iPads in the house and I bet none of them share the same iOS version number.

Thinking about the implementation...... how would you generate or configure the version number? Could that be the Realm migration number?

kitlangton commented 5 years ago

@ianbradbury-bizzibody I'm thinking something along the lines of:

  1. A static let version : Int on each Syncable class. The developer should increment this whenever the record structure changes in a way that would break things given the situation described above.
  2. Sync Engine Changes

    changesOp.recordChangedBlock = { [weak self] record in
            /// The Cloud will return the modified record since the last zoneChangesToken, we need to do local cache here.
            /// Handle the record:
            guard let self = self else { return }
            guard let syncObject = self.syncObjects.first(where: { $0.recordType == record.recordType }) else { return }
    
            /// NEW CODE
            if let version = record["version"], version > syncObject.version {
                // OMG! ABORT! CUT POWER TO THE SYNC ENGINE! WE'RE SINKING!
            }
            /// END
    
            syncObject.add(record: record)
        }
  3. Then, don't sync until the user updates the app?
kitlangton commented 5 years ago

@caiyue1993, by the way, is there a test suite somewhere? If not, I might try to create one. It would be great to be able to test weird edge cases like this.

GWesley commented 5 years ago

Good idea!

johnnyperdomo commented 5 years ago

has anyone started working on this already? 😀

Do we have a timeframe?

kleber-maia commented 4 years ago

Any luck getting a fix / workaround for this issue? This is actually a big problem I'm facing as well.