realmmongodb-realm

How quickly can Realm return sorted data?


Realm allows you to receive the results of a query in sorted order.

let realm = try! Realm()
let dogs = realm.objects(Dog.self)
let dogsSorted = dogs.sorted(byKeyPath: "name", ascending: false)

I ran this test to see how quickly realm returns sorted data

import Foundation
import RealmSwift

class TestModel: Object {
    @Persisted(indexed: true) var value: Int = 0
}

class RealmSortTest {
    let documentCount = 1000000
    var smallestValue: TestModel = TestModel()
    
    func writeData() {
        let realm = try! Realm()
        var documents: [TestModel] = []
        for _ in 0 ... documentCount {
            let newDoc = TestModel()
            newDoc.value = Int.random(in: 0 ... Int.max)
            documents.append(newDoc)
        }
        try! realm.write {
            realm.deleteAll()
            realm.add(documents)
        }
    }
    
    func readData() {
        let realm = try! Realm()
        let sortedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")
                
        let start = Date()
        
        self.smallestValue = sortedResults[0]
        
        let end = Date()
        let delta = end.timeIntervalSinceReferenceDate - start.timeIntervalSinceReferenceDate
        print("Time Taken: \(delta)")
    }
    
    func updateSmallestValue() {
        let realm = try! Realm()
        let sortedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")

        smallestValue = sortedResults[0]
        
        print("Originally loaded smallest value: \(smallestValue.value)")
        
        let newSmallestValue = TestModel()
        newSmallestValue.value = smallestValue.value - 1
        try! realm.write {
            realm.add(newSmallestValue)
        }
        
        print("Originally loaded smallest value after write: \(smallestValue.value)")
        
        let readStart = Date()
        smallestValue = sortedResults[0]
        let readEnd = Date()
        let readDelta = readEnd.timeIntervalSinceReferenceDate - readStart.timeIntervalSinceReferenceDate
        print("Reloaded smallest value \(smallestValue.value)")
        print("Time Taken to reload the smallest value: \(readDelta)")
    }
}

With documentCount = 100000, readData() output:

Time taken to load smallest value: 0.48901796340942383

and updateData() output:

Originally loaded smallest value: 2075613243102
Originally loaded smallest value after write: 2075613243102
Reloaded smallest value 2075613243101
Time taken to reload the smallest value: 0.4624580144882202

With documentCount = 1000000, readData() output:

Time taken to load smallest value: 4.807577967643738

and updateData() output:

Originally loaded smallest value: 4004790407680
Originally loaded smallest value after write: 4004790407680
Reloaded smallest value 4004790407679
Time taken to reload the smallest value: 5.2308430671691895

The time taken to retrieve the first document from a sorted result set is scaling with the number of documents stored in realm rather than the number of documents being retrieved. This indicates to me that realm is sorting all of the documents at query time rather than when the documents are being written. Is there a way to index your data so that you can quickly retrieve a small number of sorted documents?

Edit:

Following discussion in the comments, I updated the code to load only the smallest value from the sorted collection.

Edit 2

I updated the code to observe the results as suggested in the comments.

import Foundation
import RealmSwift

class TestModel: Object {
    @Persisted(indexed: true) var value: Int = 0
}

class RealmSortTest {
    let documentCount = 1000000
    var smallestValue: TestModel = TestModel()
    var storedResults: Results<TestModel> = (try! Realm()).objects(TestModel.self).sorted(byKeyPath: "value")
    var resultsToken: NotificationToken? = nil
    
    func writeData() {
        let realm = try! Realm()
        var documents: [TestModel] = []
        for _ in 0 ... documentCount {
            let newDoc = TestModel()
            newDoc.value = Int.random(in: 0 ... Int.max)
            documents.append(newDoc)
        }
        try! realm.write {
            realm.deleteAll()
            realm.add(documents)
        }
    }
    
    func observeData() {
        let realm = try! Realm()
        print("Loading Data")
        let startTime = Date()
        self.storedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")
        self.resultsToken = self.storedResults.observe { changes in
            let observationTime = Date().timeIntervalSince(startTime)
            print("Time to first observation: \(observationTime)")
            let firstTenElementsSlice = self.storedResults[0..<10]
            let elementsArray = Array(firstTenElementsSlice) //print this if you want to see the elements
            elementsArray.forEach { print($0.value) }
            let moreElapsed = Date().timeIntervalSince(startTime)
            print("Time to printed elements: \(moreElapsed)")
        }
    }
}

and I got the following output

Loading Data
Time to first observation: 5.252112984657288
3792614823099
56006949537408
Time to printed elements: 5.253015995025635

Reading the data with an observer did not reduce the time taken to read the data.


Solution

  • At this time it appears that Realm sorts data when it is accessed rather than when it is written, and there is not a way to have Realm sort data at write time. This means that accessing sorted data scales with the number of documents in the database rather than the number of documents being accessed.

    The actual time taken to access the data varies by use case and platform.