Example: Turbo Boost Mode

Some developers are desperate to wring every last bit of performance out of their database, even to the point of risking data loss or corruption. Some PRAGMA statements tie into performance this way.

For example, normally, many times when SQLite writes data to disk, it will use fsync() or the equivalent to block until all of the bytes are confirmed to be written. This is important in operating systems with write-caching filesystems, as otherwise the data that you think that you wrote might actually just be in a buffer waiting to be written in the future. Android, when using the ext4 filesystem, is one such OS. However, PRAGMA synchronous = OFF tells SQLite to skip those fsync() calls. This speeds up I/O, with increased risk of the database becoming corrupted if there is a major system problem while that I/O is going on. This is a transient PRAGMA, only affecting the current connection.

Even riskier is PRAGMA journal_mode = MEMORY. In effect, this says to keep the transaction log of the database in memory, rather than writing it to disk. As the documentation states, “if the application using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set, then the database file will very likely go corrupt”. But, some people would consider performance gains as being a valid trade-off here. This is a persistent setting, and so it only needs to be applied once.

The approach for both of these cases is to use a RoomDatabase.Callback, as seen in the PragmaTest sample module from the book’s primary sample project. RoomDatabase.Callback was introduced in the chapter on the support database API.

This is a tests-only module, one that tests importing a bunch of city population data from a JSON file into a Room-powered database called CityDatabase.

The newInstance() method that we use to create an instance of our CityDatabase uses a RoomDatabase.Builder as normal. However, based on a boolean parameter, it may also use addCallback() to add a RoomDatabase.Callback to the builder:

package com.commonsware.room.pragmatest

import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase

@Database(entities = [City::class], version = 1)
abstract class CityDatabase : RoomDatabase() {
  abstract fun cityStore(): City.Store

  companion object {
    @VisibleForTesting
    const val DB_NAME = "un.db"

    fun newInstance(ctxt: Context, applyPragmas: Boolean): CityDatabase {
      val builder = Room.databaseBuilder(
        ctxt,
        CityDatabase::class.java,
        DB_NAME
      )

      if (applyPragmas) {
        builder.addCallback(object : Callback() {
          override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            db.query("PRAGMA journal_mode = MEMORY")
          }

          override fun onOpen(db: SupportSQLiteDatabase) {
            super.onOpen(db)
            db.query("PRAGMA synchronous = OFF")
          }
        })
      }

      return builder.build()
    }
  }
}

There are two methods that you can supply on a Callback implementation: onCreate() and onOpen(). As the names suggest, they are called when the database is created and opened, respectively. In each, you are handed a SupportSQLiteDatabase instance, which has an API reminiscent of the framework’s SQLiteDatabase. It has a query() method that works like rawQuery(), taking a simple SQL statement (that might return a result set) and executing it. Since PRAGMA might return a result set, we have to use query() instead of execSQL(). Here, we invoke our PRAGMA statements at the appropriate times.

And, in truth, there does seem to be a performance gain:

Scenario Use the PRAGMAs? Time (milliseconds)
Inserting 1,063 cities via individual insert() calls No 1,135
Inserting 1,063 cities via individual insert() calls Yes 960
Inserting 1,063 cities in a single insert() call No 393
Inserting 1,063 cities in a single insert() call Yes 252

(tests conducted on a Google Pixel 4)

Proper use of transactions — such as doing all of the inserts at once rather than one at a time — has a much bigger impact, though. Using these two PRAGMA statements is a bit like using a holodeck with the safeties off: you may have some casualties.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.