Sync Conflict Resolution: What Happens to "Orphan" Objects?

Using the Swift SDK, suppose I have these Objects:

final class Foo: Object
{
    @Persisted(primaryKey: true) var _id: UUID
    @Persisted var child: Bar?
}

final class Bar: Object
{
    @Persisted(primaryKey: true) var _id: UUID
    @Persisted var name: String = ""
    @Persisted var parent: LinkingObjects<Foo> = LinkingObjects(fromType: Foo.self, property: "child")
}

Two separate users download the cloud database with a Foo object. Then, each user goes offline. While offline, each user creates a separate Bar Object and sets it as the child of the Foo Object. (The Bar Object is added to the Realm and set as .child in a write transaction.)

Next, both users connect to the Internet and sync begins. I understand that Realm will apply the changes in time-order, so whichever user’s Bar Object was created most recently will prevail. But my question is: what happens to the other, “orphaned” Bar object? Is it automatically deleted from the Realm, or does it still exist in the Realm with no parent? Do I need to worry about cleaning up such orphaned Objects myself?

I have seen this page: https://www.mongodb.com/docs/atlas/app-services/sync/details/conflict-resolution/, which states “Last Value Wins” and that Sync will keep the latest value for a property. That’s straightforward for scalar values. But when the value is an Object subclass, are the “losing” values deleted from the Realm automatically?

Hi @Bryan_Jones!

In this case what you’ve defined here is a “To-One” link between the parent class Foo and the child class Bar (See Docs Here). In the scenario you’ve described, unless both child documents have the same primary key, you will need to cleanup the existing object since you’re telling realm that you’d like to create a link between 2 objects, but both objects should exist in their tables independent of each other. If both child objects do have the same primary key, the one that “loses” conflict resolution will be replaced with the “winner”

It sounds like what you actually want here is an embedded object rather than a link between objects. For embedded objects the relationship between the two objects is one-to-one and you get things like cascading deletes and cleanup of the “orphaned” embedded object Bar in the conflict resolution scenario that you’ve described here.

So in this case you can get the behavior that you’re describing by removing the primary key from Bar and deriving from EmbeddedObject rather than Object

Thanks @Sean_Brandenburg. Unfortunately, embeddedObjects come with restrictions that often make them a non-starter. They can’t hold more than one direct relationship and there are limitations on querying them.

My example here is very simplified, but in real applications with more complex relationships, embeddedObjects just aren’t usable.

It’s one area where Core Data vastly exceeds Realm: I can specify a delete rule for any relationship; I need not use a second-class citizen to get cascade deletes.

Thinking more about this, it’s a shame there isn’t a way for me to tell Realm to cleanup “loser” Objects during a sync.

Under the current design, it’s easy for Objects to “leak” during a sync unless I manually fetch all of them that don’t have a parent relationship and delete them myself.

The mental disconnect is that Realm Sync is handling the “assign new Object to this property” process FOR me. If I handle that process manually, I obviously realize that I need to delete the old Object I’m replacing. But when sync updates this property to merge 15 different users’ changes, everything is opaque—I no longer think about the old Objects that need to be deleted because I didn’t do the property updating. So 14 orphaned Objects are just floating in the database.

There should be a way to manage this for sync. To tell Realm, “Any Objects that don’t ‘win’ the conflict resolution should be deleted.” That’s subtly different than EmbeddedObject, which is deleted when the parent object is deleted.