Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions Lite/Database/DuckDbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,20 @@
/// </summary>
public async Task InitializeAsync()
{
_logger?.LogInformation("Initializing DuckDB database at {Path}", _databasePath);

Check warning on line 55 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 55 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

var directory = Path.GetDirectoryName(_databasePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
_logger?.LogInformation("Created database directory: {Directory}", directory);

Check warning on line 61 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 61 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}

var archivePath = Path.Combine(directory ?? ".", "archive");
if (!Directory.Exists(archivePath))
{
Directory.CreateDirectory(archivePath);
_logger?.LogInformation("Created archive directory: {ArchivePath}", archivePath);

Check warning on line 68 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 68 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}

/* Try to open the database. If the DuckDB storage version has changed,
Expand Down Expand Up @@ -94,7 +94,7 @@

if (existingVersion < CurrentSchemaVersion)
{
_logger?.LogInformation("Schema upgrade needed: v{Old} -> v{New}", existingVersion, CurrentSchemaVersion);

Check warning on line 97 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 97 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
await RunMigrationsAsync(connection, existingVersion);
await SetSchemaVersionAsync(connection, CurrentSchemaVersion);
}
Expand All @@ -109,7 +109,7 @@
await ExecuteNonQueryAsync(connection, indexStatement);
}

_logger?.LogInformation("Database initialization complete. Schema version: {Version}", CurrentSchemaVersion);

Check warning on line 112 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 112 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}

await CreateArchiveViewsAsync();
Expand Down Expand Up @@ -162,7 +162,7 @@
cmd.CommandText = $"EXPORT DATABASE '{exportDir.Replace("'", "''")}' (FORMAT PARQUET)";
await cmd.ExecuteNonQueryAsync();
exported = true;
_logger?.LogInformation("Exported old database to {ExportDir}", exportDir);

Check warning on line 165 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 165 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}
}
catch (Exception ex)
Expand All @@ -176,7 +176,7 @@
{
/* DuckDB may have .wal files too */
File.Move(_databasePath, backupPath);
_logger?.LogInformation("Backed up old database to {BackupPath}", backupPath);

Check warning on line 179 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 179 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

var walPath = _databasePath + ".wal";
if (File.Exists(walPath))
Expand Down Expand Up @@ -406,12 +406,12 @@
}

if (serverNames.Count > 0)
_logger?.LogInformation("Fixed server_id in {Table} for {Count} server(s)", table, serverNames.Count);

Check warning on line 409 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 409 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}
catch (Exception ex)
{
/* Table might not exist yet — that's fine, it will be created with correct IDs */
_logger?.LogDebug(ex, "Skipped server_id fix for {Table} (may not exist yet)", table);

Check warning on line 414 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 414 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}
}
}
Expand Down Expand Up @@ -473,7 +473,7 @@
}
}

_logger?.LogDebug("Archive views created/refreshed for {Count} tables", ArchivableTables.Length);

Check warning on line 476 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)

Check warning on line 476 in Lite/Database/DuckDbInitializer.cs

View workflow job for this annotation

GitHub Actions / build

Evaluation of this argument may be expensive and unnecessary if logging is disabled (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
}

/// <summary>
Expand Down Expand Up @@ -604,14 +604,35 @@
}
}

/* Swap files: old → backup, compact → primary */
if (File.Exists(backupPath)) File.Delete(backupPath);
File.Move(_databasePath, backupPath);

/* Delete WAL files before swap — the old WAL belongs to the pre-compaction
database and would confuse the fresh compacted file on next open */
var walPath = _databasePath + ".wal";
if (File.Exists(walPath)) File.Delete(walPath);

File.Move(tempPath, _databasePath);
var tempWalPath = tempPath + ".wal";
if (File.Exists(tempWalPath)) File.Delete(tempWalPath);

/* Atomically replace the database file with the compacted version.
File.Replace swaps in a single OS operation, eliminating any window
where _databasePath doesn't exist (unlike two separate File.Move calls).
Retry briefly if a UI connection still has the file open. */
if (File.Exists(backupPath)) File.Delete(backupPath);

const int maxSwapAttempts = 3;
for (int attempt = 1; attempt <= maxSwapAttempts; attempt++)
{
try
{
File.Replace(tempPath, _databasePath, backupPath);
break;
}
catch (IOException) when (attempt < maxSwapAttempts)
{
_logger?.LogDebug("Compaction file swap attempt {Attempt}/{Max} failed (file in use), retrying in 500ms",
attempt, maxSwapAttempts);
await Task.Delay(500);
}
}

/* Recreate indexes and views on the fresh database */
using (var connection = CreateConnection())
Expand Down
12 changes: 5 additions & 7 deletions Lite/Services/CollectionBackgroundService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ public class CollectionBackgroundService : BackgroundService
private readonly ILogger<CollectionBackgroundService>? _logger;

private static readonly TimeSpan CollectionInterval = TimeSpan.FromMinutes(1);
private DateTime _lastArchiveTime = DateTime.MinValue;
private DateTime _lastRetentionTime = DateTime.MinValue;
private DateTime _lastCompactionTime = DateTime.MinValue;
/* Start at UtcNow so maintenance tasks don't all fire on the very first cycle.
Archival runs after 1 hour, retention + compaction after 24 hours of uptime. */
private DateTime _lastArchiveTime = DateTime.UtcNow;
private DateTime _lastRetentionTime = DateTime.UtcNow;
private DateTime _lastCompactionTime = DateTime.UtcNow;

/* Archive every hour, retention + compaction once per day */
private static readonly TimeSpan ArchiveInterval = TimeSpan.FromHours(1);
Expand Down Expand Up @@ -93,10 +95,6 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
IsCollecting = true;
await _collectorService.RunDueCollectorsAsync(stoppingToken);
LastCollectionTime = DateTime.UtcNow;

/* Flush WAL during idle time instead of letting auto-checkpoint
stall collectors mid-write with 2-3s stop-the-world pauses */
await _collectorService.CheckpointAsync();
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
Expand Down
16 changes: 16 additions & 0 deletions Lite/Services/RemoteCollectorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ public async Task RunDueCollectorsAsync(CancellationToken cancellationToken = de
}, cancellationToken));

await Task.WhenAll(serverTasks);

/* Run CHECKPOINT here after all collector connections are closed.
This avoids opening a separate DuckDB instance that could conflict
with concurrent UI connections via OS file locks. */
try
{
using var conn = _duckDb.CreateConnection();
await conn.OpenAsync(cancellationToken);
using var cmd = conn.CreateCommand();
cmd.CommandText = "CHECKPOINT";
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
catch (Exception ex)
{
_logger?.LogDebug(ex, "Post-collection checkpoint failed (non-critical)");
}
}

/// <summary>
Expand Down