Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance: Core Data relationships get faulted after being assigned

I have a Core Data model representing a TV guide on iOS 4+, with 3 classes:

  • Channel (BBC 1)
  • Program (Top Gear)
  • Broadcast (Top Gear on BBC 1 on Monday at 8pm)

I have about 40 channels, 8000 programs and 6000 broadcasts, and I would like to fine-tune the import process so that it doesn't take up to a minute to run.

Importing the channels and programs is easy because these are independent objects. A broadcast however has a relationship to a channel and to a program (1-to-many), and both channels and programs have inverse relationships to the broadcasts (many-to-1). To speed things up I have an in-memory dictionary of fault channels and programs that have only their Web Service identifier prefetched: I create a broadcast and look through both dictionaries to get the corresponding channel and program without a round-trip to the database.

But when I assign a program or a channel to a broadcast, the channel and program's inverse relationships access trigger a fault of both objects right away, causing a massive slowdown (6000 * 2 requests) and consequent memory pressure as shown in the Core Data Faults Instruments report. I tried pre-fetching the broadcasts relationship on both channels and programs, but the relationship still gets faulted.

Do you have any idea why the inverse relationships get accessed and fault their parents? How do I avoid reading from the database when saving a relationship?

UPDATE: Sample code, my assign / update method for a Broadcast instance. The dictionary variable comes from the Web Service and channels and programs contain the fault channels and programs objects indexed by Web Service identifier. Faulting occurs on the self.program = program and self.channel = channel lines.

- (BOOL)assignWithDictionary:(NSDictionary *)dictionary channels:(NSDictionary *)channels programs:(NSDictionary *)programs {
    // Add channel relationship
    NSNumber *channelIdentifier = [dictionary objectForKey:@"channel_id"];

    if (self.channel == nil || ![self.channel.identifier isEqualToNumber:channelIdentifier]) {
        Channel *channel = [channels objectForKey:channelIdentifier];

        if (channel == nil) {
            NSLog(@"Broadcast %@ has invalid channel: %@", identifier, channelIdentifier);

            return NO;
        }

        self.channel = channel;
    }

    // Same to add a program relationship
    // ...
}

And my fetch request to get the channels or the programs list:

- (NSDictionary *)itemsForEntity:(NSEntityDescription *)entity {
    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    NSError *error = nil;
    NSArray *itemsArray = nil;

    request.entity = entity;

    request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:@"broadcasts", nil];
    request.propertiesToFetch = [NSArray arrayWithObjects:@"identifier", @"version", nil];

    itemsArray = [self.context executeFetchRequest:request error:&error];
    NSAssert1(error == nil, @"Could not fetch the items from the database: %@", error);

    {
        NSMutableDictionary *items = [NSMutableDictionary dictionaryWithCapacity:itemsArray.count];

        for (NSManagedObject *item in itemsArray) {
            [items setObject:item forKey:[item valueForKey:@"identifier"]];
        }

        return [NSDictionary dictionaryWithDictionary:items];
    }
}
like image 921
ndfred Avatar asked Aug 12 '11 14:08

ndfred


People also ask

What is faulting in Core Data?

Faulting allows Core Data to put boundaries on the object graph. Because a fault is not realized, a managed object fault consumes less memory, and managed objects related to a fault are not required to be represented in memory at all.

Is it possible to have relationship in Core Data?

Inverse relationships enable Core Data to propagate change in both directions when an instance of either the source or destination type changes. Every relationship must have an inverse. When creating relationships in the Graph editor, you add inverse relationships between entities in a single step.

What are fetched properties in Core Data?

Fetched Properties in Core Data are properties that return an array value from a predicate. A fetched property predicate is a Core Data query that evaluates to an array of results.


1 Answers

Not exactly sure what you are trying to do here but...

The first thing is that you can't alter properties using just faults. Faults are just placeholders to allow you to measure/count the object graph and walk relationships. If you actually alter a relationship it will fire the fault causing the related objects to load.

If you are trying to set relationships between specific Channel, Program and Broadcast objects using just faults, that won't work.

Your itemsForEntity: method I don't understand. It will fetch every existing managed object of the entity passed and then it will return those objects in a dictionary. That will cause a massive memory overhead especially in the case of the Program objects of which there are 8,000.

You can't use propertiesToFetch unless you set the fetch return to dictionary, which you don't. You can't use a dictionary return type anyway if you need to set relationships. You use both these when all you want is the data held in certain attributes. It's not a tool for manipulating the object graph's relationships.

Setting the relationshipKeyPathsForPrefetching only speeds things up if you know you will be accessing an existing relationship. It doesn't help when you are setting the relationships up in the first place e.g. if there is no existing objects in the broadcasts relationships or you are adding or removing Broadcast objects, prefetching the broadcasts keypath does nothing for you.

I'm not sure I understand your data model well enough but I think you are going about this the wrong way. It looks to me like your trying to use the identifier like a primary key in a SQL database and that is counterproductive. In Core Data, a relationship links to objects together, not a shared attribute and value.

As a rule, if you have two or more objects with the same attribute name with the same value, then you have a poorly designed data model in most cases.

like image 131
TechZen Avatar answered Oct 20 '22 18:10

TechZen