Vadim
Vadim

Reputation: 89

SwiftData fetch performance problem with relationships

Trying to solve performance issues with SwiftData.

When I add one-to-many relationship to my model, performance drastically degrades.

Code below. To reproduce just add more than 5-6 thousands of models.

@Model
class EnterChild {
    @Relationship
    var enterModel: EnterModel?
    
    init(enterModel: EnterModel? = nil) {
        self.enterModel = enterModel
    }
}

@Model
class EnterModel {
    
    @Relationship(inverse: \EnterChild.enterModel)
    var enterChildren = [EnterChild]()

    var name: String
    
    init(name: String) {
        self.name = name
    }
}

struct EnterView: View {
    @Environment(\.modelContext)
    private var mc
    
    @State
    var models: [EnterModel] = []
    
    var body: some View {
        List {
            Text("\(models.count)")
                .onTapGesture {
                    for i in 1...5000 {
                        let em = EnterModel(name: "Model number \(i)")
                        mc.insert(em)
                        
                        try? mc.save()
                    }
                }
            
        }
        .onAppear {
                       
            var descriptor = FetchDescriptor<EnterModel>()
            
//            descriptor.propertiesToFetch = [\.name]
//            descriptor.relationshipKeyPathsForPrefetching = [\.enterChildren]
            
            if let result = try? mc.fetch(descriptor) {
                models = result
            }
        }
    }
}

Also in my log I see a lot of queries like this:

CoreData: sql: SELECT 0, t0.Z_PK FROM ZENTERCHILD t0 WHERE  t0.ZENTERMODEL = ? 
CoreData: annotation: sql connection fetch time: 0.0000s
CoreData: annotation: total fetch execution time: 0.0000s for 0 rows.
CoreData: annotation: to-many relationship fault "enterChildren" for objectID 0x847373e672d56572 <x-coredata://AA52F4C2-B209-45D9-9F04-FE9291706CCD/EnterModel/p6000> fulfilled from database.  Got 0 rows

I believe SwiftData makes query for every EnterModel record.

Using descriptor.relationshipKeyPathsForPrefetching = [.enterChildren] helps reducing amount of queries, but performance remains poor. Also, if in my real app I have relationships in my children model, which starts producing queries when I use prefetching.

Upvotes: 1

Views: 179

Answers (3)

Vincent
Vincent

Reputation: 11

I have the same issue. I have two classes one-to-many (parent -> [child]) relationship. If I put the SwiftData relationship (explicit or inferred), even adding the records, the performance is degraded too much (Talking about 100k+ records). My only option was remove the relationship and add manually foreign keys to child entities, and modifying the queries to look for Ids.

What helped too was:

  • Turn off auto-saving
  • Reducing save frequency
  • Pagination for querying results

How do you insert an array of objects into the SwiftData ModelContext

Upvotes: 1

Vadim
Vadim

Reputation: 89

Finally I decided to add pagination and performance issues had gone away. Still don't know why selecting several thousands is slow, it seems strange for me.

Upvotes: 0

malhal
malhal

Reputation: 30746

Try fetchIdentifiers or fetchCount

https://developer.apple.com/documentation/SwiftData/ModelContext

Upvotes: 0

Related Questions