Files
wjones 9bcb8501ab Add scripts for PostgreSQL migration and SQLite removal; update project references
- Introduced `publish-with-sql.ps1` to publish Jellyfin with SQL files.
- Added `remove-sqlite.ps1` to facilitate the removal of SQLite dependencies for PostgreSQL-only deployment.
- Created `rollback-to-net10.ps1` to revert .NET 11 changes back to .NET 10.
- Implemented `test_api.py` for testing API interactions.
- Added `verify-migration.ps1` to verify PostgreSQL migration steps.
- Updated various `.csproj` files to include `Microsoft.Kiota.Abstractions` package.
- Enhanced `JellyfinDbContext` to handle concurrency exceptions during save operations.
- Updated tests to include new package references and ensure compatibility with changes.
2026-07-07 10:59:40 -04:00

141 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.ScheduledTasks
{
/// <summary>
/// Class PostgresPeriodicTuner.
/// </summary>
public class PostgresPeriodicTuner : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILogger<PostgresPeriodicTuner> _logger;
private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
/// <summary>
/// Initializes a new instance of the <see cref="PostgresPeriodicTuner"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="dbContextFactory">The database context factory.</param>
public PostgresPeriodicTuner(ILogger<PostgresPeriodicTuner> logger, IDbContextFactory<JellyfinDbContext> dbContextFactory)
{
_logger = logger;
_dbContextFactory = dbContextFactory;
}
/// <inheritdoc />
public string Name => "PostgreSQL Periodic Tuner";
/// <inheritdoc />
public string Key => "PostgresPeriodicTuner";
/// <inheritdoc />
public string Description => "Periodically checks for dead tuples and runs VACUUM ANALYZE on tables that need it.";
/// <inheritdoc />
public string Category => "Database";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <summary>
/// Executes the task.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
_logger.LogInformation("PostgreSQL Periodic Tuner task started.");
try
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
const string DeadTupleQuery = @"
SELECT
relname AS TableName,
n_dead_tup AS DeadTuples
FROM
pg_stat_user_tables
WHERE
n_dead_tup > 1000 -- Example threshold: more than 1000 dead tuples
ORDER BY
n_dead_tup DESC;";
var tablesToVacuum = (await dbContext.Database.SqlQueryRaw<(string TableName, long DeadTuples)>(DeadTupleQuery).ToListAsync(cancellationToken).ConfigureAwait(false)).ToList();
if (tablesToVacuum.Count == 0)
{
_logger.LogInformation("No tables require vacuuming at this time.");
return;
}
progress.Report(10);
double progressStep = 90.0 / tablesToVacuum.Count;
double currentProgress = 10.0;
foreach (var table in tablesToVacuum)
{
cancellationToken.ThrowIfCancellationRequested();
_logger.LogInformation("Found {DeadTuples} dead tuples in table {TableName}. Running VACUUM ANALYZE.", table.DeadTuples, table.TableName);
try
{
await dbContext.Database.ExecuteSqlInterpolatedAsync($"VACUUM ANALYZE \"{table.TableName}\"", cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Successfully vacuumed and analyzed table {TableName}.", table.TableName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error running VACUUM ANALYZE on table {TableName}.", table.TableName);
}
currentProgress += progressStep;
progress.Report(currentProgress);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during PostgreSQL Periodic Tuner task.");
}
finally
{
_logger.LogInformation("PostgreSQL Periodic Tuner task finished.");
progress.Report(100);
}
}
/// <summary>
/// Gets the default triggers.
/// </summary>
/// <returns>IEnumerable&lt;TaskTriggerInfo&gt;.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromHours(24).Ticks
}
};
}
}
}