realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.31k stars 2.14k forks source link

Slow performance with multiple realms / Caching RLMRealm objects #3019

Closed rusik closed 8 years ago

rusik commented 8 years ago

I have data structure like this:

Trick : RLMObject {
    trickID, // primary key
    name,
    ...
    @readonly progress {
        return [TrickProgress objectForPrimaryKey:self.trickID];
    }
}

TrickProgress : RLMObject {
    trickID, // primary key
    progress
}

I have table view with data source like this:

- cellForRowAtIndexPath {

    Trick *trick = tricks[indexPath.row];
    something = trick.progress; // Some computes with Trick `progress` property
    ...
}

The question is: If I store Trick and TrickProgress entities in the same realm the performance is really great, but if i store them in the different realms (what I want to do) the performance is so bad (even on iPhone 6s) so I can't scroll table view without visible lagging. Yeah, maybe this is not so great idea to fetch entity from database in the cellForRow, but the question is why this is such a big difference in performance?

rusik commented 8 years ago

Note: realms have default configuraton except file path

rusik commented 8 years ago

Hmm, as i can see, you are caching realms with weak reference, so if it not referencing with strong anywhere in user's code it creates every time. For my objects i create realms dynamically with realmWithConfiguration and don't store this realms with strong reference. So realm for TrickProgress creates for every cellForRowAtIndexPath call. This is the cause of bad performance.

But is it a good idea to cache realms with weak? Maybe it should be strong? Becuase otherwise this is not very usefull if i should store strong reference to realm at the same time in my code.

rusik commented 8 years ago

I think your cachng strategy looks a little bit strange (and not very useful):

  1. I can't create two different objects of RLMRealm for the same thread (because the second realmWithConfiguration will return the first created object) — is it a core limitation?
  2. Cache work only if i already have strong reference to RLRealm object in my code (what in fact is a cache on my side, not yours)
  3. If i don't want to recreate RLMRealm objects every time for different threads i should implement my custom logic to store them with strong references (but you already have this logic in realmWithConfiguration but it's not working without strong references in my code)
mrackwitz commented 8 years ago

1) I can't create two different objects of RLMRealm for the same thread (because the second realmWithConfiguration will return the first created object) — is it a core limitation?

That's not the case. You can have multiple different objects of RLMRealm on the same thread. Realms are cached by their path.

2) Cache work only if i already have strong reference to RLRealm object in my code (what in fact is a cache on my side, not yours)

Yup, that's intentional. Otherwise you would have no chance to close a Realm, which is in some cases necessary. It's your responsibility to hold it as long as you want to have access to it.

3) If i don't want to recreate RLMRealm objects every time for different threads i should implement my custom logic to store them with strong references (but you already have this logic in realmWithConfiguration but it's not working without strong references in my code)

That's right. It would be already sufficient in your case, if you store the realm in a property of your view controller. This shouldn't hurt or disturb to much.

rusik commented 8 years ago

1) That's not the case. You can have multiple different objects of RLMRealm on the same thread. Realms are cached by their path.

Yeah, I said a little bit incorrect. So I can't create two different objects of RLMRealm for the same path on the same thread, why this limitation is exist?

2) Yup, that's intentional. Otherwise you would have no chance to close a Realm, which is in some cases necessary. It's your responsibility to hold it as long as you want to have access to it.

As i see there is no public method to close a realm.

3) That's right. It would be already sufficient in your case, if you store the realm in a property of your view controller. This shouldn't hurt or disturb to much.

I can't see any useful cases for this type of caching RLMRealm objects by realm. As for me it is creating more misunderstading than benefits.

mrackwitz commented 8 years ago

Yeah, I said a little bit incorrect. So I can't create two different objects of RLMRealm for the same path on the same thread, why this limitation is exist?

There are not too many valid cases, I can come up where it would make sense to allow accessing the same realm with different configurations and there would be some problems you can easily hit, if we would allow that (different schema (/versions)). If you have anything in mind where it restricts you currently, please explain how and why.

I can't see any useful cases for this type of caching RLMRealm objects by realm. As for me it is creating more misunderstading than benefits.

It's a significant performance improvement and workaround for limitations of the supported platforms. Without that you can easily run out of mapped memory regions.

rusik commented 8 years ago

Okey, I have a case for you.

I have two realm files in documents folder. One is completely empty Empty.realm and another one has some data Data.realm. I have this code:

#define FileInDocuments(file) [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:file]

[[NSFileManager defaultManager] copyItemAtPath:FileInDocuments(@"Empty.realm")
                                        toPath:FileInDocuments(@"Current.realm")
                                         error:nil];
RLMRealm *realm = [RLMRealm realmWithPath:FileInDocuments(@"Current.realm")];
NSLog(@"%d", realm.isEmpty); // 1 — this is correct

[[NSFileManager defaultManager] removeItemAtPath:FileInDocuments(@"Current.realm") error:nil];
[[NSFileManager defaultManager] copyItemAtPath:FileInDocuments(@"Data.realm")
                                        toPath:FileInDocuments(@"Current.realm")
                                         error:nil];
realm = [RLMRealm realmWithPath:FileInDocuments(@"Current.realm")];
NSLog(@"%d", realm.isEmpty); // 1 — for this time this is not correct

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    RLMRealm *realm2 = [RLMRealm realmWithPath:FileInDocuments(@"Current.realm")];
    NSLog(@"%d", realm2.isEmpty); // 0 — this is correct
}];

So as you can see first i've opened an empty file, then replaced it with file with data, but realmWithPath: returned to me a cached RLMRealm object referencing an old empty file.

How can i avoid or fix this issue without knowing the internal structure of realmWithConfiguration: method with it caching strategy?

This is just one strange case with caching but I think it can be much more.

mrackwitz commented 8 years ago

How can i avoid or fix this issue without knowing the internal structure of realmWithConfiguration: method with it caching strategy?

You should always close a Realm file by not retaining anymore any accessor to it, before making modifications to the file on disk.

For your fix I'd recommend using a dedicated autoreleasepool like seen below:

@autoreleasepool {
     RLMRealm *realm = [RLMRealm realmWithPath:FileInDocuments(@"Current.realm")];
     NSLog(@"%d", realm.isEmpty); // 1 — this is correct
}
jpsim commented 8 years ago

I'm closing this issue as @mrackwitz's suggestion is correct. @Rusik please let us know if there's anything more we can do to help you!

peterpaulis commented 7 years ago

The OP noted that doing realmWithConfiguration is slow, but thats the way how the default realm is implemented

+ (instancetype)defaultRealm {
    return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
}

does this mean that using the provided defaultRealm is a slowdown?

jpsim commented 7 years ago

No, the default Realm helper is not any slower than the configuration-based initializer (or any other initializer). However, the first initialization of a Realm will always be slower than subsequent inits because the file has to be mmapped initially, whereas that doesn't need to be done afterwards.

peterpaulis commented 7 years ago

No, the default Realm helper is not any slower than the configuration-based initializer (or any other initializer).

Thats exactly what i mean, if the configuration-based initializer is slow, than the default Realm helper is slow... and the OP indicated, that the configuration-based initializer is slow, while calling it multiple times in a loop

jpsim commented 7 years ago

Not sure what your question is. "if the configuration-based initializer is slow", what does slow mean in your context? Slower than what? There's no alternative initializer that will be faster...

peterpaulis commented 7 years ago

well, it is not explicitly clear that when calling defaultRealm a new realm is constructed each time when calling defaultRealm, so the user would expect the performance of a reference retrieval, not the performance of object construction

jpsim commented 7 years ago

it is not explicitly clear that when calling defaultRealm a new realm is constructed each time when calling defaultRealm

That is not generally the case. The file mapping is only unloaded if all in-memory references to the Realm are entirely freed.