Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix an edge case when no backup history is found in msdb #11

Merged
merged 2 commits into from
Jul 3, 2024
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
5 changes: 5 additions & 0 deletions src/SqlBackupTools/Restore/Native/BackupRestoreException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ public BackupRestoreException(string message)
: base(message)
{
}

public BackupRestoreException(string message, Exception e)
: base(message, e)
{
}
}
}
86 changes: 63 additions & 23 deletions src/SqlBackupTools/Restore/Native/NativeRestoreMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,45 @@ private async Task<Exception> InternalFullDiffLogAsync(RestoreItem item, bool st
}
else
{
if (_state.RestoredDbs.TryGetValue(item.Name, out RestoreHistoryInfo historyInfo) &&
!string.IsNullOrEmpty(historyInfo.LastBackupPath))
if (_state.RestoredDbs.TryGetValue(item.Name, out RestoreHistoryInfo historyInfo))
{
lastRestored = new FileInfo(historyInfo.LastBackupPath);
var restoreLogsFrom = lastRestored.BackupDate();
item.RpoRecentRestore = restoreLogsFrom;
_state.Loggger.Debug("Last restore made : path=" + historyInfo.LastBackupPath + ", date=" +
restoreLogsFrom);
if (string.IsNullOrEmpty(historyInfo.LastBackupPath))
{
_state.Loggger.Debug("No last restore path found, but db exists, dropping " + item.Name);
// drop the db
var exc = await DropDatabaseWhileTryingRestore(item, sqlConnection);
if (exc != null)
{
return exc;
}

// then restore full
_state.Loggger.Debug("Restoring the last backup FULL : " + item.Name);
lastRestored = await RestoreFullAsync(item, modeFileList, sqlConnection);
item.RpoRecentRestore = lastRestored.BackupDate();
}
else
{
lastRestored = new FileInfo(historyInfo.LastBackupPath);
var restoreLogsFrom = lastRestored.BackupDate();
item.RpoRecentRestore = restoreLogsFrom;
_state.Loggger.Debug("Last restore made : path=" + historyInfo.LastBackupPath + ", date=" + restoreLogsFrom);
}
}
else
{
throw new BackupRestoreException("No backup full, and no database in recovering state, can't continue");
_state.Loggger.Debug("No last restore found, but db may exists, dropping " + item.Name);
// drop the db
var exc = await DropDatabaseWhileTryingRestore(item, sqlConnection);
if (exc != null)
{
return exc;
}

// then restore full
_state.Loggger.Debug("Restoring the last backup FULL : " + item.Name);
lastRestored = await RestoreFullAsync(item, modeFileList, sqlConnection);
item.RpoRecentRestore = lastRestored.BackupDate();
}
}

Expand All @@ -100,21 +127,10 @@ private async Task<Exception> InternalFullDiffLogAsync(RestoreItem item, bool st
item.StatsDropped++;
_state.Loggger.Debug(sqle, item.Name + " : Error on first attempt, retrying from scratch");
forceRestoreFull = true;
bool singleUserMode = false;
try
{
if (_state.ActualDbs.TryGetValue(item.Name, out var databaseInfo) && databaseInfo.State != DatabaseState.RESTORING)
{
singleUserMode = true;
await sqlConnection.ExecuteAsync($"ALTER DATABASE [{item.Name}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", commandTimeout: _state.RestoreCommand.Timeout);
}
await sqlConnection.ExecuteAsync($"DROP DATABASE [{item.Name}]", commandTimeout: _state.RestoreCommand.Timeout);
await sqlConnection.ExecuteAsync($"EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = N'{item.Name}'", commandTimeout: _state.RestoreCommand.Timeout);
}
catch (Exception e)
var exc = await DropDatabaseWhileTryingRestore(item, sqlConnection);
if (exc != null)
{
_state.Loggger.Error(e, $"Error while trying to drop db{(singleUserMode ? " in SINGLE_USER mode" : string.Empty)}");
return e;
return exc;
}
}
catch (SqlException sqle)
Expand All @@ -132,6 +148,29 @@ private async Task<Exception> InternalFullDiffLogAsync(RestoreItem item, bool st
return null;
}

private async Task<Exception> DropDatabaseWhileTryingRestore(RestoreItem item, SqlConnection sqlConnection)
{
bool singleUserMode = false;
try
{
if (_state.ActualDbs.TryGetValue(item.Name, out var databaseInfo) && databaseInfo.State != DatabaseState.RESTORING)
{
singleUserMode = true;
await sqlConnection.ExecuteAsync($"ALTER DATABASE [{item.Name}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", commandTimeout: _state.RestoreCommand.Timeout);
}
await sqlConnection.ExecuteAsync($"DROP DATABASE [{item.Name}]", commandTimeout: _state.RestoreCommand.Timeout);
await sqlConnection.ExecuteAsync($"EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = N'{item.Name}'", commandTimeout: _state.RestoreCommand.Timeout);
}
catch (Exception e)
{
_state.Loggger.Error(e, $"Error while trying to drop db{(singleUserMode ? " in SINGLE_USER mode" : string.Empty)}");
{
return e;
}
}
return null;
}

private async Task<FileInfo> RestoreFullAsync(RestoreItem item, bool modeFileList, SqlConnection sqlConnection)
{
FileInfo fullFile = item.Full
Expand Down Expand Up @@ -245,7 +284,8 @@ private async Task RestoreLogAsync(RestoreItem item, FileInfo lastRestored, SqlC
// - adding the current one again
if (lastLogFile == null)
{
throw new BackupRestoreException("todo : extract last trn from backup history");
var ex = await DropDatabaseWhileTryingRestore(item, sqlConnection);
throw new BackupRestoreException("The log in this backup set begins at LSN x, which is too recent to apply to the database. We're dropping the DB, and will try restore full on next loop.", ex);
}
backupLogsToRestore.Push((currentLogFile, RetryStrategy.None));
backupLogsToRestore.Push((lastLogFile, RetryStrategy.ExtractHeaders));
Expand Down
4 changes: 2 additions & 2 deletions src/SqlBackupTools/Restore/RestoreState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public async Task LoadServerInfosAsync()
ServerInfos = await SqlConnection.GetServerInfosAsync();
}

private const int _systemQueryTimeout = 10;
private const int _systemQueryTimeout = 30;

public Task EnsuredIndexsExistsAsync()
{
Expand Down Expand Up @@ -293,6 +293,6 @@ public static Task<IEnumerable<DatabaseInfo>> GetUserDatabasesAsync(this SqlConn
sqlConnection.QueryAsync<DatabaseInfo>(@"select name, state
from sys.databases where database_id > 4 ", commandTimeout: _systemQueryTimeout);

private const int _systemQueryTimeout = 10;
private const int _systemQueryTimeout = 30;
}
}