CBL 2.1 indexing best practice and crash probelm

In my last Android app I’m using couchbase-lite-android:2.1.0 1 and I build a java (kotlin) class that manages all database’s read /write operations. In constructor, after getting db instance, I always (re)create an index:

 constructor(context : Context, deviceId: String) {
      this.dbConfig = DatabaseConfiguration(context)
      this.database = Database(DATA_DB_NAME, dbConfig)
      this.prefDb = Database(PREF_DB_NAME, dbConfig)
      this.database!!.createIndex(TYPE_INDEX_NAME, IndexBuilder.valueIndex(ValueIndexItem.property(PROPERTY_TYPE), ValueIndexItem.property(PROPERTY_END_TYPE), ValueIndexItem.property(PROPERTY_END_TYPE)))
    [...]
    }

Sometimes the createIndex crashes with the following stack:

Caused by com.couchbase.lite.CouchbaseLiteException: database is locked
       at com.couchbase.lite.CBLStatus.convertException(CBLStatus.java:51)
       at com.couchbase.lite.CBLStatus.convertException(CBLStatus.java:55)
       at com.couchbase.lite.AbstractDatabase.createIndex(AbstractDatabase.java:525)
       at com.couchbase.lite.Database.createIndex(Database.java:25)

I’ve 2 questions:

  1. What’s the best practice in creating indexes in CBL 2.1? Is it right to create index every time I open db or I can check index existence with getIndexes and create it only if missing?
  2. What might be causing the crash in (re)creating index? Might be the db is accessed in another thread (I implemented a WorkerManager’s Worker that launches sync in another thread) and this causes lock?

Thanks.
Paolo

  1. It’s safe to create the index every time you open the db. If you’re requesting an index identical to one that already exists by that name, nothing happens. (If it’s different than the existing one, the index is dropped and rebuilt.)
  2. You mean “exception”, not “crash”, right? I believe the “database locked” exception can happen if another thread is in a transaction (batch operation) that lasts long enough that the current thread times out waiting. Have you looked at what’s happening on other threads at the time of the exception? Is there a long pause before this happens?
  1. ok, in my scenario I always request an index with the same name.
  2. You’re right, I mean exception, CouchbaseLiteException is thrown. I don’t have any batch operation running, the only possible concurrent operation is a sequence of purge operations, something like this:
  query = QueryBuilder
                .select(...)
                .from(...)
                .where(...)
                        .and(...)
        try {
            val rs = query.execute()
            for (result in rs) {
                val docId = result.getString(ModelConstants.PROPERTY_ID)
                database.purge(database.getDocument(docId))
            }
        } catch (e: CouchbaseLiteException) {
            Log.e(TAG, e.message)
        }

Are the purge calls made on the same Database instance, or is it a different one? (I’m guessing it’s a different one, as it takes two database handles to get a locking error.)

As I asked above: Is there a long pause (on the thread creating the index) before this happens? You could log something before calling createIndex to capture the time, then compare with the time that the exception occurs.

Are the purge calls made on the same Database instance, or is it a different one? (I’m guessing it’s a different one, as it takes two database handles to get a locking error.)

Yes, I instantiate my data mediator class (that creates a DB instance to work on) once in my activity and every time a sync is needed and workermanager starts a work. Should be useful to close ed instance after sync competes and worker ends its job?

As I asked above: Is there a long pause (on the thread creating the index) before this happens? You could log something before calling createIndex to capture the time, then compare with the time that the exception occurs.

I’m checking this, just added some more logs to inspect exception timing .

Hi @jens - can you comment on the recommended usage of the API with respect to Database instances…more specifically:

  1. Is it recommended to have a singleton instance of the Database class w/in an app or is it ok to instantiate multiple instances of Database (given the same path/name) – to be clear, I’m not talking about different databases, I’m asking if is ok, or discouraged to create multiple instances of let db = Database(name: "mydb")
  2. Is the singleton instance of the Database class thread-safe?
  3. If you turn on replication with question 1 (i.e. spin up multiple instances of Database with replication) – does that introduce additional potential issues that we should be aware of?

Yes, you can create multiple Database instances on the same file. There’s a couple of megabytes overhead for each one (mostly for page caches.) The main advantage is better concurrency if you access the instances on separate threads. If instance A is in the middle of something on one thread (like a lengthy query), instance B can still be used.

The limitation on this concurrency is that only one connection can be writing to the database (in Database.saveDocument or Database.inBatch) at a time.

  1. No.
  2. Every instance is thread-safe.
  3. No. In fact, each replication internally creates a new database instance to do its work, but that’s transparent to you.
1 Like