CloudKit synchronization options #1569
Replies: 6 comments 8 replies
-
A shortcoming in Matt’s code relative to my usage is foreign key handling. I have many tables and relations. What happens when CloudKit sends changed records in an order that violates foreign keys? It doesn’t seem to come up in Matt’s example, which has just a few tables. Harmony handles it by catching FK violations and re-queuing those records. Harmony also notes other options, like temporarily disabling FK checking. Those are reasonable choices for a small database but don’t give me confidence when there’s more complexity. I have multiple “levels” of FKs between tables so requeueing could fail again. Do you just keep requeueing until there are no failures? Aside from sounding like a terrible pattern, Harmony notes that the missing FK dependencies may not even exist in the current batch of records from CloudKit. So requeueing isn’t a robust solution. What I’m experimenting with now is the use of What I don’t know without experimenting is how that preferred order plays out. Is it a guarantee? To begin experimenting, I used this rough SQL WITH RECURSIVE
dependents AS (
SELECT 0 AS level, s.name, NULL AS fk
FROM sqlite_schema s
LEFT JOIN pragma_foreign_key_list(s.name) fk
ON fk."table" != s.name -- ignore self-joins
WHERE s.type = 'table'
GROUP BY s.name
HAVING COUNT(fk.id) = 0 -- no foreign keys
UNION
SELECT d.level + 1 AS level, s.name, fk."table"
FROM sqlite_schema s,
pragma_foreign_key_list(s.name) fk
INNER JOIN dependents d
ON d.name = fk."table"
AND d.name != s.name
WHERE s.type = 'table'
)
SELECT
MAX(level) AS level,
name,
group_concat(fk) AS foreign_keys
FROM dependents
GROUP BY name
ORDER BY level, name to group tables according to their FKs. The “level” 0 tables have no FKs. The level 1 tables only have FKs in level 0. The level 2 tables only have FKs in level < 2. And so on. I end up with five levels in my database. I wrote code that generates It is pretty clean but there are many failure modes. I’ll comment here with my learnings. (Edit: Corrected attributions I’d confused between Harmony and Matt) |
Beta Was this translation helpful? Give feedback.
-
Another thing I’m experimenting with is avoiding all model initializations by sending the raw rows and I’ll share code when I have something more put together. |
Beta Was this translation helpful? Give feedback.
-
I won’t go into much detail @x-creates but I’ll offer an overview of what I ended up with and am glad to answer specific questions about it. One of the solutions linked at the top is best for most people, I think. A core piece for me is a
The other core piece is, of course, a subclass of TransactionObserver. It watches for table changes and uses the syncEngine.state.add(pendingRecordZoneChanges: pendingChanges.map(\.recordZone)) I fear I’ve only stated the obvious, here. As I typed this up and looked at my code, I realized how much it’s infused with the peculiarities of my own models. It would be confusing to dump all that into this discussion. I am happy to share code to answer any specific questions you might have. |
Beta Was this translation helpful? Give feedback.
-
@Jason-Abbott hi Jason , May I ask if you can share how you handled it? Can you solve the problem of GRDB fk? If possible, would you be willing to share your code demo? |
Beta Was this translation helpful? Give feedback.
-
Just stumbled onto this, having missed all the earlier discussion last year. @Jason-Abbott I agree with all your points on the weaknesses of my minimalist approach! And would add that it also doesn't even attempt to acknowledge conflicts let alone set a path towards resolving them. It's just "accept everything that comes in, because YOLO". Though if I did ever take another stab at it (and no doubt I will - CloudKit sync is something that keeps coming back up as a requirement) I'd probably attempt to work in some minimalist conflict acknowledgement and resolution. The foreign keys thing, I think I dealt with the same way as you proposed: try to type order the incoming objects before writing, to minimise foreign key conflicts. Which as you say, depends on it being a small and simple database. And even then there's still potential for edge cases blowing things up. There's a reason that gist never migrated to being a proper repo - it's appealingly simple and sneakily hiding traps. When I get to doing all this again I'll probably take a similar approach to last time, albeit using CKSyncEngine. But would be keen to also learn from improvements / divergences you found worked better in your own code! |
Beta Was this translation helpful? Give feedback.
-
Seems like this might be a new option worth considering? I'm curious to hear your thoughts on it! |
Beta Was this translation helpful? Give feedback.
-
Just some musings of possible use to others …
There are some good options for synchronizing GRDB through CloudKit
FetchableRecord & PersistableRecord
Harmonic
layer rather than GRDB directlyTransactionObserver
to createCKRecord
s so you can write changes however you wantFor me, these all have shortcomings relative to my GRDB usage, which involves a lot of raw SQL, including updates across many rows in one statement and updates through SQLite triggers. That rules out Harmony.
I like the concept of the changeset sync. It seems it would be efficient and uncomplicated for actively used devices. Where it might fall down is adding a new device. Or having cloud sync disabled in your app and then deciding to enable it. It’s not obvious how changesets could efficiently address those common scenarios.
That brings me to Matt’s code. No magic to it, which I appreciate. It seems the right balance for me between high level Harmony and low level changesets.
Continued ...
Beta Was this translation helpful? Give feedback.
All reactions