Fastest Way To Query Multiple Objects by Primary Key?


I’m modeling a filesystem and I have 1,000,000 of these in a Realm:

class FileItem: Object
    @Persisted(primaryKey: true) var path: String

Retrieving any single FileItem by the Primary Key is insanely fast:

let theItem: FileItem = someRealm.object(ofType: FileItem.self, forPrimaryKey: "some/path")

The Issue

Fetching a handful (say, 10) items is most quickly accomplished like this:

let pathsToFetch: [String] = ...
var items: [FileItem] = []

for path: String in pathsToFetch
   if let item = someRealm.object(ofType: FileItem.self, forPrimaryKey: path) {

But the disadvantage here is that I lose access to sorting the results with Realm’s SortDescriptor. And because there’s no straightforward way to convert from RealmSwift.SortDescriptor to NSSortDescriptor, that’s inconvenient.

I know I can make pathsToFetch a Set and query with contains, but that degrades performance, since I’m now evaluating every FileItem in the database. Is there a way to retrieve and sort a handful of objects by Primary Key that is still close to the performance of object(ofType:forPrimaryKey:)?

I would typically build a query string and use that instead.

let queryString = ""
for path: String in pathsToFetch
  if queryString = "" {
    queryString = "_id == \"\(path)\""
  } else {
    queryString += " || _id == \"\(path)\""

Then I would use that to filter for the objets:

const items = someRealm.objects(FileItem.self).filtered(queryString)

This should give you a Results object that you can still do all the Realm stuff you want.

@Kurt_Libby1 Thanks! How does the performance of that query scale with the number of FileItem objects in the database, though?

In a way, I guess what I’m asking is what’s the difference in performance between fetching an item by Primarykey, versus fetching an item by an indexed string property using .objects().filtered(), assuming that we’re testing for an exact string match (==) in both cases.

I just don’t have 10M files at my disposal to try it!

I’m not sure. I also don’t have anywhere north of 10K objects to test it on.

But in my experience, the reason I’m creating these types of queryString filters is to present some data to a user. The chances that the user actually needs a million objects is very low.

As long as this queryString is the end of the line in any sort of manual pipeline/aggregation, I think you can take advantage of all of the quick Realm functions and then apply it.

Where are you getting the let pathsToFetch: [String] = [...] from? If these are already in Realm like in a List or something, you don’t really need to do this, you can just get the list, but if they are from another source, and you can just send the string, my experience is that the filtering is very quick.