Skip to content

Fix 'limitExceeded' errors related to FK constraint failures.#359

Merged
stephencelis merged 9 commits into
mainfrom
fix-limit-exceeded
Jan 6, 2026
Merged

Fix 'limitExceeded' errors related to FK constraint failures.#359
stephencelis merged 9 commits into
mainfrom
fix-limit-exceeded

Conversation

@mbrandonw
Copy link
Copy Markdown
Member

@mbrandonw mbrandonw commented Jan 6, 2026

Right now it is possible to run into a limitExceeded error when downloading lots of data (such as fresh install) when there are associations that have lots of data. Those associations can only sync when their parent record is available, and if the parent is not available the record ID is cached to be tried later. The problem is that when the record is tried later we load the freshest data from the server via records(for:), and that API must be batched if fetching lots of records.

This PR implements that batching logic and updates the mock cloud database to reject fetching/sending data if there is too much data.

Fixes #333 / #334

@mbrandonw mbrandonw requested a review from stephencelis January 6, 2026 16:46
let rhsIndex = tablesByOrder[rhs.recordType]
else { return true }
return lhsIndex > rhsIndex
topologicallyAscending(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a bunch of places we do this sorting logic, and I needed to do it again, so I added a helper for it.

Comment on lines +1493 to +1495
let batchSize = 150
let batchCount = unsyncedRecordIDs.count / batchSize
let orderedUnsyncedRecordIDs = unsyncedRecordIDs.sorted {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now invoking records(for:) in batches of 150 and also topologically sorting the unsync'd records IDs before fetching so that we can get the root most records first. This logic could be beefed up to use bigger batches and catch the limitExceeded error so that we can then split the batch in half. Something worth considering later.

errors
*/
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func batchAssociations() async throws {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test failed before the fixes in this PR because it creates 500 reminders and syncs them to the client before the client receives the reminders list. That lead to the limit exceeded error and the system couldn't recover from that.

privateTables: [any SynchronizableTable] = []
) throws {
let allTables = Set((tables + privateTables).map(HashableSynchronizedTable.init))
let allTables = OrderedSet((tables + privateTables).map(HashableSynchronizedTable.init))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to the core of this PR, but I did want to write a test on our topological sorting of tables, and so needed the order of things to be stable.

@stephencelis stephencelis merged commit 8af16d5 into main Jan 6, 2026
5 checks passed
@stephencelis stephencelis deleted the fix-limit-exceeded branch January 6, 2026 21:25
doozMen pushed a commit to doozMen/sqlite-data that referenced this pull request Jan 22, 2026
…reeco#359)

* Fix 'limitExceeded' errors related to FK constraint failures.

* revert

* test explanation

* emulate error for modifyRecords too

* fix test

* rename

* added a test for tablesByOrder

* clean up

* add test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CloudKit sync fails with "Limit Exceeded" error when syncing more than 400 items

2 participants