Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slow deletes and saves in nested NSManagedObjectContext

In my app I have a "master" NSPrivateQueueConcurrencyType context which serves as a parent to a NSMainQueueConcurrencyType context that the view controller's rely on and pass around. I wanted to do this to take advantage of async saves (this app uses iCloud with Core Data and saves do a lot of work exporting ubiquity logs). The setup is similar to the Zarra approach mentioned at the bottom of this article

Saves within the app typically look like:

[context save:nil];
[context.parentContext performBlock:^{
     [context.parentContext save:nil];
}];

This seems to work well for small edits/updates, but I'm confused about what I see when I delete many objects (eg, delete a project that has hundreds of task objects related to it).

In that situation the save is async but it looks like the main thread gets into a semaphore wait situation (as seen when I pause the debugger) and is effectively blocked (I can't scroll in the UI) until the background private context finishes the save.

Actually, I just noticed that swipe-to-delete deletions of a single object incur a noticeable delay, despite this async save pattern, so it seems all deletions in the app are slow.

If, alternatively, I alloc a new NSPrivateQueueConcurrencyType context, set its persistent store coordinator to the main PSC in the AppDelegate, and perform the delete and save, it still does a lot of work but the UI is never blocked (but then I have to worry about coordinating the contexts, refetching in the main context).

Any ideas what I might have done wrong, or have you seen this behavior also?

like image 618
azsromej Avatar asked Aug 03 '12 18:08

azsromej


1 Answers

Deletes can take a long time. They have to fix-up all the relationships. There are several things you should do when making large deletions.

First, why does your UI block when the deletions are happening in the background? Because that background thread is really busy doing the deletion block and your UI is trying to fetch to update the data while the delete is happening.

The thread running the actual work is managed by a FIFO queue, so if you give it a large task to execute, it will not be able to do other tasks until the long-running one completes.

You are probably using a FRC, and it is trying to fetch updates when objects and relationships are changing.

If you have a large delete, you may want to change your fetch so it only looks at the current MOC for data while the delete is happening. When the delete is finished, you can then tell the fetch request to go back to querying all the way to the store itself.

Also, when deleting a lot of stuff, it is best to do it in small chunks, inside of an autorelease pool, in a separate thread. From that background thread, delete small chunks at a time, and do it with performBlockAndWait. That way, you do not load up the worker thread's queue with delete requests, and your UI thread can get its requests processed. more quickly.

Alternatively, you can also use advanced features of GCD to have a high-priority and low-priority queue that is used to feed work to the worker thread. You can put your writes on the low priority queue, and your reads on the high priority queue.

like image 147
Jody Hagins Avatar answered Sep 28 '22 16:09

Jody Hagins