From 7c92ce771fe7ad6f8e6ca49da91e133daf1db03d Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Tue, 10 Sep 2024 17:34:02 +0900 Subject: [PATCH] Reindex Invoices table if corrupt, fix migration timeout (#6207) --- .../HostedServices/BlobMigratorHostedService.cs | 17 +++++++++++++++-- .../InvoiceBlobMigratorHostedService.cs | 14 +++++++++++++- BTCPayServer/Hosting/MigrationStartupTask.cs | 6 +++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/BTCPayServer/HostedServices/BlobMigratorHostedService.cs b/BTCPayServer/HostedServices/BlobMigratorHostedService.cs index ae170d8bf..e6221a014 100644 --- a/BTCPayServer/HostedServices/BlobMigratorHostedService.cs +++ b/BTCPayServer/HostedServices/BlobMigratorHostedService.cs @@ -67,14 +67,23 @@ public abstract class BlobMigratorHostedService : IHostedService retry: List entities; DateTimeOffset progress; - await using (var ctx = ApplicationDbContextFactory.CreateContext()) + await using (var ctx = ApplicationDbContextFactory.CreateContext(o => o.CommandTimeout((int)TimeSpan.FromDays(1.0).TotalSeconds))) { var query = GetQuery(ctx, settings?.Progress).Take(batchSize); entities = await query.ToListAsync(cancellationToken); if (entities.Count == 0) { + var count = await GetQuery(ctx, null).CountAsync(cancellationToken); + if (count != 0) + { + settings = new Settings() { Progress = null }; + Logs.LogWarning("Corruption detected, reindexing the table..."); + await Reindex(ctx, cancellationToken); + goto retry; + } await SettingsRepository.UpdateSetting(new Settings() { Complete = true }, SettingsKey); Logs.LogInformation("Migration completed"); + await PostMigrationCleanup(ctx, cancellationToken); return; } @@ -84,7 +93,7 @@ retry: await ctx.SaveChangesAsync(); batchSize = BatchSize; } - catch (DbUpdateConcurrencyException) + catch (Exception ex) when (ex is DbUpdateConcurrencyException or TimeoutException or OperationCanceledException) { batchSize /= 2; batchSize = Math.Max(1, batchSize); @@ -95,6 +104,10 @@ retry: await SettingsRepository.UpdateSetting(settings, SettingsKey); } } + + protected abstract Task PostMigrationCleanup(ApplicationDbContext ctx, CancellationToken cancellationToken); + + protected abstract Task Reindex(ApplicationDbContext ctx, CancellationToken cancellationToken); protected abstract IQueryable GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress); protected abstract DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List entities); public async Task ResetMigration() diff --git a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs index 2b4abfc78..7bcf1a694 100644 --- a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs +++ b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs @@ -9,6 +9,7 @@ using AngleSharp.Dom; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Data; using BTCPayServer.Services.Invoices; +using Dapper; using Google.Apis.Logging; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; @@ -42,9 +43,13 @@ public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService o.Payments).Where(i => i.Currency == null); return query.OrderByDescending(i => i.Created); } + protected override Task Reindex(ApplicationDbContext ctx, CancellationToken cancellationToken) + { + return ctx.Database.GetDbConnection().ExecuteAsync(new("REINDEX INDEX \"IX_Invoices_Created\";REINDEX INDEX \"PK_Invoices\";", cancellationToken: cancellationToken)); + } protected override DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List invoices) { - // Those clean up the JSON blobs, and mark entities as modified + // Those clean up the JSON blobs foreach (var inv in invoices) { var blob = inv.GetBlob(); @@ -70,4 +75,11 @@ public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService o.CommandTimeout(cancellationTimeout + 1)); + var db = _DBContextFactory.CreateContext(o => o.CommandTimeout(((int)cancellationTimeout.TotalSeconds) + 1)); await db.Database.MigrateAsync(timeout.Token); _logger.LogInformation("All migration scripts ran successfully"); }