Realm: mmap() failed: Cannot allocate memory size
Asked Answered
H

1

6

I'm using Realm 2.4.3 in a large iPad Application for one of our customer.
Most of the time Realm performed very well, but now the number of Records is increasing and a get some memory issues.

App

  • Deployment Target: 10.0
  • BaseSDK: 10.2
  • iPad only
  • Realm 2.4.3
  • The App was designed to work offline, thats why we use Realm for local storage
  • There are some methods which syncs Data from a Backend via HTTP to the Realm Database and vice versa.
  • Number of Records in Realm Database Number of Records

(unexpected) behaviour

  • the realm file size growth above 2,5GB (with compact fix from below ~500KB)
  • realm tries to load the whole file into memory
  • iPad Air 2 (2GB) crashes, on iPad Pro (4GB) everything works well

Issues i already read

The fix I tried

the writeCopyToURL:encryptionKey:error: compact hack

I have a Singleton Object which handles all storage actions to realm, which has a writeTransaction: method which handles beginWriteTransaction and commitWriteTransaction handling. All storage actions are coming through this method.

- (void)writeTransaction:(void (^)(void))block
{
    [self _ensureRealmThread:^{
        NSDate *startDate = [NSDate date];
        [[self _defaultRealm] beginWriteTransaction];
        block();

        NSError *commitWriteTransactionError = nil;
        [[self _defaultRealm] commitWriteTransaction:&commitWriteTransactionError];

        if (commitWriteTransactionError) {
            NSLog(@"commit error: %@", commitWriteTransactionError);
        }

        NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:startDate];
        if (time > 0.5) {
            NSLog(@"WARNING: Transaction duration > 0.5");
        }

        // i added these 5 lines to compact the database every 2000 requests 
        _writeTransactionIndex++;
        if (_writeTransactionIndex > 2000) {
            _writeTransactionIndex = 0;
            [self compactDatabase];
        }
    }];
}


- (void)compactDatabase
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                               NSUserDomainMask,
                                                               YES);
    NSString *documentsPath = searchPaths[0];

    NSString *defaultCompactPath = [documentsPath stringByAppendingPathComponent:@"defaultCompact.realm"];
    NSURL *defaultCompactURL = [NSURL fileURLWithPath:defaultCompactPath];

    // remove old
    if ([[NSFileManager defaultManager] fileExistsAtPath:[defaultCompactURL path]]) {
        [[NSFileManager defaultManager] removeItemAtURL:defaultCompactURL
                                                  error:nil];
    }

    NSError *writeError = nil;
    [[self _defaultRealm] writeCopyToURL:defaultCompactURL
                           encryptionKey:nil
                                   error:&writeError];
    if (!writeError) {
        [[NSFileManager defaultManager] replaceItemAtURL:[self _defaultRealm].configuration.fileURL
                                           withItemAtURL:defaultCompactURL
                                          backupItemName:nil
                                                 options:NSFileManagerItemReplacementUsingNewMetadataOnly
                                        resultingItemURL:nil
                                                   error:nil];
    }
}

The fix works in the storage!! The file shrinks from 2,5GB to 500KB. But i still got the issue that realm wants to allocate too much memory:

commit error: Error Domain=io.realm Code=9 "mmap() failed: Cannot allocate memory size: 268435456 offset: 2952790016" UserInfo={NSLocalizedDescription=mmap() failed: Cannot allocate memory size: 268435456 offset: 2952790016, Error Code=9}

Is there somebody with an idea to fix this ? :-)
If i missed some necessary information please leave a comment.. i'm in this issue for several days and my brain is like 💥

Haematoxylin answered 1/3, 2017 at 9:23 Comment(1)
Can you file a ticket on our GitHub repo at github.com/realm/realm-cocoa? Also, are you opening the Realm before you compact it using compactDatabase?Ping
M
2

We had this error. Below is our workaround until Realm fixes the issue:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_SERIAL, 0), ^{

    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    config.schemaVersion = [schema intValue];
    config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
        // totalBytes refers to the size of the file on disk in bytes (data + free space)
        // usedBytes refers to the number of bytes used by data in the file

        // Compact if the file is over 50MB in size and less than 50% 'used'
        NSUInteger oneHundredMB = 50 * 1024 * 1024;
        return (totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5;
    };

    __block BOOL deleteRealm = false;

    //schema has changed. Set delete flag.
    config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
        if (oldSchemaVersion < [schema intValue]) {
            deleteRealm = TRUE;
        }
    };

    [RLMRealmConfiguration setDefaultConfiguration:config];


    //*** Memory Allocation bug 'fix/hack' ***
    // Put realm init in try block. If it fails, blow it away in catch block. Then the following realm init will work.
    // This fix eliminated the error for us, with no effect on perfomance.
    @try
    {
        [RLMRealm defaultRealm];
    }
    @catch(...) {
        NSURL *rurl = [RLMRealmConfiguration defaultConfiguration].fileURL;
        // blow the database clean
        NSError *error = nil;
        [[NSFileManager defaultManager] removeItemAtURL:rurl error:&error];
        if(error) {
            NSLog(@"error %@ removing realm db", error);
        } else {
            NSLog(@"removed realm db successfully!");
        }
    }

    [RLMRealm defaultRealm];

    if (deleteRealm){
        [[RLMRealm defaultRealm] beginWriteTransaction];
        [[RLMRealm defaultRealm] deleteAllObjects];
        [[RLMRealm defaultRealm] commitWriteTransaction];
        deleteRealm = FALSE;
    }
});
Metzger answered 5/8, 2018 at 16:1 Comment(3)
Is your workaround simply deleting the realm file (loss of data), but prevent the app from crashing?Lycanthropy
Good point. I will add to the answer. The is not an issue for us as we are synchronized from the cloud whenever database changes occur. If it doesn't exist in realm, then it gets created immediately.Metzger
it would make more sense to create a new realm file rather than destroy the current one (?)Atmospherics

© 2022 - 2024 — McMap. All rights reserved.