Very slow one-shot replication when in background on iOS

I’m trying to implement replication when a background-fetch is triggered, or when my app receives a push notification. As continuous replication tasks are suspended when the app is backgrounded (and not un-suspened until the app is foregrounded) I’m trying to create a one-shot pull replication (and then a one-shot push replication). I understand from the apple docs that I have around 30s to do this after which I need to call a completion handler.

It seems that the replications that I start (using the REST API) when the app is in this backgrounded state can be extremely slow, often around 20s and sometimes much more. The amount of data that needs to be replicated is tiny, barely a kilobyte maybe.

Any idea if there’s something I need to set up that allows CBL to behave more normally when my app is backgrounded? Or am I maybe doing something wrong by starting a one-shot replication when I’ve already got a continuous replication running, albeit in a suspended state? Any advice would be appreciated.

I’m not aware of a reason why replications would run more slowly in the background. I suggest you turn on logging, which will give you an idea of what the replicator is doing, and look at the timestamps of the log messages to see what might be taking a long time. Useful log channels would be SyncVerbose and RemoteRequest.

Should I log CBLRemoteRequest or just RemoteRequest? I’ve see docs for both around.

Thanks for the response. I think I’ve narrowed down the problem a bit…

I’m using a long poll with the _changes feed to get new data into my app. On receipt of a push notification I trigger a one-shot pull replication and the get the changes, and then a one-shot push replication… This is an example that resulted in no new data being picked up when there is new data available in the sync gateway. I can’t tell if the one-shot replication failed to download the new data, or the _changes feed didn’t return the new data yet.

In this example the pull sync (which found no new data) was very fast, just 142ms. But the subsequent push sync (which had nothing to do) took 52s.

FYI - The text ‘push-notification with content body’ shows where the notification arrives and process I’ve described starts.

[EDIT] and this is a subsequent example where it all worked perfectly:

And another of it not working (not downloading new data):

So when it works I see this:

CBLRestPuller[]: App backgrounding; starting temporary background task

And when it fails this:

CBLRestPuller[]: App backgrounding, but can't run background task; suspending
CBLRestPuller[]: Error fetching last sequence: NSURLError[-999]

From the logs I can see that part of the problem is that a non-continuous replication has a different checkpoint ID than a continuous one. So when you first run the non-continuous push/pull in the background, they have no saved checkpoints and have to start from sequence 0, which takes quite a while — the docID/revID pairs of all documents in the db have to be transferred to see which ones don’t exist on the other side.

(The reason the checkpoint IDs are different is so the two replications won’t step on each other’s checkpoints; otherwise you couldn’t have them running at the same time. CBL only allows one running replication per checkpoint ID.)

Ok, so is there a workaround? Is it possible to start a one-shot replication without starting from scratch each time?

And what about the log messages, they don’t look related to replication at all. Sometimes I see this (and everything works well):

App backgrounding; starting temporary background task

… and sometimes I see this (and no replication appears to start at all):

App backgrounding, but can't run background task; suspending

I can see any pattern to it. Here’s another example log; 4 push notifications are received. For the 1st and 2nd the app failed to replicate, the 3rd one worked fine, and the 4th failed. I’ve not touched the device at all in-between notifications. As you can see, sometimes it works and sometimes it doesn’t.

The “can’t run background task” message is supposed to be logged when the app is backgrounded if a request to UIApplication to start a background task fails. But I just noticed that the same message appears if the replicator is inactive at the time and doesn’t need to use a background task; that’s misleading and should be fixed. In your case it sounds like the latter is happening. So just ignore that.

It isn’t possible to start another replication and use the same checkpoint, because the two replicators could end up conflicting with each other over what checkpoint value to save. Or rather, it is possible, but it would involve redesign of the way we manage checkpoints; something to look into for 2.0.

What happens if, instead of starting another replication, you set the existing continuous replication’s suspended property to NO? That should wake it and let it catch up. Then when it goes idle (or you run out of time for your push-notification handler) set suspended back to YES.

I’m using the REST API and it isn’t possible to alter the state of existing replications (as discussed elsewhere), even through native code because the replications created via the rest API aren’t accessible via native code. If I could get access to the replication tasks via native code I’m sure I could un-suspend them temporarily.

So why does my current approach work sometimes and not others?

Maybe managing your replications entirely in native code would be the best workaround?

Thanks for the tip. Wish I’d done this 6 months ago instead of persevering with the REST API. Early days but it seems to work much better so far with a native implementation.

Should I re-suspend the replications after I’m done with them, or will that happen naturally?

You should re-suspend them before your bg task finishes.

Sorry this has been so frustrating. We have a small team, and there are so many combinations of features / platforms / APIs that sometimes one of them falls between the cracks, like this combination of iOS, background tasks, replication and REST API.

I understand. Thanks again for the advice.