Skip to content

Commit

Permalink
Merge pull request #11 from LuccaSA/fix.restore.without.history
Browse files Browse the repository at this point in the history
Fix an edge case when no backup history is found in msdb
  • Loading branch information
rducom committed Jul 3, 2024
2 parents 79b2825 + 28cfe67 commit 54aceea
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 25 deletions.
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;
}
}

0 comments on commit 54aceea

Please sign in to comment.