Based on this public documentation Auto-failover groups overview & best practices - Azure SQL Database | Microsoft Learn , we received several questions about how to identify if any data has been not synced between primary and secondary when we performed a force failover in Azure Transparent Failover Group.
As you could see in this public documentation Auto-failover groups overview & best practices - Azure SQL Database | Microsoft Learn about sp_wait_for_database_copy_sync "sp_wait_for_database_copy_sync prevents data loss after geo-failover for specific transactions, but does not guarantee full synchronization for read access. The delay caused by a sp_wait_for_database_copy_sync procedure call can be significant and depends on the size of the not yet transmitted transaction log on the primary at the time of the call."
Our customer asked about several scenarios to understand this behaviour and also, verify if there is possible to identify the rows that have not been synced. For this, I developed a POC to test it:
So, my lesson learned that using SQL Auditing Log will be an option to identify if any data has been lost.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Data;
using C = System.Data.SqlClient;
using System.Diagnostics;
using System.Threading;
using System.Configuration;
namespace DotNetExample
{
class ClsGeo
{
private static string GetConnectionString(bool bPooling)
{
return ConfigurationManager.ConnectionStrings["OkGeo"].ToString() + (bPooling ? "yes" : "no");
}
public void Inicia(int nRows, bool bPooling, bool bInstanciaCadaVez = false)
{
try
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
C.SqlConnection oConn = new C.SqlConnection();
ClsRetryLogic oClsRetry = new ClsRetryLogic();
if (oClsRetry.HazUnaConexionConReintentos(GetConnectionString(bPooling), oConn, bInstanciaCadaVez))
{
C.SqlTransaction transaction;
transaction = oConn.BeginTransaction();
C.SqlCommand command = new C.SqlCommand("insert into Example (Name) values(@Name)", oConn, transaction);
command.CommandTimeout = 30;
command.Parameters.Add("@Name", SqlDbType.VarChar, 100);
command.Prepare();
for (int tries = 1; tries <= nRows; tries++)
{
Console.WriteLine("Attempt Nr.: " + tries.ToString());
command.Parameters["@Name"].Value = "Example " + (tries).ToString();
command.ExecuteNonQuery();
Console.WriteLine("ExecuteNonQuery Done.: " + tries.ToString());
}
transaction.Commit();
stopWatch.Stop();
// Obtenemos el tiempo pasado
TimeSpan ts = stopWatch.Elapsed;
// Formateamos y mostramos.
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine("Pooling:{0}. Time:{1}", bPooling ? "Pooling" : "Without Pooling", elapsedTime);
Console.ReadLine();
}
}
catch (Exception e)
{
Console.WriteLine("ExecuteNonQuery Error.: ");
Console.WriteLine("Ups!! " + e.Message);
Console.ReadLine();
}
}
public void Inicia3(int nRows, bool bPooling, bool bInstanciaCadaVez = false)
{
try
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
C.SqlConnection oConn = new C.SqlConnection();
ClsRetryLogic oClsRetry = new ClsRetryLogic();
if (oClsRetry.HazUnaConexionConReintentos(GetConnectionString(bPooling), oConn, bInstanciaCadaVez))
{
C.SqlCommand command = new C.SqlCommand("insert into Example (nAME) values(@Name)", oConn);
command.CommandTimeout = 30;
command.Parameters.Add("@Name", SqlDbType.VarChar, 100);
command.Prepare();
C.SqlCommand commandSyncData = new C.SqlCommand("EXEC sys.sp_wait_for_database_copy_sync _server = N'datasource', _database = N'failover-test'", oConn);
commandSyncData.CommandTimeout = 30;
for (int tries = 1; tries <= nRows; tries++)
{
Console.WriteLine("Attempt Nr.: " + tries.ToString());
command.Parameters["@Name"].Value = "Example " + (tries).ToString();
command.ExecuteNonQuery();
Console.WriteLine("ExecuteNonQuery Done.: " + tries.ToString());
Console.WriteLine("ExecuteNonQuery Sync.: " );
commandSyncData.ExecuteNonQuery();
Console.WriteLine("ExecuteNonQuery Sync Done.: " );
}
stopWatch.Stop();
// Obtenemos el tiempo pasado
TimeSpan ts = stopWatch.Elapsed;
// Formateamos y mostramos.
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine("Pooling:{0}. Time:{1}", bPooling ? "Pooling" : "Without Pooling", elapsedTime);
Console.ReadLine();
}
}
catch (Exception e)
{
Console.WriteLine("ExecuteNonQuery Error.: ");
Console.WriteLine("Ups!! " + e.Message);
Console.ReadLine();
}
}
}
}
Enjoy!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.