Regularly analyze database tables to improve index usage.
This commit is contained in:
parent
713298109a
commit
982f602178
6 changed files with 169 additions and 0 deletions
|
@ -1288,6 +1288,12 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.AnalyzeDatabaseAlarmListener" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
|
||||
|
||||
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
|
||||
|
|
|
@ -83,6 +83,7 @@ import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
|
|||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
||||
import org.thoughtcrime.securesms.service.AnalyzeDatabaseAlarmListener;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||
|
@ -419,6 +420,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
LocalBackupListener.schedule(this);
|
||||
RotateSenderCertificateListener.schedule(this);
|
||||
RoutineMessageFetchReceiver.startOrUpdateAlarm(this);
|
||||
AnalyzeDatabaseAlarmListener.schedule(this);
|
||||
|
||||
if (BuildConfig.MANAGES_APP_UPDATES) {
|
||||
ApkUpdateRefreshListener.schedule(this);
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.getAllTables
|
||||
import org.signal.core.util.logTime
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Analyzes the database, updating statistics to ensure that sqlite is using the best indices possible for different queries.
|
||||
*
|
||||
* Given that analysis can be slow, this job will only analyze one table at a time, retrying itself so long as there are more tables to analyze.
|
||||
* This should help protect it against getting canceled by the system for running for too long, while also giving it the ability to save it's place.
|
||||
*/
|
||||
class AnalyzeDatabaseJob private constructor(
|
||||
parameters: Parameters,
|
||||
private var lastCompletedTable: String?
|
||||
) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(AnalyzeDatabaseJob::class.java)
|
||||
|
||||
const val KEY = "AnalyzeDatabaseJob"
|
||||
|
||||
private const val KEY_LAST_COMPLETED_TABLE = "last_completed_table"
|
||||
}
|
||||
|
||||
constructor() : this(
|
||||
Parameters.Builder()
|
||||
.setMaxInstancesForFactory(1)
|
||||
.setLifespan(1.days.inWholeMilliseconds)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
null
|
||||
)
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putString(KEY_LAST_COMPLETED_TABLE, lastCompletedTable)
|
||||
.build()
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun run(): Result {
|
||||
val tables = SignalDatabase.rawDatabase.getAllTables()
|
||||
.sorted()
|
||||
.filterNot { it.startsWith("sqlite_") || it.contains("fts_") }
|
||||
|
||||
if (tables.isEmpty()) {
|
||||
Log.w(TAG, "Table list is empty!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val startingIndex = if (lastCompletedTable != null) {
|
||||
tables.indexOf(lastCompletedTable) + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
if (startingIndex >= tables.size) {
|
||||
Log.i(TAG, "Already finished all of the tables!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val table = tables[startingIndex]
|
||||
|
||||
logTime(TAG, "analyze-$table", decimalPlaces = 2) {
|
||||
SignalDatabase.rawDatabase.execSQL("PRAGMA analysis_limit=1000")
|
||||
SignalDatabase.rawDatabase.execSQL("ANALYZE $table")
|
||||
}
|
||||
|
||||
if (startingIndex >= tables.size - 1) {
|
||||
Log.i(TAG, "Finished all of the tables!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
lastCompletedTable = table
|
||||
return Result.retry(1.seconds.inWholeMilliseconds)
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<AnalyzeDatabaseJob> {
|
||||
override fun create(parameters: Parameters, data: ByteArray?): AnalyzeDatabaseJob {
|
||||
val builder = JsonJobData.deserialize(data)
|
||||
|
||||
return AnalyzeDatabaseJob(parameters, builder.getStringOrDefault(KEY_LAST_COMPLETED_TABLE, null))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -99,6 +99,7 @@ public final class JobManagerFactories {
|
|||
public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
|
||||
return new HashMap<String, Job.Factory>() {{
|
||||
put(AccountConsistencyWorkerJob.KEY, new AccountConsistencyWorkerJob.Factory());
|
||||
put(AnalyzeDatabaseJob.KEY, new AnalyzeDatabaseJob.Factory());
|
||||
put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory());
|
||||
put(AttachmentCopyJob.KEY, new AttachmentCopyJob.Factory());
|
||||
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
|
||||
|
|
|
@ -35,6 +35,7 @@ internal class MiscellaneousValues internal constructor(store: KeyValueStore) :
|
|||
private const val LAST_CDS_FOREGROUND_SYNC = "misc.last_cds_foreground_sync"
|
||||
private const val LINKED_DEVICE_LAST_ACTIVE_CHECK_TIME = "misc.linked_device.last_active_check_time"
|
||||
private const val LEAST_ACTIVE_LINKED_DEVICE = "misc.linked_device.least_active"
|
||||
private const val NEXT_DATABASE_ANALYSIS_TIME = "misc.next_database_analysis_time"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() {
|
||||
|
@ -236,4 +237,9 @@ internal class MiscellaneousValues internal constructor(store: KeyValueStore) :
|
|||
* Details about the least-active linked device.
|
||||
*/
|
||||
var leastActiveLinkedDevice: LeastActiveLinkedDevice? by protoValue(LEAST_ACTIVE_LINKED_DEVICE, LeastActiveLinkedDevice.ADAPTER)
|
||||
|
||||
/**
|
||||
* When the next scheduled database analysis is.
|
||||
*/
|
||||
var nextDatabaseAnalysisTime: Long by longValue(NEXT_DATABASE_ANALYSIS_TIME, 0)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package org.thoughtcrime.securesms.service
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.AnalyzeDatabaseJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.toMillis
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Schedules database analysis to happen everyday at 3am.
|
||||
*/
|
||||
class AnalyzeDatabaseAlarmListener : PersistentAlarmManagerListener() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun schedule(context: Context?) {
|
||||
AnalyzeDatabaseAlarmListener().onReceive(context, getScheduleIntent())
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldScheduleExact(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getNextScheduledExecutionTime(context: Context): Long {
|
||||
var nextTime = SignalStore.misc().nextDatabaseAnalysisTime
|
||||
|
||||
if (nextTime == 0L) {
|
||||
nextTime = getNextTime()
|
||||
SignalStore.misc().nextDatabaseAnalysisTime = nextTime
|
||||
}
|
||||
|
||||
return nextTime
|
||||
}
|
||||
|
||||
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
||||
ApplicationDependencies.getJobManager().add(AnalyzeDatabaseJob())
|
||||
|
||||
val nextTime = getNextTime()
|
||||
SignalStore.misc().nextDatabaseAnalysisTime = nextTime
|
||||
|
||||
return nextTime
|
||||
}
|
||||
|
||||
private fun getNextTime(): Long {
|
||||
return LocalDateTime
|
||||
.now()
|
||||
.plusDays(1)
|
||||
.withHour(3)
|
||||
.withMinute(0)
|
||||
.withSecond(0)
|
||||
.toMillis()
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue