-
Type: Bug
-
Resolution: Done
-
Priority: Minor - P4
-
None
-
Affects Version/s: None
-
Component/s: None
Goals
A change in a single property of a single object should not degrade the user experience with a Realm that has a complex structure.
Expected Results
I expected a change in a single property would not overload the CPU and I'd have to wait for all the DeepChangeCheckers to be completed to change another property of the same object or another object or the same property of the same object.
Actual Results
Here is the issue with my app:
- It is a book production project management app that lets one manage:
- Publications
- Editions of publications
- Stages of work on publication (prepress work) and editions (editorial work).
- Tasks that can be assigned to publications, editions, stages and so on.
- Editorial and production staff database
- etc etc
- Using RealmSwift 5.5.0
I am having an issue with my app that uses RealmSwift and also used for collaboration via the Realm Sync. The schema is quite complex taking in account I am more of a project management person.
The current version of the RealmSwift I use is 5.5.0.
I could elaborate more on the app and its schema but I created a sample app where I can reproduce the scope of the issue (see the Code Sample section of the issue).
The sample app is an augmented version of the Dogs/Persons Realm example. Here are the Realm classes:
class Person: Object { @objc dynamic var name = “” let dogs = List<Dog>() @objc dynamic var uuid: String = “” override static func primaryKey() -> String? { return “uuid” } } class Dog: Object { @objc dynamic var name = “” @objc dynamic var age: Int = 0 let owners = LinkingObjects(fromType: Person.self, property: “dogs”) @objc dynamic var uuid: String = “” override static func primaryKey() -> String? { return “uuid” } }
To make the problem manifest in the same way I am experiencing it in my app, I have 100 000 dogs and 100 000 persons in the database (the sample app generates all of these on the first launch of the app), so wait around a minute for it to finish generating the sample data.
The dogs (List<Dog>) is populated for each person with a random number of dogs per person (between 0 and 50 dogs per person). But that is again just the setup of the database.
As you can see the backlink from a dog to the person owning it is:
LinkingObjects(fromType: Person.self, property: “dogs”)
As you can see there are two notification tokens in the app: one for all the dogs and one for all the persons.
The app allows you to change/regenerate the name of a random dog or a random person by clicking the buttons in the interface.
Now to usage and the issue:
When I click either of the two buttons, only one property of a single object in the database changes (a dog’s or a person’s name). As you can see in the Instruments, a “realm notification listener” thread goes into heavy action, the CPU is 100%+ and it last for a few seconds.
As fat as I understand the following is happening here:
In the case when a dog’s name is changed:
Once the button is clicked and the value is changed, the Realm goes into “DeepChangeChecker mode” by checking every instance of a dog (and perhaps persons too) to make sure no other object got changed and traversing each and every property of every dog/person.
As you can see the collection notifications for the “updated” persons fire (all of them who happen to own that dog).
I cannot click the button to change the name of a person (or a dog) immediately since the “realm notification listener” thread will be blocking the realm till the DeepChangeChecker is finalized.
It means the data entry/changes are crippled/delayed/the beach ball spins but I am changing a single value in a single object.
In the case when a person’s name is changed:
Since the owners are linking object, only the person’s name change notification token is triggered…but… the Realm happily goes into the “DeepChangeChecker mode” which I thought it should not be doing in case of linkingObjects.
I understand that it is probably not the Realm’s problem since the same behavior can be observed both in the older RealmSwift 5 and the latest RealmSwift 10.
Perhaps the problem is the schema of the app or my misconception about the way the notification tokens should be created.
E.g. if I remove all the tokens, naturally things work fast.
But imagine the following example: I have a NSOutlineView that displays all persons at the first level and then the dogs they own when you disclose an item. And a colleague of mine looks at the same view from his/her computer and wants to see the changes in real time, etc.
Steps for others to Reproduce
Run the supplied sample app, click any of the two buttons and observe the CPU/thread activity in Xcode or Instruments.
Code Sample
Here are two GitHub repositories with the sample apps: they are identical with one difference: one uses RealmSwift 5.5.0 and the other one RealmSwift 10.5.1 (latest as of today):
GitHub - HKdAlex/Realm-5.5.0
GitHub - HKdAlex/Realm-10
Version of Realm and Tooling
Realm framework version: RealmSwift 5.5.0 and RealmSwift 10.5.1
Realm Object Server version: Irrelevant in this case.
Xcode version: 12.3 (12C33)
iOS/OSX version: macOS Big Sur, 11.1 (20C69)
Dependency manager + version: Swift Package Manager