Concurrent reads

We are using the CouchbaseLiteSwift SDK and using the database as a sync engine to drive the application.

Apologies if we have misunderstood or misinterpreted the implementation.
Really the question is do I have the correct understanding of the implementation.

The perceived issue: Concurrent reads and parsing from different query results seems not possible using the ResultSet object returned by queries. We checked the implementation of the CBLQueryResultSet here and it appears that the database acquires a lock (via @synchronized) on each access to a Result object, and as such, it contends with every other operation to the database.

Due to the lock limitation on every database operation, our application essentially becomes a single threaded application. This presents a challenge where we must read large number of documents from many different queries.

If one query returns 10K files, and the second returns 10K as well. We must first wait for the the result from 1st query to completely finish parsing, and only then the second 10K can begin to process.

Since the backing store for couchbase is SQLite, from my understanding:
SQLite supports an unlimited number of simultaneous readers, but it will only allow one writer at any instant in time.

—However, it appears this is not the case ?

Are there any recommendations or suggestions for allowing concurrent reading of Result objects from different queries ?

Thank you!

2 Likes

SQLite supports simultaneous readers, but on separate database connections. Each database connection is single-threaded.

This applies to CBL too. If you want concurrent access, open a separate CBLDatabase instance on each thread.

2 Likes

Hi Jens! Thank you for the suggestion, much appreciated.

If you want concurrent access, open a separate CBLDatabase instance on each thread.

The only public API I see in the swift SDK, to get access to a database (and therefore a connection) is this:

public init(name: String, config: CouchbaseLiteSwift.DatabaseConfiguration = DatabaseConfiguration())

So I assume you mean creating two instances of the Database object like so?

var options = DatabaseConfiguration()
options.directory = path
let mainSyncDB = try Database(name: databaseName, config: config)
let backgroundDB = try Database(name: databaseName, config: config)

We would like to experiment/use the approach to:

  1. use mainSyncDB to drive the UI and sync from backend work
  2. use backgroundDB to drive analytics and clean up work

Do you foresee any issues regarding having multiple instances of the same database, but with different connections?
Thanks again! :pray:

var options = DatabaseConfiguration()
options.directory = path
let mainSyncDB = try Database(name: databaseName, config: config)
let backgroundDB = try Database(name: databaseName, config: config)

Yes, that is correct.

Do you foresee any issues regarding having multiple instances of the same database, but with different connections?

This is a general approach to use CBL databases concurrently.

1 Like

Thank you Pasin for the guidance!
A related question I have to this is:

In the context of only using one instance of the database:

I see that acquiring the defaultCollection is also a locking operation each time we access it via: database.defaultCollection

One of my colleague suggested that, what if we simply hold onto the defaultCollection reference once at initialization of the database, so we could work around this limitation, and prevent some lock contentions?

let db = Database()
self.collection = db.defaultCollection

Do you see any downsides to this approach ? Or was it not intended to be stored as such, and we should always access it via the database.defaultCollection ?

It doesn’t matter what you do, a single Database instance is single-threaded and will only perform one operation at a time.

1 Like