aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-09-05 07:34:38 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-09-05 07:36:17 +0200
commit5465e0c69423506d6836f2a6b8aacfce8cb0501e (patch)
tree4a77740d2fedd93e4fd7ba6abefa65be630a754c
parent0b5bbb528af08d950bc9887b5a1114888820688b (diff)
Optimize the database on startup instead of before shutdown
-rw-r--r--Jellyfin.Server/Migrations/JellyfinMigrationService.cs6
-rw-r--r--Jellyfin.Server/Program.cs30
-rw-r--r--Jellyfin.Server/ServerSetupApp/StartupActivity.cs2
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs2
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs14
5 files changed, 16 insertions, 38 deletions
diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs
index 5ef039a843..6ecbe6a95e 100644
--- a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs
+++ b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs
@@ -188,8 +188,8 @@ internal class JellyfinMigrationService
/// </summary>
/// <param name="stage">The stage to migrate.</param>
/// <param name="serviceProvider">The service provider handed to the migrations.</param>
- /// <returns>A value indicating whether at least one migration has been applied.</returns>
- public async Task<bool> MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider)
+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+ public async Task MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider)
{
var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate stage {stage}.");
ICollection<CodeMigration> migrationStage = (Migrations.FirstOrDefault(e => e.Stage == stage) as ICollection<CodeMigration>) ?? [];
@@ -303,8 +303,6 @@ internal class JellyfinMigrationService
completedMigrations++;
}
-
- return completedMigrations > 0;
}
}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 2341af47c1..2391c86641 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -61,7 +61,6 @@ namespace Jellyfin.Server
private static ILogger _logger = NullLogger.Instance;
private static bool _restartOnShutdown;
private static IStartupLogger<JellyfinMigrationService>? _migrationLogger;
- private static bool _optimizeDatabaseAfterMigration;
private static string? _restoreFromBackup;
/// <summary>
@@ -210,15 +209,15 @@ namespace Jellyfin.Server
await jellyfinMigrationService.PrepareSystemForMigration(_logger).ConfigureAwait(false);
// "Preparing migrations" carries through the DB read; per-migration progress is reported
// as "Running migration X of Y" from inside the step once the pending set is known.
- _optimizeDatabaseAfterMigration |= await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
+ await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
SetupServer.ReportActivity(StartupActivity.InitializingServices);
await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
_appHost = appHost;
- _optimizeDatabaseAfterMigration |= await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
+ await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
await jellyfinMigrationService.CleanupSystemAfterMigration(_logger).ConfigureAwait(false);
- await OptimizeDatabaseAfterMigrationAsync(appHost.ServiceProvider).ConfigureAwait(false);
+ await OptimizeDatabaseAsync(appHost.ServiceProvider).ConfigureAwait(false);
try
{
configurationCompleted = true;
@@ -273,11 +272,8 @@ namespace Jellyfin.Server
// Don't throw additional exception if startup failed.
if (appHost.ServiceProvider is not null)
{
- _logger.LogInformation("Running query planner optimizations in the database... This might take a while");
-
var databaseProvider = appHost.ServiceProvider.GetRequiredService<IJellyfinDatabaseProvider>();
- using var shutdownSource = new CancellationTokenSource();
- shutdownSource.CancelAfter((int)TimeSpan.FromSeconds(60).TotalMicroseconds);
+ using var shutdownSource = new CancellationTokenSource(TimeSpan.FromSeconds(60));
await databaseProvider.RunShutdownTask(shutdownSource.Token).ConfigureAwait(false);
}
@@ -316,7 +312,7 @@ namespace Jellyfin.Server
var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(startupService);
await jellyfinMigrationService.CheckFirstTimeRunOrMigration(appPaths, startupOptions).ConfigureAwait(false);
- _optimizeDatabaseAfterMigration |= await jellyfinMigrationService.MigrateStepAsync(Migrations.Stages.JellyfinMigrationStageTypes.PreInitialisation, startupService).ConfigureAwait(false);
+ await jellyfinMigrationService.MigrateStepAsync(Migrations.Stages.JellyfinMigrationStageTypes.PreInitialisation, startupService).ConfigureAwait(false);
}
/// <summary>
@@ -331,30 +327,24 @@ namespace Jellyfin.Server
public static async Task ApplyCoreMigrationsAsync(IServiceProvider serviceProvider, Migrations.Stages.JellyfinMigrationStageTypes jellyfinMigrationStage)
{
var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(serviceProvider, _migrationLogger!);
- _optimizeDatabaseAfterMigration |= await jellyfinMigrationService.MigrateStepAsync(jellyfinMigrationStage, serviceProvider).ConfigureAwait(false);
+ await jellyfinMigrationService.MigrateStepAsync(jellyfinMigrationStage, serviceProvider).ConfigureAwait(false);
}
- private static async Task OptimizeDatabaseAfterMigrationAsync(IServiceProvider serviceProvider)
+ private static async Task OptimizeDatabaseAsync(IServiceProvider serviceProvider)
{
- if (!_optimizeDatabaseAfterMigration)
- {
- return;
- }
-
- // Reset first: a restart runs no migrations and must not optimize again.
- _optimizeDatabaseAfterMigration = false;
SetupServer.ReportActivity(StartupActivity.OptimizingDatabase);
- _logger.LogInformation("Migrations have been applied, optimizing the database... This might take a while");
+ _logger.LogInformation("Vacuuming and analyzing the database... This might take a while");
try
{
+ // Deliberately untimed: incomplete statistics are worse than a slow start.
var databaseProvider = serviceProvider.GetRequiredService<IJellyfinDatabaseProvider>();
await databaseProvider.RunScheduledOptimisation(CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
// A missed optimization only costs performance, so never fail startup over this.
- _logger.LogError(ex, "Error while optimizing the database after migration");
+ _logger.LogError(ex, "Error while optimizing the database");
}
}
diff --git a/Jellyfin.Server/ServerSetupApp/StartupActivity.cs b/Jellyfin.Server/ServerSetupApp/StartupActivity.cs
index abfc5bc8ba..5fc485bd48 100644
--- a/Jellyfin.Server/ServerSetupApp/StartupActivity.cs
+++ b/Jellyfin.Server/ServerSetupApp/StartupActivity.cs
@@ -27,7 +27,7 @@ public static class StartupActivity
/// <summary>Bringing up core services and plugins.</summary>
public const string InitializingServices = "Initializing services";
- /// <summary>Refreshing the database statistics after migrations have run.</summary>
+ /// <summary>Refreshing the database query planner statistics.</summary>
public const string OptimizingDatabase = "Optimizing database";
/// <summary>Running the final startup tasks.</summary>
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs
index 87d87e92b8..a7b2aa493b 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/IJellyfinDatabaseProvider.cs
@@ -38,7 +38,7 @@ public interface IJellyfinDatabaseProvider
/// <summary>
/// If supported this should run any periodic maintaince tasks, reclaiming unused space and refreshing the query
- /// planner statistics. Also used after migrations have modified the database.
+ /// planner statistics. Also runs on startup once all migrations have been applied.
/// </summary>
/// <param name="cancellationToken">The token to abort the operation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
index dff834bfec..6103ecd992 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
@@ -124,21 +124,11 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
}
/// <inheritdoc/>
- public async Task RunShutdownTask(CancellationToken cancellationToken)
+ public Task RunShutdownTask(CancellationToken cancellationToken)
{
- if (DbContextFactory is null)
- {
- return;
- }
-
// Run before disposing the application
- var context = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- await using (context.ConfigureAwait(false))
- {
- await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
- }
-
SqliteConnection.ClearAllPools();
+ return Task.CompletedTask;
}
/// <inheritdoc/>