CloudKit: Handling CKError.partialFailure with pcs_data errors

My iOS app uses Core Data for local data management and NSPersistentCloudKitContainer to sync data with user’s private iCloud storage. The app has been functioning correctly for several years, but recently, some users have started encountering a CKError.partialFailure error when the app attempts to export data to iCloud. Due to the critical nature of export errors, several features in the app have been disabled to prevent potential data duplication.

Core Data Setup:

lazy var container: NSPersistentContainer = {
  let container: NSPersistentContainer

  if storeType == .inMemory {
      // Used by unit tests
      container = NSPersistentContainer(name: "models")
      container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
  } else {
      container = NSPersistentCloudKitContainer(name: "models")
  }

  container.loadPersistentStores { [weak self] _, error in
      if let error = error {
          self?.logger.error("Failed to load persistent store: \(error)")
          fatalError()
      }
  }
  return container
}()

lazy var context: NSManagedObjectContext = {
  container.viewContext.name = "main"
  container.viewContext.automaticallyMergesChangesFromParent = true
  container.viewContext.undoManager = nil
  container.viewContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
  return container.viewContext
}()

Error Handling:

Following the API documentation on partial failures, I have attempted to log the error’s userInfo property for the CKPartialErrorsByItemIDKey. However, the userInfo object appears to be empty:

  guard let ckError = SyncMonitor.shared.lastError as? CKError else {
      return
  }

  logger.error("ckError: \(ckError)")
  if ckError.code == CKError.partialFailure {
      if let dictionary = ckError.userInfo[CKPartialErrorsByItemIDKey] as? NSDictionary {
          for (recordID, error) in dictionary {
              logger.error("\(recordID): \(error)")
          }
      }
  }
}

This code results in the following log:

ckError: Error Domain=CKErrorDomain Code=2 "(null)"

CloudKit Logs:

Upon reviewing the CloudKit Console logs, I observed two types of errors: QUOTA_EXCEEDED and BAD_REQUEST, both associated with the “returnedRecordTypes” field showing _pcs_data.

Log 1:

{
  "time":"17/08/2024, 19:02:14 UTC",
  "database":"PRIVATE",
  "zone":"com.apple.coredata.cloudkit.zone",
  "userId":<redacted>,
  "operationId":"14F4FAE7F4B75973",
  "operationType":"RecordSave",
  "platform":"iPhone",
  "clientOS":"iOS;17.5.x",
  "overallStatus":"USER_ERROR",
  "error":"QUOTA_EXCEEDED",
  "requestId":"12EB47C3-08A9-439B-9560-E38C32EE4643",
  "executionTimeMs":"259",
  "interfaceType":"NATIVE",
  "recordInsertBytes":300418,
  "recordInsertCount":25,
  "returnedRecordTypes":"_pcs_data"
}

Log 2:

{
  "time":"17/08/2024, 18:41:31 UTC",
  "database":"PRIVATE",
  "zone":"com.apple.coredata.cloudkit.zone",
  "userId":<redacted>,
  "operationId":"8AC0CDC966F6E903",
  "operationType":"RecordSave",
  "platform":"iPhone",
  "clientOS":"iOS;17.5.x",
  "overallStatus":"USER_ERROR",
  "error":"BAD_REQUEST",
  "requestId":"75AC88E2-BFB7-4A41-977D-8E4067A0F40A",
  "executionTimeMs":"283",
  "interfaceType":"NATIVE",
  "returnedRecordTypes":"_pcs_data"
}

It is worth noting that all RecordSave logs containing my app’s data models in the returnedRecordTypes field have been successful. Additionally, I can confirm that users experiencing this error have ample unused iCloud storage.

Despite extensive research on this topic, I have been unable to find relevant information to resolve this issue. It’s unclear whether this _pcs_data error can be ignored, what kind of quota this error refers to, or where I can find more information about how much space my app has consumed. I would greatly appreciate any help in pointing me in the right direction.

_pcs_data is tied to the iCloud account (Apple ID), and is needed when an app accesses a CloudKit private database. When the device is logged out and then logged in with a new iCloud account, NSPersistentCloudKitContainer deletes the data owned by the original account. Unless you do something interesting that allows one account to access the database owned by the other, you don't need to worry about _pcs_data.

The BAD_REQUEST error isn't quite concerning to me because NSPersistentCloudKitContainer takes care the requests and may retry them if needed. Worth mentioning though, CloudKit has some limits, as discussed in Avoid hitting a CloudKit limit. You might want to confirm that your app doesn't hit any of them.

QUOTA_EXCEEDED is what I would concerned. It is related to the iCloud storage quota of the current logged in account. If that account did have plenty of un-used storage, as you had mentioned, it can be something on the system side went wrong – Either the device showed a wrong un-used space, or the server did a wrong check. In that case, I’d suggest that you file a feedback report (http://developer.apple.com/bug-reporting/).

To debug the issue, you might consider gathering and looking into a sysdiagnose to see if it has more verbose logs that unveil more information. The following technote covers the details of how to do that:

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

CloudKit: Handling CKError.partialFailure with pcs_data errors
 
 
Q