Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lib/data/db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ class LocalDb {
}
}

/// The most the -wal keeps once a checkpoint has emptied it. See [_open].
static const int walSizeLimitBytes = 32 << 20;

static Future<Database> _open() async {
final dir = await getDatabasesPath();
final path = p.join(dir, dbName);
Expand Down Expand Up @@ -556,6 +559,18 @@ class LocalDb {
} catch (_) {
/* keep the default journal — this is a perf tweak, not a requirement */
}
// DISK. A checkpoint copies the -wal into the database and the next
// write starts over at its head, but the FILE keeps its high-water
// mark. One big transaction (a VACUUM writes every page through it, a
// backlog drain) left a -wal as large as the database for good. With
// a limit the file is truncated back each time it restarts, on this
// connection and on an existing install's oversized -wal alike.
// Per-connection, and it returns a row: rawQuery, never fatal.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
try {
await db.rawQuery('PRAGMA journal_size_limit=$walSizeLimitBytes');
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
} catch (_) {
/* non-fatal */
}
try {
await db.execute('PRAGMA synchronous=NORMAL');
// ~40 MB page cache (negative = KiB) so hot b-tree pages stay resident
Expand Down Expand Up @@ -13169,6 +13184,14 @@ class LocalDb {
// ("Query reaches storage outside the coach views") for the rest of the
// process. The invariant belongs to whoever moves the pages.
await CoachDb.close();
// The VACUUM wrote the whole database through the -wal. Copy it back and
// truncate it now, rather than at the next write; the size limit set on
// open covers it if a reader holds this checkpoint back.
try {
await db.rawQuery('PRAGMA wal_checkpoint(TRUNCATE)');
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
} catch (_) {
/* non-fatal: the bytes are reclaimed, the -wal shrinks on a later write */
}
return free;
}

Expand Down
30 changes: 30 additions & 0 deletions test/db_storage_hygiene_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
// pruned. They are keyed (day_id, algo_version), so every kAlgoVersion
// bump wrote a whole new generation beside the old one and nothing
// removed it.
// 3. A VACUUM does not leave the -wal the size of the whole database: in WAL
// mode it writes every page through the -wal, and without a size limit
// the file is reused but never shrunk, doubling what the app holds.
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
Expand Down Expand Up @@ -153,6 +158,31 @@ void main() {
)).map((r) => r['algo_version'] as int).toList();
expect(recent, [49, 50]);
});

test('the -wal is size-limited, so a VACUUM does not leave it the size of '
'the database', () async {
final db = await LocalDb.instance;
final limit =
(await db.rawQuery('PRAGMA journal_size_limit')).first.values.first;
expect(limit, LocalDb.walSizeLimitBytes);

// 48 MB to keep and 16 MB to free: the VACUUM then writes ~48 MB of pages
// through the -wal, more than the limit.
await db.execute('CREATE TABLE wal_probe (b BLOB)');
for (var i = 0; i < 64; i++) {
await db.rawInsert('INSERT INTO wal_probe VALUES (randomblob(1048576))');
}
await db.delete('wal_probe', where: 'rowid > 48');
expect(await LocalDb.vacuumIfBloated(minFreeBytes: 1), greaterThan(0));

final wal = File(
p.join(await databaseFactory.getDatabasesPath(), '${LocalDb.dbName}-wal'),
);
final walBytes = wal.existsSync() ? wal.lengthSync() : 0;
expect(walBytes, lessThanOrEqualTo(LocalDb.walSizeLimitBytes),
reason: 'the -wal kept ${walBytes >> 20} MB after the VACUUM');
await db.execute('DROP TABLE wal_probe');
});
}

Future<List<String>> _rrIndexes(Database db) async => (await db.rawQuery(
Expand Down