ios – CoreData : Why updating single column using Batch Update Operation is much slower than using NSManagedObject?


All the while, my thoughts on Batch Update Operations are that they are much faster than updating via NSManagedObject. However, as per my testing on a 2000 rows production data set, using Batch Update Operations is 2 to 3 times slower than updating via NSManagedObject.

My update pattern is, there are around 2000 rows. I am updating each of their column named “order”, with different value for each row.

The goal is simple, we want to make the current row’s order value, is larger than previous row’s order value.

Here’s my code snippet. nsPlainNote is an NSManagedObject

Update via Batch Update Operation (slow)

private func _updateOrdersIfPossible2(context: NSManagedObjectContext, updateOrders: [UpdateOrder]) {
    let count = updateOrders.count
    
    if count < 2 {
        return
    }
    
    var prevOrder = updateOrders[0].order
    
    var updatedObjectIDs = [NSManagedObjectID]()
    
    if !Utils.isValidOrder(prevOrder) {
        prevOrder = prevOrder - 1
        
        precondition(Utils.isValidOrder(prevOrder))
        
        // Time-consuming operation.
        if let updatedObjectID = _updateWithoutMerge(context: context, objectID: updateOrders[0].objectID, propertiesToUpdate: [
            "order": prevOrder
        ]) {
            updatedObjectIDs.append(updatedObjectID)
        }
    }
    
    for index in 1..<count {
        precondition(Utils.isValidOrder(prevOrder))
        
        let updateOrder = updateOrders[index]
        
        if !Utils.isValidOrder(updateOrder.order) || updateOrder.order <= prevOrder {
            var newOrder = prevOrder + 1
            
            if !Utils.isValidOrder(newOrder) {
                newOrder = newOrder + 1
            }
            
            precondition(newOrder > prevOrder)
            
            prevOrder = newOrder
            
            precondition(Utils.isValidOrder(newOrder))
            
            // Time-consuming operation.
            if let updatedObjectID = _updateWithoutMerge(context: context, objectID: updateOrder.objectID, propertiesToUpdate: [
                "order": newOrder
            ]) {
                updatedObjectIDs.append(updatedObjectID)
            }
        } else {
            // Skip from updating. Fast!
            
            prevOrder = updateOrder.order
        }
    }   // for index in 1..<count
    
    if !updatedObjectIDs.isEmpty {
        let changes = [NSUpdatedObjectsKey : updatedObjectIDs]
        CoreDataStack.INSTANCE.mergeChanges(changes)
    }
}

private func _updateWithoutMerge(context: NSManagedObjectContext, objectID: NSManagedObjectID, propertiesToUpdate: [AnyHashable : Any]) -> NSManagedObjectID? {
    return RepositoryUtils._updateWithoutMerge(
        context: context,
        entityName: "NSPlainNote",
        objectID: objectID,
        propertiesToUpdate: propertiesToUpdate
    )
}

static func _updateWithoutMerge(context: NSManagedObjectContext, entityName: String, objectID: NSManagedObjectID, propertiesToUpdate: [AnyHashable : Any]) -> NSManagedObjectID? {
    var result: NSManagedObjectID? = nil
    
    do {
        let batchUpdateRequest = NSBatchUpdateRequest(entityName: entityName)
        batchUpdateRequest.predicate = NSPredicate(format: "self = %@", objectID)
        batchUpdateRequest.propertiesToUpdate = propertiesToUpdate
        batchUpdateRequest.resultType = .updatedObjectIDsResultType
        
        let batchUpdateResult = try context.execute(batchUpdateRequest) as? NSBatchUpdateResult
        
        if let managedObjectIDs = batchUpdateResult?.result as? [NSManagedObjectID] {
            result = managedObjectIDs.first
        }
    } catch {
        context.rollback()
        
        error_log(error)
    }
    
    return result
}

As per my benchmark, most time are spent in the loop of calling _updateWithoutMerge.


Update via NSManagedObject (Faster)

private func _updateOrdersIfPossible(context: NSManagedObjectContext, updateOrders: [UpdateOrder]) {
    let count = updateOrders.count
    if count < 2 {
        return
    }
    var prevOrder = updateOrders[0].order
    var updatedObjectIDs = [NSManagedObjectID]()
    if !Utils.isValidOrder(prevOrder) {
        prevOrder = prevOrder - 1
        precondition(Utils.isValidOrder(prevOrder))
        // Time-consuming operation.
        if let nsPlainNote = NSPlainNoteRepository.getNSPlainNote(context: context, objectID: updateOrders[0].objectID, propertiesToFetch: ["order"]) {
            nsPlainNote.order = prevOrder
        }
    }
    for index in 1..<count {
        precondition(Utils.isValidOrder(prevOrder))
        let updateOrder = updateOrders[index]
        if !Utils.isValidOrder(updateOrder.order) || updateOrder.order <= prevOrder {
            var newOrder = prevOrder + 1
            if !Utils.isValidOrder(newOrder) {
                newOrder = newOrder + 1
            }
            precondition(newOrder > prevOrder)
            prevOrder = newOrder
            precondition(Utils.isValidOrder(newOrder))
            // Time-consuming operation.
            if let nsPlainNote = NSPlainNoteRepository.getNSPlainNote(context: context, objectID: updateOrder.objectID, propertiesToFetch: ["order"]) {
                nsPlainNote.order = newOrder
            }
        } else {
            // Skip from updating. Fast!
            prevOrder = updateOrder.order
        }
    }   // for index in 1..<count
    RepositoryUtils.saveContextIfPossible(context)
}
static func getNSPlainNote(context: NSManagedObjectContext, objectID: NSManagedObjectID, propertiesToFetch: [Any]?) -> NSPlainNote? {
    var nsPlainNote: NSPlainNote? = nil
    context.performAndWait {
        let fetchRequest = NSPlainNote.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "self = %@", objectID)
        fetchRequest.propertiesToFetch = propertiesToFetch
        fetchRequest.fetchLimit = 1
        do {
            let nsPlainNotes = try fetchRequest.execute()
            if let _nsPlainNote = nsPlainNotes.first {
                nsPlainNote = _nsPlainNote
            }
        } catch {
            error_log(error)
        }
    }
    return nsPlainNote
}

I thought using Batch Update Operation should be faster than using NSManagedObject?

Am I having wrong expectation, or there is an error in my implementation?

Thanks.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img