Realm Connection Status

Our project needs to know if it’s got a connection to Realm (Flexible Sync) or not. There are some tasks that should not happen if the app is offline (deleting a certain object for example).

We are using KVO to determine connection status - however, when the app goes from connected to disconnected it can sometimes take up a minute or longer for the event to fire (the opposite happens almost instantly)

Any suggestions on how to know if the app has gone offline/disconnected faster?

Here’s the code we’re using for tracking connection status. self.observer is a NSKeyValueObservation

func showConnectionStatus(realm: Realm) {
    let session = realm.syncSession

    // Observe connectionState for changes using KVO
    self.observer = session!.observe(\.connectionState, options: [.initial]) { (syncSession, change) in
        switch syncSession.connectionState {
        case .connecting:
            print("    -> Connecting...")
        case .connected:
            print("    -> Connected")
        case .disconnected:
            print("    -> Disconnected")]
        default:
            break
        }
    }
}

Would you mind clarifying what you mean by deleting an object while it’s offline? Is that due to a local aggregation or index on the client?

Would you mind giving some more detail in regards to this use case? Is this an IoT device or service per chance? There’s other ways to do this would be to logically construct a dispatch queue and have that queue just sever the connection as necessary or open the Realm connection.

That’s what I’ve seen done and have actually done on an iOS and Android App for a devices remote control system.

@Brock Thanks for taking a look.

This use case is a multi-user contacts app. Contact details can be added, and while they are being added the contact is considered “in-use”. Details would be “called contact” or “texted contact”. Contacts can also be deleted if they are not 'in-use". In the UI, a user can see a list of contacts and then select one to get details. From there, additional details can be added.

The objective is preventing a contact from being deleted while its in the process of being added to.

What we do in code, when the user selects a contact, we flag it as “in-use” which syncs to the server. That makes it so other users can see it’s in use, and prevents other users from deleting it while it’s “in-use”

Users that are offline cannot add to or delete a contact. That would be a syncing nightmare if an offline user deleted a bunch of contacts that were actually in use by other users that were online. Delete’s always win so when that user went back online, poof - there goes the contacts everyone else were using.

When a user is offline, the “in-use” status of a contact is unknown. So in code we prevent the user from deleting anything.

The above code works perfectly if we get a syncSession and suspend it in code like this

if let syncSession = realm.syncSession {
    syncSession.suspend()
} else {
    print("  no syncSession")
}

…it fires immediately. But that’s not the use case. This use case is if the device disconnects from the server due to dropped internet.

For example; if I am running that code on the hard-wired iMac I am sitting in front of and pull out the ethernet jack (simulating a dropped internet connection). The code does not fire immediately - it can take over a minute for Realm to know it’s offline. We’re looking for something to indicate offline status faster - say within a few seconds. Many other databases have that ability.

Edit:

In testing, when an internet connection drops it can take a minute+ for Realm to realize it’s offline. However, when it’s offline and then goes back online, it recognizes that with a couple of seconds.

Hey Jay,

It sounds like you are using “in-use” as a lock, and you want to gate behavior on this lock. I think you want to be very careful doing this, because this behavior is inherently racy. For instance, consider the case where client A and client B are both online, and client A deletes a contact at the same time client B updates it.
In this scenario, setting the “in-use” flag will race with the delete, and it would be likely that the delete would go through anyway.

Adding a dependency around online status will only exacerbate this problem, even if you could detect online status with zero latency (which you can’t).

Instead, I’d recommend the following:

  • when a client attempts to delete a contact, set contact.deleted = now
  • when a client attempts to update a contact, set contact.lastUpdated = now in addition to the update.
  • If lastDeleted > lastUpdated, don’t render the contact and consider it “soft deleted”. Otherwise, the contact is not deleted.
  • If you need to “hard delete” contacts to, eg, free up space, you can create a scheduled trigger to periodically delete contacts where lastDeleted > lastUpdated and lastDeleted was sufficiently long ago (perhaps longer than maxOfflineTime )

@Sudarshan_Muralidhar

Thanks for the response and insight. The “in-use” property is definitely racey and I want to avoid that.

One solution is for the app to know whether it’s online/connected or not. If it’s not connected, disallow objects to be deleted, per my question.

We have also considered a soft-delete mechanism as you suggest but are having a hard time making it work for offline situations. Let me provide an example.

Suppose we have an app that stores scientific articles, they can be added, edited and removed. One such article is called “A Solution for Faster Than Light Travel” but within the body there are no solutions (yet).

Two users are online and are looking at the article in their UI. One user drops connection. Meanwhile the other user adds an equation to the article of how to achieve faster than light travel (article.lastUpdated = now)

Meanwhile the offline user says “Faster than light is not possible” and soft-deletes it (article.deleted = now).

Then the offline user reconnects. Realm sync’s and deleted > lastUpdated so the article disappears from view and on the next Cron cycle, it’s removed entirely.

Obviously that would be bad.

If Realm knows it’s not connected, then the app could prevent the user from soft-deleting in the first place, preventing the issue entirely.

The ultimate goal is to give user(s) the flexibility to add, edit objects and remove old or unused objects but at the same time keeping objects that are being used intact. “used” would be objects that are being viewed or in the process of being edited by another user, hence the “in-use” reference.

Understood. Unfortunately, any “is connected” approach would be racy as well - online status can change at any time (and indeed, can’t always be determined reliably in a circumstance with spotty connection).

Further, the situation you describe can happen in an entirely online case, if a device simply hasn’t seen the latest changes yet.

You could build a better solution for presence - for instance, you could create a scheduled trigger that updates a “heartbeat” document on the cloud regularly, and listen for that change on the client. If the change does not come through, you would know you’re offline. We are also thinking about better presence solutions internally.

However, I would not recommend building any application logic that depends on online/offline state, because it can’t always be reliably determined and will be a source of future bugs.

Maybe one way to solve this could be to automatically consider an article “undeleted” if the number of solutions > 1?

Thanks for the suggestions @Sudarshan_Muralidhar

Seeing that MongoDB (Realm) is a serverless, multi-user platform, it’s hard to imagine an app or use case where the online state of a user is not considered, record/object locking doesn’t exist and data is “never” deleted (generally speaking)

How are those processes generally handled in a multi-user MongoDB environment where apps are offline and online all the time?

A situation where user A is working with an object and User B deletes it seems like it would be a common occurrence. Perhaps it’s not?

Soft-deletes “work” but are obviously not ideal and fail with offline/online issues. Heartbeat is something we’ve done but it’s a lot of back and forth communication the developer has to code into the app. Works, but again, not ideal.

How are other developers handling these issues? Realm or otherwise? Is there perhaps a white paper or general guidance? Or have we just come upon a edge case that just doesn’t happen?

I understand the scope of the the ask, but working through Parse, Couchbase, Firebase and a few others, it wasn’t really an issue.

Any insight/direction is greatly appreciated.

Jay

Jay, I apologize for the very long delay here.
I will say that many Realm users who expect concurrent writes on the same objects don’t allow deletes, or are okay with the current semantics. For completeness, those semantics are:

  • if a delete happens after or at the same time as anything else, the delete “wins”
  • the object can be recreated by a client who has received the delete.

I’d suggest looking into potential solutions that could limit deletions (soft-deleting or archiving for example) of objects that could be touched by multiple devices and must be able to “survive” a delete in the way you’re describing.