Optimizing your programs

MongoKitten is a high performance client. It stores Documents and other BSON data in binary format and deserializes on-the-fly. It has many tricks under the hood to increase your performance and productivity.

Document Performance

Documents come in two forms. Dictionary and Array. Dictionary documents have a key-value store where the key is a string and the value of a supported primitive type.

Arrays are a Dictionary document that only contains numerical incremental keys. For this reason they are technically identical.

Iterating over Document pairs can be easy

// Dictionary
for (key, value) in document {
	...
}

// Array
for (_, value) in document {
	...
}

This works perfectly fine. And however both work, only the first is efficient. Because we’re deserializing on-the-fly the overhead of BSON is minimal. This also allows us to ignore deserialization for the key in Array documents. So Document has a property called arrayValue which only returns the values as an array.

// Array
for value in document.arrayValue {
	...
}

The above will be significantly faster. In one function in MongoKitten it lowered the CPU cost of the operation by 70%.

Finding/fetching data

MongoKitten makes use of MongoDB cursors under the hood. Simply a pointer to the server’s data. We can use this to incrementally fetch more data as we need it. But we can also drain the cursor quickly and request all data and load it to our memory. Cursors are a special citizen in MongoKitten.

Performance

Due to the nature of Cursor we’ve implemented strategies. They can be applied on the server-wide scale using database.server.cursorStrategy or server.cursorStrategy. They can also be changed for individual cursors.

Strategies currently come in three flavours:

  • .lazy, low performance, low memory usage
  • .intelligent(X), medium performance, medium memory usage
  • .aggressive, high performance, high memory usage

Intelligent can be configured using an integer. The integer defines the amount of pre-loaded chunks. Each chunk being the provided batchSize or chunkSize, 100 by default.

Transformation

Cursors and CollectionSlices can be transformed like you would with array.flatMap. This will lazily transform all users coming through the cursor/iterator and will thus be very efficient.

class User {
  ...

  init?(document: Document) throws {
    ...
	}
}

let maleUserDocumentSlice: CollectionSlice<Document> = try db["users"].find("gender" == "male")

let maleUserSlice: CollectionSlice<User>: try maleUserDocumentSlice.flatMap(User.init)

for maleUser in maleUserSlice {
	...
}