Skip to content

Commit ff10d69

Browse files
authored
Merge pull request #492 from powersync-ja/cache-temp-storage
Cache size & temp storage
2 parents 7de287f + 56185bb commit ff10d69

File tree

12 files changed

+107
-39
lines changed

12 files changed

+107
-39
lines changed

.changeset/lovely-impalas-do.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': minor
3+
---
4+
5+
Add cacheSizeKb option, defaulting to 50MB.

.changeset/slow-spiders-smash.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/diagnostics-app': minor
3+
---
4+
5+
Switch diagnostics app to OPFS.

.changeset/spotty-students-serve.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/op-sqlite': minor
3+
---
4+
5+
Default to using memory for temp store, and 50MB cache size.

packages/powersync-op-sqlite/README.md

+4-23
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ To load additional SQLite extensions include the `extensions` option in `sqliteO
7474

7575
```js
7676
sqliteOptions: {
77-
extensions: [
78-
{ path: libPath, entryPoint: 'sqlite3_powersync_init' }
79-
]
77+
extensions: [{ path: libPath, entryPoint: 'sqlite3_powersync_init' }];
8078
}
8179
```
8280

@@ -87,38 +85,21 @@ Example usage:
8785
```ts
8886
import { getDylibPath } from '@op-engineering/op-sqlite';
8987

90-
let libPath: string
88+
let libPath: string;
9189
if (Platform.OS === 'ios') {
92-
libPath = getDylibPath('co.powersync.sqlitecore', 'powersync-sqlite-core')
90+
libPath = getDylibPath('co.powersync.sqlitecore', 'powersync-sqlite-core');
9391
} else {
9492
libPath = 'libpowersync';
9593
}
9694

9795
const factory = new OPSqliteOpenFactory({
9896
dbFilename: 'sqlite.db',
9997
sqliteOptions: {
100-
extensions: [
101-
{ path: libPath, entryPoint: 'sqlite3_powersync_init' }
102-
]
98+
extensions: [{ path: libPath, entryPoint: 'sqlite3_powersync_init' }]
10399
}
104100
});
105101
```
106102

107-
## Using the Memory Temporary Store
108-
109-
For some targets like Android 12/API 31, syncing of large datasets may cause disk IO errors due to the default temporary store option (file) used.
110-
To resolve this you can use the `memory` option, by adding the following configuration option to your application's `package.json`
111-
112-
```json
113-
{
114-
// your normal package.json
115-
// ...
116-
"op-sqlite": {
117-
"sqliteFlags": "-DSQLITE_TEMP_STORE=2"
118-
}
119-
}
120-
```
121-
122103
## Native Projects
123104

124105
This package uses native libraries. Create native Android and iOS projects (if not created already) by running:

packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,28 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
4444
}
4545

4646
protected async init() {
47-
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous } = this.options.sqliteOptions!;
47+
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous, cacheSizeKb, temporaryStorage } =
48+
this.options.sqliteOptions!;
4849
const dbFilename = this.options.name;
4950

5051
this.writeConnection = await this.openConnection(dbFilename);
5152

52-
const statements: string[] = [
53+
const baseStatements = [
5354
`PRAGMA busy_timeout = ${lockTimeoutMs}`,
55+
`PRAGMA cache_size = -${cacheSizeKb}`,
56+
`PRAGMA temp_store = ${temporaryStorage}`
57+
];
58+
59+
const writeConnectionStatements = [
60+
...baseStatements,
5461
`PRAGMA journal_mode = ${journalMode}`,
5562
`PRAGMA journal_size_limit = ${journalSizeLimit}`,
5663
`PRAGMA synchronous = ${synchronous}`
5764
];
5865

59-
for (const statement of statements) {
66+
const readConnectionStatements = [...baseStatements, 'PRAGMA query_only = true'];
67+
68+
for (const statement of writeConnectionStatements) {
6069
for (let tries = 0; tries < 30; tries++) {
6170
try {
6271
await this.writeConnection!.execute(statement);
@@ -79,7 +88,9 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
7988
this.readConnections = [];
8089
for (let i = 0; i < READ_CONNECTIONS; i++) {
8190
const conn = await this.openConnection(dbFilename);
82-
await conn.execute('PRAGMA query_only = true');
91+
for (let statement of readConnectionStatements) {
92+
await conn.execute(statement);
93+
}
8394
this.readConnections.push({ busy: false, connection: conn });
8495
}
8596
}

packages/powersync-op-sqlite/src/db/SqliteOptions.ts

+22
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ export interface SqliteOptions {
3030
*/
3131
encryptionKey?: string | null;
3232

33+
/**
34+
* Where to store SQLite temporary files. Defaults to 'MEMORY'.
35+
* Setting this to `FILESYSTEM` can cause issues with larger queries or datasets.
36+
*
37+
* For details, see: https://www.sqlite.org/pragma.html#pragma_temp_store
38+
*/
39+
temporaryStorage?: TemporaryStorageOption;
40+
41+
/**
42+
* Maximum SQLite cache size. Defaults to 50MB.
43+
*
44+
* For details, see: https://www.sqlite.org/pragma.html#pragma_cache_size
45+
*/
46+
cacheSizeKb?: number;
47+
3348
/**
3449
* Load extensions using the path and entryPoint.
3550
* More info can be found here https://op-engineering.github.io/op-sqlite/docs/api#loading-extensions.
@@ -40,6 +55,11 @@ export interface SqliteOptions {
4055
}>;
4156
}
4257

58+
export enum TemporaryStorageOption {
59+
MEMORY = 'memory',
60+
FILESYSTEM = 'file'
61+
}
62+
4363
// SQLite journal mode. Set on the primary connection.
4464
// This library is written with WAL mode in mind - other modes may cause
4565
// unexpected locking behavior.
@@ -65,6 +85,8 @@ export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
6585
journalMode: SqliteJournalMode.wal,
6686
synchronous: SqliteSynchronous.normal,
6787
journalSizeLimit: 6 * 1024 * 1024,
88+
cacheSizeKb: 50 * 1024,
89+
temporaryStorage: TemporaryStorageOption.MEMORY,
6890
lockTimeoutMs: 30000,
6991
encryptionKey: null,
7092
extensions: []

packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ export class WASqliteConnection
235235
await this.openDB();
236236
this.registerBroadcastListeners();
237237
await this.executeSingleStatement(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
238+
await this.executeSingleStatement(`PRAGMA cache_size = -${this.options.cacheSizeKb};`);
238239
await this.executeEncryptionPragma();
239240

240241
this.sqliteAPI.update_hook(this.dbP, (updateType: number, dbName: string | null, tableName: string | null) => {

packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import * as Comlink from 'comlink';
33
import { resolveWebPowerSyncFlags } from '../../PowerSyncDatabase';
44
import { OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
55
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
6-
import { ResolvedWebSQLOpenOptions, TemporaryStorageOption, WebSQLFlags } from '../web-sql-flags';
6+
import {
7+
DEFAULT_CACHE_SIZE_KB,
8+
ResolvedWebSQLOpenOptions,
9+
TemporaryStorageOption,
10+
WebSQLFlags
11+
} from '../web-sql-flags';
712
import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
813
import { WASQLiteVFS } from './WASQLiteConnection';
914
import { WASQLiteOpenFactory } from './WASQLiteOpenFactory';
@@ -27,6 +32,7 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
2732

2833
vfs?: WASQLiteVFS;
2934
temporaryStorage?: TemporaryStorageOption;
35+
cacheSizeKb?: number;
3036

3137
/**
3238
* Encryption key for the database.
@@ -43,7 +49,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
4349
super({
4450
name: options.dbFilename,
4551
openConnection: async () => {
46-
const { workerPort, temporaryStorage } = options;
52+
const { workerPort, temporaryStorage, cacheSizeKb } = options;
4753
if (workerPort) {
4854
const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
4955
return new WorkerWrappedAsyncDatabaseConnection({
@@ -52,6 +58,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
5258
baseConnection: await remote({
5359
...options,
5460
temporaryStorage: temporaryStorage ?? TemporaryStorageOption.MEMORY,
61+
cacheSizeKb: cacheSizeKb ?? DEFAULT_CACHE_SIZE_KB,
5562
flags: resolveWebPowerSyncFlags(options.flags),
5663
encryptionKey: options.encryptionKey
5764
})
@@ -63,6 +70,7 @@ export class WASQLiteDBAdapter extends LockedAsyncDatabaseAdapter {
6370
debugMode: options.debugMode,
6471
flags: options.flags,
6572
temporaryStorage,
73+
cacheSizeKb,
6674
logger: options.logger,
6775
vfs: options.vfs,
6876
encryptionKey: options.encryptionKey,

packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../
44
import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory';
55
import { AsyncDatabaseConnection, OpenAsyncDatabaseConnection } from '../AsyncDatabaseConnection';
66
import { LockedAsyncDatabaseAdapter } from '../LockedAsyncDatabaseAdapter';
7-
import { ResolvedWebSQLOpenOptions, TemporaryStorageOption, WebSQLOpenFactoryOptions } from '../web-sql-flags';
7+
import {
8+
DEFAULT_CACHE_SIZE_KB,
9+
ResolvedWebSQLOpenOptions,
10+
TemporaryStorageOption,
11+
WebSQLOpenFactoryOptions
12+
} from '../web-sql-flags';
813
import { WorkerWrappedAsyncDatabaseConnection } from '../WorkerWrappedAsyncDatabaseConnection';
914
import { WASqliteConnection, WASQLiteVFS } from './WASQLiteConnection';
1015

@@ -44,6 +49,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
4449
const {
4550
vfs = WASQLiteVFS.IDBBatchAtomicVFS,
4651
temporaryStorage = TemporaryStorageOption.MEMORY,
52+
cacheSizeKb = DEFAULT_CACHE_SIZE_KB,
4753
encryptionKey
4854
} = this.waOptions;
4955

@@ -60,6 +66,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
6066
optionsDbWorker({
6167
...this.options,
6268
temporaryStorage,
69+
cacheSizeKb,
6370
flags: this.resolvedFlags,
6471
encryptionKey
6572
})
@@ -74,6 +81,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
7481
dbFilename: this.options.dbFilename,
7582
vfs,
7683
temporaryStorage,
84+
cacheSizeKb,
7785
flags: this.resolvedFlags,
7886
encryptionKey: encryptionKey
7987
}),
@@ -94,6 +102,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
94102
debugMode: this.options.debugMode,
95103
vfs,
96104
temporaryStorage,
105+
cacheSizeKb,
97106
flags: this.resolvedFlags,
98107
encryptionKey: encryptionKey
99108
});

packages/web/src/db/adapters/web-sql-flags.ts

+14
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export interface ResolvedWebSQLOpenOptions extends SQLOpenOptions {
4747
*/
4848
temporaryStorage: TemporaryStorageOption;
4949

50+
cacheSizeKb: number;
51+
5052
/**
5153
* Encryption key for the database.
5254
* If set, the database will be encrypted using ChaCha20.
@@ -59,6 +61,8 @@ export enum TemporaryStorageOption {
5961
FILESYSTEM = 'file'
6062
}
6163

64+
export const DEFAULT_CACHE_SIZE_KB = 50 * 1024;
65+
6266
/**
6367
* Options for opening a Web SQL connection
6468
*/
@@ -74,12 +78,22 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions {
7478
worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker);
7579

7680
logger?: ILogger;
81+
7782
/**
7883
* Where to store SQLite temporary files. Defaults to 'MEMORY'.
7984
* Setting this to `FILESYSTEM` can cause issues with larger queries or datasets.
85+
*
86+
* For details, see: https://www.sqlite.org/pragma.html#pragma_temp_store
8087
*/
8188
temporaryStorage?: TemporaryStorageOption;
8289

90+
/**
91+
* Maximum SQLite cache size. Defaults to 50MB.
92+
*
93+
* For details, see: https://www.sqlite.org/pragma.html#pragma_cache_size
94+
*/
95+
cacheSizeKb?: number;
96+
8397
/**
8498
* Encryption key for the database.
8599
* If set, the database will be encrypted using ChaCha20.

packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
SharedSyncClientEvent,
77
SharedSyncImplementation
88
} from '../../worker/sync/SharedSyncImplementation';
9-
import { resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
9+
import { DEFAULT_CACHE_SIZE_KB, resolveWebSQLFlags, TemporaryStorageOption } from '../adapters/web-sql-flags';
1010
import { WebDBAdapter } from '../adapters/WebDBAdapter';
1111
import {
1212
WebStreamingSyncImplementation,
@@ -106,10 +106,10 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
106106
* This worker will manage all syncing operations remotely.
107107
*/
108108
const resolvedWorkerOptions = {
109-
...options,
110109
dbFilename: this.options.identifier!,
111-
// TODO
112110
temporaryStorage: TemporaryStorageOption.MEMORY,
111+
cacheSizeKb: DEFAULT_CACHE_SIZE_KB,
112+
...options,
113113
flags: resolveWebSQLFlags(options.flags)
114114
};
115115

tools/diagnostics-app/src/library/powersync/ConnectionManager.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {
44
PowerSyncDatabase,
55
WebRemote,
66
WebStreamingSyncImplementation,
7-
WebStreamingSyncImplementationOptions
7+
WebStreamingSyncImplementationOptions,
8+
WASQLiteOpenFactory,
9+
TemporaryStorageOption,
10+
WASQLiteVFS
811
} from '@powersync/web';
912
import Logger from 'js-logger';
1013
import { DynamicSchemaManager } from './DynamicSchemaManager';
@@ -25,14 +28,18 @@ export const getParams = () => {
2528

2629
export const schemaManager = new DynamicSchemaManager();
2730

31+
const openFactory = new WASQLiteOpenFactory({
32+
dbFilename: 'diagnostics.db',
33+
debugMode: true,
34+
cacheSizeKb: 500 * 1024,
35+
temporaryStorage: TemporaryStorageOption.MEMORY,
36+
vfs: WASQLiteVFS.OPFSCoopSyncVFS
37+
});
38+
2839
export const db = new PowerSyncDatabase({
29-
database: {
30-
dbFilename: 'example.db',
31-
debugMode: true
32-
},
40+
database: openFactory,
3341
schema: schemaManager.buildSchema()
3442
});
35-
db.execute('PRAGMA cache_size=-500000');
3643

3744
export const connector = new TokenConnector();
3845

0 commit comments

Comments
 (0)