Skip to content

Commit 276b2e1

Browse files
Make database access faster
Refactor some code around, add comments and introduce a dedicated db checkpointing thread with background priority to not block write transactions longer than strictly needed.
1 parent b84b2af commit 276b2e1

File tree

2 files changed

+64
-16
lines changed

2 files changed

+64
-16
lines changed

Monal/Classes/DataLayer.m

-5
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ -(id) init
9393

9494
//checking db version and upgrading if necessary
9595
DDLogInfo(@"Database version check");
96-
97-
//set wal mode (this setting is permanent): https://www.sqlite.org/pragma.html#pragma_journal_mode
98-
//this is a special case because it can not be done while in a transaction!!!
99-
[self.db enableWAL];
100-
[self.db executeNonQuery:@"PRAGMA secure_delete=on;"];
10196

10297
//needed for sqlite >= 3.26.0 (see https://sqlite.org/lang_altertable.html point 2)
10398
[self.db executeNonQuery:@"PRAGMA legacy_alter_table=on;"];

Monal/Classes/MLSQLite.m

+64-11
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,38 @@ @interface MLSQLite()
1919
@end
2020

2121
static NSMutableDictionary* currentTransactions;
22+
static dispatch_queue_t walCheckpointingQueue;
23+
static NSMutableArray* dbFilesList;
24+
25+
static int wal_hook(void* arg, sqlite3* database, const char* dbname, int numberOfPages)
26+
{
27+
if(numberOfPages < 1000)
28+
return SQLITE_OK;
29+
30+
DDLogVerbose(@"Triggering db checkpoint...");
31+
dispatch_async(walCheckpointingQueue, ^{
32+
DDLogDebug(@"Checkpointing database: %@", (__bridge NSString*)arg);
33+
int walLogFrames;
34+
int checkpointedFrames;
35+
if(sqlite3_wal_checkpoint_v2(database, dbname, SQLITE_CHECKPOINT_PASSIVE, &walLogFrames, &checkpointedFrames) == SQLITE_OK)
36+
DDLogDebug(@"Checkpointed %d frames with wal log size %d in database: %@", checkpointedFrames, walLogFrames, (__bridge NSString*)arg);
37+
else
38+
{
39+
int errcode = sqlite3_extended_errcode(database);
40+
NSString* error = [NSString stringWithUTF8String:sqlite3_errmsg(database)];
41+
DDLogError(@"Error checkpointing wal log: %d %@, database %@", errcode, error, (__bridge NSString*)arg);
42+
}
43+
});
44+
return SQLITE_OK;
45+
}
2246

2347
@implementation MLSQLite
2448

2549
+(void) initialize
2650
{
2751
currentTransactions = [NSMutableDictionary new];
52+
walCheckpointingQueue = dispatch_queue_create_with_target("im.monal.walCheckpointingQueue", DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
53+
dbFilesList = [NSMutableArray new];
2854

2955
if(sqlite3_config(SQLITE_CONFIG_MULTITHREAD) == SQLITE_OK)
3056
DDLogInfo(@"sqlite initialize: sqlite3 configured ok");
@@ -104,13 +130,25 @@ -(id) initWithFile:(NSString*) dbFile
104130
//some settings (e.g. truncate is faster than delete)
105131
//this uses the private api because we have no thread local instance added to the threadData dictionary yet and we don't use a transaction either (and public apis check both)
106132
//--> we must use the internal api because it does not call testThreadInstanceForQuery: testTransactionsForQuery:
107-
sqlite3_busy_timeout(self->_database, 2000); //set the busy time as early as possible to make sure the pragma states don't trigger a retry too often
133+
sqlite3_busy_timeout(self->_database, 1000); //set the busy time as early as possible to make sure the pragma states don't trigger a retry too often
134+
135+
//set wal mode (this setting is permanent): https://www.sqlite.org/pragma.html#pragma_journal_mode
136+
//this is a special case because it can not be done while in a transaction!!!
137+
[self enableWAL];
138+
139+
//some settings for faster sqlite, see https://hg.prosody.im/trunk/file/df32fff0963d/plugins/mod_storage_sql.lua#l943
140+
//synchronous NORMALE versus OFF don't have any differences in WAL mode, see: https://sqlite.org/pragma.html#pragma_synchronous
141+
while([self executeNonQuery:@"PRAGMA secure_delete=FAST;" andArguments:@[] withException:NO] != YES)
142+
DDLogError(@"Database locked, while calling 'PRAGMA secure_delete=FAST;', retrying...");
108143
while([self executeNonQuery:@"PRAGMA synchronous=NORMAL;" andArguments:@[] withException:NO] != YES)
109144
DDLogError(@"Database locked, while calling 'PRAGMA synchronous=NORMAL;', retrying...");
110145
while([self executeNonQuery:@"PRAGMA truncate;" andArguments:@[] withException:NO] != YES)
111146
DDLogError(@"Database locked, while calling 'PRAGMA truncate;', retrying...");
147+
148+
//this is needed because we use foreign keys to cascade deletes
112149
while([self executeNonQuery:@"PRAGMA foreign_keys=on;" andArguments:@[] withException:NO] != YES)
113150
DDLogError(@"Database locked, while calling 'PRAGMA foreign_keys=on;', retrying...");
151+
114152
//this seems to provide *slightly* better security
115153
//see https://sqlite.org/pragma.html#pragma_trusted_schema
116154
while([self executeNonQuery:@"PRAGMA trusted_schema = off;" andArguments:@[] withException:NO] != YES)
@@ -639,17 +677,32 @@ -(void) enableWAL
639677
MLAssert([threadData[@"_sqliteTransactionsRunning"][_dbFile] intValue] == 0, @"Could not enable wal, inside transaction!", (@{
640678
@"threadDictionary": threadData
641679
}));
680+
642681
NSString* mode = [self internalExecuteScalar:@"PRAGMA journal_mode;" andArguments:@[]];
643-
if([mode isEqualToString:@"wal"])
644-
return;
645-
mode = [self internalExecuteScalar:@"PRAGMA journal_mode=WAL;" andArguments:@[]];
646-
if([mode isEqualToString:@"wal"])
647-
DDLogWarn(@"Transaction mode set to WAL");
648-
else
649-
@throw [NSException exceptionWithName:@"SQLite3Exception" reason:@"Failed to enable sqlite WAL mode" userInfo:@{
650-
@"file": _dbFile,
651-
@"mode": mode
652-
}];
682+
if(![mode isEqualToString:@"wal"])
683+
{
684+
mode = [self internalExecuteScalar:@"PRAGMA journal_mode=WAL;" andArguments:@[]];
685+
if([mode isEqualToString:@"wal"])
686+
DDLogWarn(@"Transaction mode set to WAL");
687+
else
688+
@throw [NSException exceptionWithName:@"SQLite3Exception" reason:@"Failed to enable sqlite WAL mode" userInfo:@{
689+
@"file": _dbFile,
690+
@"mode": mode
691+
}];
692+
}
693+
694+
@synchronized(dbFilesList) {
695+
//we collect all db names ever opened, but store every name only once to prevent memory leaks
696+
//this is neccessary to make ARC keep the db name in memory so that we can safely pass a pointer to this object to our sqlite hook below
697+
NSUInteger index = [dbFilesList indexOfObject:_dbFile];
698+
if(index == NSNotFound)
699+
{
700+
[dbFilesList addObject:_dbFile];
701+
index = dbFilesList.count - 1;
702+
}
703+
//this makes sure to run the checkpointing in a background thread to not block the main thread or any other important thread
704+
sqlite3_wal_hook(self->_database, wal_hook, (__bridge_retained void*)dbFilesList[index]);
705+
}
653706
}
654707

655708
-(void) checkpointWal

0 commit comments

Comments
 (0)