I'm using SQLite3, via php's extension module, to create a persistent object cache for WordPress. It's intended for use on modestly sized sites that don't have access to redis or memcached. Why use this? To give smaller WordPress installations access to similar performance-enhancing tech as the bigger ones. Many php configurations at el-cheapo hosting services have the SQLite 3 extension in them. The tag line is "the persistent object cache for the rest of us". I'm here asking for a review, specifically, of the code that configures SQLite3. My repo is here.
php has what we can call the "fifty first dates" problem. It starts up anew to handle every page view and other incoming http/s request (unlike, for example, nodejs which handles requests from within a single long-lived process). So, without a persistent object cache php must load all sorts of data from the MariaDB or MySQL database before it can handle any page view. That's a ponderous process. Reading that same stuff from a cache is generally faster.
Characteristics of this SQLite3 application.
- It contains no irreplaceable data; it's entirely populated from the database instance supporting WordPress.
- It's a key-value store with an expiration time on some, but not all, objects.
- The keys are case-sensitive ASCII TEXT strings. Most are shorter than 30 characters, but some run to 100 characters or so.
- The values are BLOBs. They can be any size from a few bytes up. I've seen some lengths of 600KiB or so, but most are a few hundred bytes or less.
- It's read-mostly.
- It's write-infrequenty. But when writing, it's necessary to write immediately. Writes operations can't be batched up into multi-row transactions.
- It's helpful to think of this cache as a way to memoize various operations.
- It needs to work with SQLite versions 3.7 and up.
So, that's the context. The code works. Here's the initialization code, running in a php class derived from SQLite3. I hope somebody who's familiar with making SQLite3 efficient can point out mistakes.
This initialization code runs for every page view. Remember, "fifty first dates". We can't make assumptions about the state of the system.
/* set some initial pragma stuff */
$this->exec( 'PRAGMA synchronous = OFF' );
$this->exec( "PRAGMA encoding = 'UTF-8'" );
$this->exec( 'PRAGMA case_sensitive_like = true' );
$this->exec( 'PRAGMA journal_mode = MEMORY' );
/* the table */
$this->exec( 'BEGIN;' );
/* does our table exist? */
$q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = 'cache';";
$r = $this->querySingle( $q );
if ( 0 === $r ) {
/* later versions of SQLite3 have clustered primary keys, "WITHOUT ROWID" */
if ( version_compare( $this->sqlite_get_version(), '3.8.2' ) < 0 ) {
$t = "CREATE TABLE IF NOT EXISTS cache (
name TEXT NOT NULL COLLATE BINARY,
value BLOB,
expires INT
);
CREATE UNIQUE INDEX IF NOT EXISTS name ON cache (name);
CREATE INDEX IF NOT EXISTS expires ON $tbl (expires);";
} else {
$t = "CREATE TABLE IF NOT EXISTS cache (
name TEXT NOT NULL PRIMARY KEY COLLATE BINARY,
value BLOB,
expires INT
) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS expires ON cache (expires);";
}
$this->exec( $t );
}
$this->exec( 'COMMIT;' );
My specific questions:
- Are my PRAGMA directives suitable for this application?
- In particular, does the
journal_mode = MEMORY
pragma make sense? - Did I miss any other useful PRAGMAs?
- Are the table definitions suitable?
- Can anything be done here to make this any faster or more time / space efficient?
- Is there anything dumb that I should fix?