mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Periodically optimize the FTS index.
This commit is contained in:
@@ -79,6 +79,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchove
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
import org.thoughtcrime.securesms.insights.InsightsConstants;
|
||||
import org.thoughtcrime.securesms.jobs.OptimizeMessageSearchIndexJob;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
@@ -1534,6 +1535,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
String[] args = SqlUtil.buildArgs(parentStoryId);
|
||||
|
||||
db.delete(TABLE_NAME, PARENT_STORY_ID + " = ?", args);
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
public int deleteStoriesOlderThan(long timestamp, boolean hasSeenReleaseChannelStories) {
|
||||
@@ -1578,6 +1580,10 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedStoryCount > 0) {
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
return deletedStoryCount;
|
||||
} finally {
|
||||
@@ -3041,7 +3047,16 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
return deleteMessage(messageId, threadId);
|
||||
}
|
||||
|
||||
public boolean deleteMessage(long messageId, boolean notify) {
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
return deleteMessage(messageId, threadId, notify);
|
||||
}
|
||||
|
||||
public boolean deleteMessage(long messageId, long threadId) {
|
||||
return deleteMessage(messageId, threadId, true);
|
||||
}
|
||||
|
||||
private boolean deleteMessage(long messageId, long threadId, boolean notify) {
|
||||
Log.d(TAG, "deleteMessage(" + messageId + ")");
|
||||
|
||||
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
|
||||
@@ -3058,9 +3073,14 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
|
||||
SignalDatabase.threads().setLastScrolled(threadId, 0);
|
||||
boolean threadDeleted = SignalDatabase.threads().update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
|
||||
if (notify) {
|
||||
notifyConversationListeners(threadId);
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
return threadDeleted;
|
||||
}
|
||||
|
||||
@@ -3269,6 +3289,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
setTransactionSuccessful();
|
||||
} finally {
|
||||
endTransaction();
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3286,16 +3307,27 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
deleteMessage(cursor.getLong(0));
|
||||
deleteMessage(cursor.getLong(0), false);
|
||||
}
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadIds);
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
int deleteMessagesInThreadBeforeDate(long threadId, long date) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
String where = THREAD_ID + " = ? AND " + DATE_RECEIVED + " < " + date;
|
||||
|
||||
return db.delete(TABLE_NAME, where, SqlUtil.buildArgs(threadId));
|
||||
int count = db.delete(TABLE_NAME, where, SqlUtil.buildArgs(threadId));
|
||||
|
||||
if (count > 0) {
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void deleteAbandonedMessages() {
|
||||
@@ -3305,6 +3337,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
int deletes = db.delete(TABLE_NAME, where, null);
|
||||
if (deletes > 0) {
|
||||
Log.i(TAG, "Deleted " + deletes + " abandoned messages");
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3342,6 +3375,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
database.delete(TABLE_NAME, null, null);
|
||||
OptimizeMessageSearchIndexJob.enqueue();
|
||||
}
|
||||
|
||||
public @Nullable ViewOnceExpirationInfo getNearestExpiringViewOnceMessage() {
|
||||
|
||||
@@ -6,7 +6,9 @@ import android.database.Cursor
|
||||
import android.text.TextUtils
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.withinTransaction
|
||||
|
||||
/**
|
||||
* Contains all databases necessary for full-text search (FTS).
|
||||
@@ -148,6 +150,73 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This performs the same thing as the `optimize` command in SQLite, but broken into iterative stages to avoid locking up the database for too long.
|
||||
* If what's going on in this method seems weird, that's because it is, but please read the sqlite docs -- we're following their algorithm:
|
||||
* https://www.sqlite.org/fts5.html#the_optimize_command
|
||||
*
|
||||
* Note that in order for the [SqlUtil.getTotalChanges] call to work, we have to be within a transaction, or else the connection pool screws everything up
|
||||
* (the stats are on a per-connection basis).
|
||||
*
|
||||
* There's this double-batching mechanism happening here to strike a balance between making individual transactions short while also not hammering the
|
||||
* database with a ton of independent transactions.
|
||||
*
|
||||
* To give you some ballpark numbers, on a large database (~400k messages), it takes ~75 iterations to fully optimize everything.
|
||||
*/
|
||||
fun optimizeIndex(timeout: Long): Boolean {
|
||||
val pageSize = 64 // chosen through experimentation
|
||||
val batchSize = 10 // chosen through experimentation
|
||||
val noChangeThreshold = 2 // if less changes occurred than this, operation is considered no-op (see sqlite docs ref'd in kdoc)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
var totalIterations = 0
|
||||
var totalBatches = 0
|
||||
var actualWorkTime = 0L
|
||||
var finished = false
|
||||
|
||||
while (!finished) {
|
||||
var batchIterations = 0
|
||||
val batchStartTime = System.currentTimeMillis()
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
// Note the negative page size -- see sqlite docs ref'd in kdoc
|
||||
db.execSQL("INSERT INTO $MMS_FTS_TABLE_NAME ($MMS_FTS_TABLE_NAME, rank) values ('merge', -$pageSize)")
|
||||
var previousCount = SqlUtil.getTotalChanges(db)
|
||||
|
||||
val iterativeStatement = db.compileStatement("INSERT INTO $MMS_FTS_TABLE_NAME ($MMS_FTS_TABLE_NAME, rank) values ('merge', $pageSize)")
|
||||
iterativeStatement.execute()
|
||||
var count = SqlUtil.getTotalChanges(db)
|
||||
|
||||
while (batchIterations < batchSize && count - previousCount >= noChangeThreshold) {
|
||||
previousCount = count
|
||||
iterativeStatement.execute()
|
||||
|
||||
count = SqlUtil.getTotalChanges(db)
|
||||
batchIterations++
|
||||
}
|
||||
|
||||
if (count - previousCount < noChangeThreshold) {
|
||||
finished = true
|
||||
}
|
||||
}
|
||||
|
||||
totalIterations += batchIterations
|
||||
totalBatches++
|
||||
actualWorkTime += System.currentTimeMillis() - batchStartTime
|
||||
|
||||
if (actualWorkTime >= timeout) {
|
||||
Log.w(TAG, "Timed out during optimization! We did $totalIterations iterations across $totalBatches batches, taking ${System.currentTimeMillis() - startTime} ms. Bailed out to avoid database lockup.")
|
||||
return false
|
||||
}
|
||||
|
||||
// We want to sleep in between batches to give other db operations a chance to run
|
||||
ThreadUtil.sleep(50)
|
||||
}
|
||||
|
||||
Log.d(TAG, "Took ${System.currentTimeMillis() - startTime} ms and $totalIterations iterations across $totalBatches batches to optimize. Of that time, $actualWorkTime ms were spent actually working (~${actualWorkTime / totalBatches} ms/batch). The rest was spent sleeping.")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun createFullTextSearchQuery(query: String): String {
|
||||
return query
|
||||
.split(" ")
|
||||
|
||||
Reference in New Issue
Block a user