CKError: Фильтр запросов превышает предел значений: 250 для контейнера

Я хочу собрать около 500 записей "Визит" из публичной базы данных. CloudKit дает вам только 100 записей за раз, поэтому я просто использую CKQueryCursor, как показано ниже, чтобы получить все записи, которые я хочу.

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)
}

Я называю функцию как:

fetchVisits()

Это прекрасно работает, он получает все посещения, которые мне нужны. Консольный журнал

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)>)

Но проблема в том, что теперь я хочу обновить, когда я нажимаю кнопку, и теперь она дает мне эту ошибку:

"Сервис недоступен" (6/2022); "Ошибка запроса с кодом состояния http 503"; Повторите попытку через 30 секунд

Что само собой разумеется, я думаю, я перегружаю сервер, запрашивая жалкие 500 записей? Поэтому я жду 30 секунд и снова вызываю функцию, и теперь я получаю эту ошибку.

"Превышен лимит" (27/2023); server message = "Фильтр запросов превышает предел значений: 250 для контейнера

По какой-то причине я не могу запустить эту функцию снова. Если я перезапущу приложение, оно снова будет работать нормально, но только в первый раз. Эта проблема характерна для любой таблицы, которая возвращает CKQueryCursor. У меня есть другие таблицы, в которые я попал, которые содержат менее 100 записей (поэтому курсор равен нулю), и я могу вытащить их несколько раз без каких-либо проблем.

2 ответа

Решение

Итак, я видел это раньше, и проблема, я полагаю, заключается в ошибке на серверах CloudKit. По моему опыту, это связано со сложными запросами.

Если вы попытаетесь изменить свой предикат на:

NSPredicate(value: true)

Или даже упростить существующую, удалив ЛЮБУЮ деталь, этого может быть достаточно, чтобы исправить это.

Вы запрашиваете больше CKRecords для iCloud до завершения операции запроса.

...
operation.queryCompletionBlock = {
    (cursor, error) in
    if let error = error {
        //handle error
    } else if let cursor = cursor {
        self.fetchVisits(cursor)
    } else {
        //all done!
    }
}
...

функция self.fetchVisits(cursor) вызов выполняется внутри блока завершения, это означает, что вы запрашиваете больше записей до завершения текущей операции.

Возможное решение состоит в том, чтобы использовать замыкание (completeHandler), в которое вы включаете CKQueryCursor, когда пользователю нужно больше записей (прокрутка таблицы или что-то еще), вы вызываете один раз self.fetchVisits прохождение курсора получено на ваше закрытие.

РЕДАКТИРОВАТЬ: Я думаю, что к настоящему времени я нашел причину:

Хотя мой предыдущий ответ (ниже) верен, он не относится к этой проблеме.
К настоящему времени я столкнулся с двумя ошибками сервера, упомянутыми в вопросе, и исследовал ситуацию.

Моя установка следующая:
я хочу загрузить подмножество записей iCloud в зависимости от условия, указанного в предикате:

let doNotDownloadIfOlderFormat = "NOT " + iCloudRecordKeyName + " IN %@"
let doNotDownloadIfOlderPredicate = NSPredicate.init(format: doNotDownloadIfOlderFormat, doNotDownloadIfOlder)  

Вот, doNotDownloadIfOlder представляет собой набор настраиваемых объектов.

В некоторых тестовых случаях их количество превышало 300. Я подозревал, что это могло быть причиной сообщения об ошибке. Фильтр запросов превышает лимит значений: 250 для контейнера.

Таким образом, я изменил свой код:

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)  

где я установил maxдо тестового значения ниже 300.
Вот результат:

  • max <= 140: без ошибок
  • 141 <= max <= 250: "Служба недоступна" (6/2022); "Запрос не удался, код статуса http 503"; Повторить попытку через 30,0 секунд>
  • max > 250: "Превышен предел" (27/2023); server message = "Фильтр запроса превышает лимит значений: 250 для контейнера

Очевидно, существует недокументированный лимит сервера для запросов. Странное число 140 могло иметь какое-то отношение к размеру моих пользовательских объектов.

Предыдущий ответ:

В документах говорится:

Сервер может изменить свои ограничения в любое время, но следующие общие рекомендации:

  • 400 элементов (записей или акций) на операцию
  • 2 МБ на запрос (не считая размеров активов)

Если ваше приложение получает CKError.Code.limitExceeded, он должен разделить операцию пополам и повторить оба запроса.

Другие вопросы по тегам