Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data Migration is slow although there is nothing to migrate

Tags:

ios

core-data

I've created a new model and renamed 2 fields of an entity. This entity was never used in the previous app version, so I didn't map it to the new updated entity in the database. Consequently the mapping is not mapping anything new, but the migration is very slow (3 seconds for approximately 50 rows in the main table.). I'm wondering if core data is creating a new database and deleting the older one, and reinserting all data. This is the migration log file: http://cl.ly/3H1v252R1p1c

ps. If NSInferMappingModelAutomaticallyOption is YES, does this mean that I can potentially experience data losses, since the mapping is improvised by core data ?

thanks

like image 357
aneuryzm Avatar asked Jun 11 '13 09:06

aneuryzm


2 Answers

Can you show us your database schema? My guess is that you've used object inheritance in it liberally.

Core Data implements inheritance in SQL stores by creating a single table for the parent entity and adding columns for union of all fields used by child entities. So you may think you've renamed only two fields on one entity but Core Data has to apply changes to every object that inherits from that entity or which has a common ancestor of that entity.

In your specific schema it looks like you have an entity, Item, from which at least Genre, AudioTrack, Studio, Director, Store, Movie, Condition, Region, Owner, AspectRatio, Year, MyRating, Episode, Edition, Format, Producer, Writer, AudienceRating, SeenIt and Movie descend. There may be others as I read your log by eye rather than analysing it automatically. So Core Data creates one table named Item into which every instance of every one of those entities is stored. Whenever any of those entities (or the others I've possibly not found) changes, Core Data has to update the records for all instances of every one of those entities.

That's why you see:

CREATE TABLE ZITEM ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, 
ZUNIQUEID INTEGER, ZCOLLECTIONSTATUS INTEGER, ZHASCOVER INTEGER, ZINDEX INTEGER, 
ZPLOTNOTE INTEGER, ZUSERVALUES INTEGER, ZPURCHASEDATEDAY INTEGER, 
ZPURCHASEDATEMONTH INTEGER, ZPURCHASEDATEYEAR INTEGER, ZRELEASEDATEDAY INTEGER, 
ZRELEASEDATEMONTH INTEGER, ZRUNTIME INTEGER, ZVIEWINGDATEDAY INTEGER, 
ZVIEWINGDATEMONTH INTEGER, ZVIEWINGDATEYEAR INTEGER, ZAUDIENCERATING INTEGER, 
ZCONDITION INTEGER, ZEDITION INTEGER, ZFORMAT INTEGER, ZLOANER INTEGER, 
ZLOCATION INTEGER, ZMYRATING INTEGER, ZOWNER INTEGER, ZSEARCH INTEGER, 
ZSEENIT INTEGER, ZSEENWHERE INTEGER, ZSERIES INTEGER, ZSTORAGEDEVICE INTEGER, 
ZSTORE INTEGER, ZYEAR INTEGER, ZRANK INTEGER, ZTYPEID INTEGER, 
ZCOLLECTIBLE INTEGER, Z3_COLLECTIBLE INTEGER, ZBIN INTEGER, ZSORTORDER INTEGER, 
ZSECTION VARCHAR, ZCLZID VARCHAR, ZCONNECTHASH VARCHAR, ZSORTTITLE VARCHAR, 
ZTITLE VARCHAR, ZACTORS VARCHAR, ZCLZMEDIAID VARCHAR, ZCURRENTVALUE VARCHAR, 
ZIMDBNUMBER VARCHAR, ZIMDBRATING VARCHAR, ZLOANDATE VARCHAR, ZLOANDUEDATE VARCHAR, 
ZPURCHASEPRICE VARCHAR, ZSTORAGESLOT VARCHAR, ZTITLEEXTENSION VARCHAR, 
ZUPC VARCHAR, ZTHEDESCRIPTION VARCHAR, ZURL VARCHAR, ZDISPLAYNAME VARCHAR, 
ZSORTNAME VARCHAR ) 

That's the table containing the union of all fields of all descendants of Item. It's also why e.g. a fetch for AudienceRatings is executed as:

SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZSECTION ... FROM ZITEM t0 WHERE  t0.Z_ENT = ?

So:

  • Core Data is updating a lot more than 50 rows;
  • if you want to avoid this you need to avoid inheritance;
  • you can avoid 'is a' relationships with 'has a' relationships but the two-way nature of Core Data often makes that a pain (if 30 objects have a connection to Item then Item must have 30 connections to other objects);
  • if Item is just something trivial like a uniform timestamp it's usually easier just to add a timestamp property to every entity and have each call a common piece of code to establish it.
like image 139
Tommy Avatar answered Nov 15 '22 05:11

Tommy


Core Data on iOS uses SQLite for data storage. SQLite doesn't support ALTER COLUMN, so Core Data works around this limitation by:

  1. renaming the old table
  2. creating a new table with the updated columns
  3. inserting the records into the new table
  4. dropping the old table

There's a good SO answer here which describes this in a bit more detail.

I've never seen any issues with Core Data losing data while performing this operation - I would assume Apple have implemented safeguards to prevent this, but it's probably worth getting in touch with Apple DTS directly to confirm.

like image 36
Vinny Coyne Avatar answered Nov 15 '22 05:11

Vinny Coyne