Hi, it's Takuya here.
I've been developing a React Native app which syncs user data with CouchDB. The app is called Inkdrop.
I've published several posts on how to use PouchDB on React Native in the past:
This article is an update for using PouchDB on RN.
Fortunately, PouchDB is still maintained, and recently they've shipped v7.3.0 🎉. Much appreciated the community effort.
TL;DR
- 2~5x speed improvement!
- Less library tweaks & simpler setup
- Attachments are no longer supported
- How to use
Use a JSI-based SQLite driver
Oscar Franco created a great SQLite driver react-native-quick-sqlite, which provides a low-level API to execute SQL queries, fast bindings via JSI. JSI has a smaller overhead than the traditional RN bridge's.
So, this is much faster than react-native-sqlite-2, which I made to get PouchDB to work on RN before.
I wanted to use it in my project, so I made react-native-quick-websql, which is a thin wrapper to make quick-sqlite compatible with WebSQL. It works flawlessly!
I'll discuss how it improves the performance later.
Tackling the NULL character issue (again)
There is still an issue in React Native - It can't store string data with \u0000
.
To avoid the issue, the NULL characters are escaped/unescaped in react-native-sqlite-2 like so:
function escapeBlob(data: any) {
if (typeof data === 'string') {
return data
.replace(/\u0002/g, '\u0002\u0002')
.replace(/\u0001/g, '\u0001\u0002')
.replace(/\u0000/g, '\u0001\u0001')
} else {
return data
}
}
But it's not ideal because it would affect the performance.
PouchDB internally uses \u0000
for indexing documents with MapReduce in pouchdb-collate:
/ convert the given key to a string that would be appropriate
// for lexical sorting, e.g. within a database, where the
// sorting is the same given by the collate() function.
function toIndexableString(key) {
var zero = '\u0000';
key = normalizeKey(key);
return collationIndex(key) + SEP + indexify(key) + zero;
}
I didn't see why it should be \u0000
, so I've changed the zero marker to \u0003
:
And it turns out that it works!
Now the overhead of escaping NULL characters has been removed.
It's 2~5x faster 💨
I've tested it on my Google Pixel 5 and it turns out it's blazing fast. Here is a quick benchmark:
sqlite-2 | quick-sqlite + quick-websql | |
---|---|---|
Non-cached query for 38 docs | 4,510 ms | 984 ms |
Cached query for 38 docs | 275 ms | 75 ms |
Cached query for 1422 docs | 636 ms | 206 ms |
PouchDB performs MapReduce to index the documents based on design documents.
That's why the first query takes a long time.
But thanks to the small overheads, it's now about 4~5x faster.
I can't wait to release the new version of my app.
Attachments are no longer supported
In the past work, the main focus was to get PouchDB able to store attachments. But I no longer store them in PouchDB, so that requirement has been dropped.
But that allows you to simply use the official PouchDB packages except for pouchdb-collate
as I mentioned above.
How to use
Install
Install dev package:
yarn add -D babel-plugin-module-resolver
Install polyfill packages:
yarn add events react-native-get-random-values react-native-quick-base64
Install pouchdb packages:
yarn add pouchdb-core pouchdb-replication pouchdb-mapreduce pouchdb-adapter-http
Install the patched package:
yarn add @craftzdog/pouchdb-collate-react-native
Install storage adapter packages:
yarn add pouchdb-adapter-react-native-sqlite react-native-quick-sqlite react-native-quick-websql
- react-native-quick-sqlite - A fast bindings via JSI for SQLite
- react-native-quick-websql - WebSQL wrapper for quick-sqlite
- pouchdb-adapter-react-native-sqlite - PouchDB adapter for SQLite with those two modules
- Install CocoaPods:
npx pod-install
Configure
Make a shim.js
:
import {shim} from 'react-native-quick-base64'
shim()
// Avoid using node dependent modules
process.browser = true
then, require it at the beginning of your index.js
.
Edit your babel.config.js
like so:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
[
'module-resolver',
{
alias: {
'pouchdb-collate': '@craftzdog/pouchdb-collate-react-native',
},
},
],
],
}
Initialize
Create pouchdb.ts
like so:
import 'react-native-get-random-values'
import PouchDB from 'pouchdb-core'
import HttpPouch from 'pouchdb-adapter-http'
import replication from 'pouchdb-replication'
import mapreduce from 'pouchdb-mapreduce'
import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite'
import WebSQLite from 'react-native-quick-websql'
const SQLiteAdapter = SQLiteAdapterFactory(WebSQLite)
export default PouchDB.plugin(HttpPouch)
.plugin(replication)
.plugin(mapreduce)
.plugin(SQLiteAdapter)
Then, import and use it as usual:
import PouchDB from './pouchdb'
const db = new PouchDB('mydb.db', {
adapter: 'react-native-sqlite'
})
That's it!
Limitations & Debugging
As the libraries use JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible.
Instead, you should use Flipper.
Hope you find it useful and helpful.
- Follow me on Twitter & Instagram
- Inkdrop — Markdown note-taking app