I want to pull down around 500 "Visit" records from the public database. CloudKit only gives you 100 records at a time so I just utilize the CKQueryCursor like below to get all the records I want.
func fetchVisits(_ cursor: CKQueryCursor? = nil) {
print("fetchVisits \(cursor)")
var operation: CKQueryOperation!
if let cursor = cursor {
operation = CKQueryOperation(cursor: cursor)
} else {
let query = CKQuery(recordType: "Visit", predicate: NSPredicate(format: "Client IN %@ AND ANY Students IN %@", visitClients, visitStudents))
operation = CKQueryOperation(query: query)
}
operation.recordFetchedBlock = {
(record) in
totalVisits.append(record)
}
operation.queryCompletionBlock = {
(cursor, error) in
if let error = error {
//handle error
} else if let cursor = cursor {
self.fetchVisits(cursor)
} else {
//all done!
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
I call the function like:
fetchVisits()
That works fine, it gets all the visits I need. Console Log
fetchVisits nil
fetchVisits Optional(<CKQueryCursor: 0x174228140; id=4bb7887c326fc719, zone=(null)>)
fetchVisits Optional(<CKQueryCursor: 0x17422a320; id=f67fb25669486da9, zone=(null)>)
fetchVisits Optional(<CKQueryCursor: 0x174228380; id=7e87eb8b7cfe1a74, zone=(null)>)
fetchVisits Optional(<CKQueryCursor: 0x17422cc80; id=e77e47ef2b29c8a4, zone=(null)>)
But the issue is now I want to refresh when I push a button and now it gives me this error:
"Service Unavailable" (6/2022); "Request failed with http status code 503"; Retry after 30.0 seconds
Which is pretty self explanatory, I guess I am overwhelming the server by requesting a measly 500 records? So I wait 30 seconds and invoke the function again and now I get this error.
"Limit Exceeded" (27/2023); server message = "Query filter exceeds the limit of values: 250 for container
For some reason I cannot run that function again. If I restart the app it works fine again but only the first time. This issue seems to be specific to any table that returns a CKQueryCursor. I have other tables that I'm hitting that have less than 100 records (so the cursor is nil) and I can pull those multiple times without any issues.
Ok, so I've seen this before and the issue is, I believe, a bug in on the CloudKit servers. In my experience, it has to do with complex queries.
If you try changing your predicate to:
NSPredicate(value: true)
Or even simplifying your existing one by removing the ANY part, that may be enough to fix it.
You're requesting more CKRecords to iCloud before your query operation finished.
...
operation.queryCompletionBlock = {
(cursor, error) in
if let error = error {
//handle error
} else if let cursor = cursor {
self.fetchVisits(cursor)
} else {
//all done!
}
}
...
Function self.fetchVisits(cursor)
call is done inside the completion block, this means that your are requesting for more records before your current operation has finished.
Possible solution is to use a closure (completionHandler) in which you include the CKQueryCursor, when user need more records (scroll a table or whatever) you call one again self.fetchVisits
passing the cursos received on your closure.
EDIT: I think by now I found the reason:
While my previous answer (below) is right, it does not apply to this problem.
By now, I encountered myself the two server errors mentioned in the question, and investigated the situation.
My setup is the following:
I want to download a subset of iCloud records depending on a condition specified in a predicate:
let doNotDownloadIfOlderFormat = "NOT " + iCloudRecordKeyName + " IN %@"
let doNotDownloadIfOlderPredicate = NSPredicate.init(format: doNotDownloadIfOlderFormat, doNotDownloadIfOlder)
Here, doNotDownloadIfOlder
is a set of custom objects.
In some test cases, their number was more than 300. I suspected that this could be the reason for the error message Query filter exceeds the limit of values: 250 for container.
I thus modified my code:
let doNotDownloadIfOlderFormat = "NOT " + iCloudRecordKeyName + " IN %@"
let limit = min(doNotDownloadIfOlder.count, max)
let shortened = Set(Array(doNotDownloadIfOlder)[0 ..< limit])
let doNotDownloadIfOlderPredicate = NSPredicate.init(format: doNotDownloadIfOlderFormat, shortened)
where I set max
to a test value below 300.
This is the result:
Obviously there is a probably undocumented server limit for queries. The strange number 140 could have something to do with the size of my custom objects.
Previous answer:
The docs say:
The server can change its limits at any time, but the following are general guidelines:
- 400 items (records or shares) per operation
- 2 MB per request (not counting asset sizes)
If your app receives
CKError.Code.limitExceeded
, it must split the operation in half and try both requests again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With