5

The database in our app consists of objects nested several layers deep. For architectural reasons, it is not feasible for us to migrate away from this right now.

A high percentage of the data becomes expired on a daily basis. Our app performance degrades as the database grows in size.

Therefore, we need to find an effective way to keep the database small (at least in this release) and we are considering one of the following approaches:

  1. During applicationWillResignActive, delete NSManagedObjects by iterating thru all of the objects at the root level, calling delete on each one, and then allowing the deletes to be cascaded to the 3 layers of “to-many” objects. This involves one context save at the end that commits all this to the DB. This often it takes 10-20 seconds (on an iPhone 4) to delete the objects and Springboard terminates the process at 10 seconds. One major downside of this is that if the context save does not complete before the 10 second timeout, nothing gets deleted and the DB continues to grow each time the user runs the app.

  2. Delete the entire sqlite file inside didFinishLaunchingWithOptions or applicationDidBecomeActive, or inside applicationWillResignActive. This forces us to pop to the root of the app’s view controller stack so as to avoid attempting to display deleted data. The biggest downside of this is that the app has to download and parse data for several seconds before the user can do anything the next time they launch the app.

  3. Delete DB objects using beginBackgroundTaskWithExpirationHandler, after the user has hit the Home or Power button. There are unknowns associated with this that make it scary. Is anyone doing it (successfully) this way?

  4. Delete smaller groups of objects incrementally while the app is running. This increases the load on the device enough that it wrecks the smoothness of the tableviews, making them jittery. The amount of data already being parsed by API calls concurrent to deletion of old objects seems impractical here.

Any thoughts on the best practice for deleting objects in Core Data would be appreciated!

jpswain
  • 14,642
  • 8
  • 58
  • 63

3 Answers3

1

I ran into such a situation, where I didn't had the time to clean the data.

I solved the problem marking some key entities as deleted (an entity attribute), and deleting them (and cascade) for real later on. You do not have to mark every deleted entity, just the ones that makes sense to allow the postponed deletion and so the UI can easily know if any entity, marked or not, is about to be deleted. Sounds hard, but it shouldn't be if your data model is clean enough.

When your app restart, you either delete the entities and cascading - if this can't go to the end, you'll just restart the deletion when possible - or first iterate and mark all the deletable entities, possibly deleting them "one by one". This can be done in the background without messing up the UI (as the UI knows about the deletable entities). If you have performance issues doing so, you can process the deletions incrementally pretty easily.

That "mark and delete later" policy worked pretty well for me.

  • Additionally: Do **not** call the attribute "deleted" (or, in general, anything that conflicts with existing methods under KVC naming conventions), and make sure that it is indexed. You can then do (say) 3 seconds' worth of deletions every time the app is backgrounded, which should be sufficient. – tc. Jul 24 '12 at 17:19
  • The issue of marking it "deleted" is not really relevant for us anyways, because our fetchRequests ensure that the "expired" objects are never queried anyways. So keeping them out of results is not a challenge, but rather when to get rid of them without hurting performance. – jpswain Jul 24 '12 at 17:34
  • Sounds you already have everything you need to delete the data at any time, you just need to figure out when and how to delete them efficiently. You can monitor the user activity and schedule small chunks of deletions when the user's sleeping (you see what I mean). Do not try to delete everything and save once. – fabrice truillot de chambrier Jul 24 '12 at 17:43
  • @fabricetruillotdechambrier Cool, I will try the small batch/one-at-a-time idea. Any reason not to use the backgroundTask solution offered by adonoho ? Based on testing so far it looks like it should work well for us. – jpswain Jul 25 '12 at 03:33
1

orange80,

You should do your cascading delete in a background queue launched from within -applicationDidEnterBackground:. To keep from being terminated, you must use a background task ID.

I use this strategy in my frequently churning instance CD apps. By and large using this strategy, the user will never see the delete.

Andrew

adonoho
  • 4,339
  • 1
  • 18
  • 22
  • Except you still *ought to* handle the case where it returns `UIBackgroundTaskInvalid`, especially if you want to support the iPhone 3G. There's direct way to restrict to devices that support background tasks, but I think compiling only for `armv7` or specifying a deployment target of 4.3 is sufficient. – tc. Jul 24 '12 at 17:31
  • tc, the strategy I describe works fine on any iOS v4+ device, `arm6` or `arm7`. A 3G running iOS v4.2.1 works fine. Andrew – adonoho Jul 26 '12 at 12:53
  • Last I checked, -applicationDidEnterBackground: does not get called on the iPhone 3G. – tc. Jul 31 '12 at 18:32
0

You may want to consider doing option 1 in combination with flagging certain entities for deletion. That way even if the delete does not complete upon app exit, upon startup (assuming your app launch doesn't already take some time) you can perform deletions of the flagged entities (for say a couple seconds) and then resort to doing "light" deletes in the background, say when there is no user interaction (no moving table view). Sure, this will add some time to firing up the app, but I think it may be a viable option given your circumstances.

Additionally, I hope that you are performing batch loads via Core Data and are only loading objects into memory as needed.

Quickly rereading your question I just had another thought... Along the lines of #2... why don't you experiment with migrating your obsolete data to a separate file and then when the user exits the app, merely delete that file? There will definitely be overhead with migrating these objects but it may provide for a smoother UX.

Stunner
  • 12,025
  • 12
  • 86
  • 145
  • I'm assuming you say "when the user exits" you mean "backgrounds". It has the same problem as (2) unless you manually fix up references to objects in your VCs. – tc. Jul 24 '12 at 17:22