Skip to content

Commit a684e01

Browse files
Add additional trigger debug logging (#1103)
* Add additional debug logging * Add max attempts to lease locked check * log when match condition is null --------- Co-authored-by: MaddyDev <[email protected]>
1 parent 953a1cf commit a684e01

File tree

2 files changed

+64
-31
lines changed

2 files changed

+64
-31
lines changed

src/Telemetry/Telemetry.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,6 @@ public enum TelemetryMeasureName
394394
GetColumnDefinitionsDurationMs,
395395
GetPrimaryKeysDurationMs,
396396
GetUnprocessedChangesDurationMs,
397-
GetLockedRowCountDurationMs,
398397
InsertGlobalStateTableRowDurationMs,
399398
MaxBatchSize,
400399
MaxChangesPerWorker,
@@ -441,7 +440,7 @@ public enum TelemetryErrorName
441440
Upsert,
442441
UpsertRollback,
443442
GetServerTelemetryProperties,
444-
GetLeaseLockedRowCount,
443+
GetLeaseLockedOrMaxAttemptRowCount,
445444
}
446445

447446
internal class ServerProperties

src/TriggerBinding/SqlTableChangeMonitor.cs

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,12 @@ private async Task GetTableChangesAsync(SqlConnection connection, CancellationTo
289289
using (SqlCommand updateTablesPreInvocationCommand = this.BuildUpdateTablesPreInvocation(connection, transaction))
290290
{
291291
var commandSw = Stopwatch.StartNew();
292-
await updateTablesPreInvocationCommand.ExecuteNonQueryAsyncWithLogging(this._logger, token, true);
292+
object result = await updateTablesPreInvocationCommand.ExecuteScalarAsyncWithLogging(this._logger, token, true);
293+
if (result != null)
294+
{
295+
// If we updated the LastSyncVersion we'll get a message back from the query, so log it here
296+
this._logger.LogDebug($"[PreInvocation] {result}");
297+
}
293298
setLastSyncVersionDurationMs = commandSw.ElapsedMilliseconds;
294299
}
295300

@@ -312,13 +317,13 @@ private async Task GetTableChangesAsync(SqlConnection connection, CancellationTo
312317
getChangesDurationMs = commandSw.ElapsedMilliseconds;
313318
}
314319
// Also get the number of rows that currently have lease locks on them
320+
// or are skipped because they have reached their max attempt count.
315321
// This can help with supportability by allowing a customer to see when a
316-
// trigger was processed successfully but returned fewer rows than expected
317-
// because of the rows being locked.
318-
int leaseLockedRowCount = await this.GetLeaseLockedRowCount(connection, transaction);
319-
if (rows.Count > 0 || leaseLockedRowCount > 0)
322+
// trigger was processed successfully but returned fewer rows than expected.
323+
string leaseLockedOrMaxAttemptRowCountMessage = await this.GetLeaseLockedOrMaxAttemptRowCountMessage(connection, transaction, token);
324+
if (rows.Count > 0 || leaseLockedOrMaxAttemptRowCountMessage != null)
320325
{
321-
this._logger.LogDebug($"Executed GetChangesCommand in GetTableChangesAsync. {rows.Count} available changed rows ({leaseLockedRowCount} found with lease locks).");
326+
this._logger.LogDebug($"Executed GetChangesCommand in GetTableChangesAsync. {rows.Count} available changed rows. {leaseLockedOrMaxAttemptRowCountMessage}");
322327
}
323328
// If changes were found, acquire leases on them.
324329
if (rows.Count > 0)
@@ -530,6 +535,7 @@ private async Task RenewLeasesAsync(SqlConnection connection, CancellationToken
530535

531536
if (rowsAffected > 0)
532537
{
538+
this._logger.LogDebug($"Renewed leases for {rowsAffected} rows");
533539
// Only send an event if we actually updated rows to reduce the overall number of events we send
534540
var measures = new Dictionary<TelemetryMeasureName, double>
535541
{
@@ -598,6 +604,10 @@ private async Task ClearRowsAsync(CancellationToken token)
598604
await this._rowsToProcessLock.WaitAsync(token);
599605
this._leaseRenewalCount = 0;
600606
this._state = State.CheckingForChanges;
607+
if (this._rowsToProcess.Count > 0)
608+
{
609+
this._logger.LogDebug($"Clearing internal state for {this._rowsToProcess.Count} rows");
610+
}
601611
this._rowsToProcess = new List<IReadOnlyDictionary<string, object>>();
602612
this._rowsToProcessLock.Release();
603613
}
@@ -635,7 +645,12 @@ private async Task ReleaseLeasesAsync(SqlConnection connection, CancellationToke
635645
using (SqlCommand updateTablesPostInvocationCommand = this.BuildUpdateTablesPostInvocation(connection, transaction, newLastSyncVersion))
636646
{
637647
var commandSw = Stopwatch.StartNew();
638-
await updateTablesPostInvocationCommand.ExecuteNonQueryAsyncWithLogging(this._logger, token);
648+
object result = await updateTablesPostInvocationCommand.ExecuteScalarAsyncWithLogging(this._logger, token);
649+
if (result != null)
650+
{
651+
// If we updated the LastSyncVersion we'll get a message back from the query, so log it here
652+
this._logger.LogDebug($"[PostInvocation] {result}");
653+
}
639654
updateLastSyncVersionDurationMs = commandSw.ElapsedMilliseconds;
640655
}
641656
transaction.Commit();
@@ -782,9 +797,12 @@ private SqlCommand BuildUpdateTablesPreInvocation(SqlConnection connection, SqlT
782797
WHERE UserFunctionID = '{this._userFunctionId}' AND UserTableID = {this._userTableId};
783798
784799
IF @last_sync_version < @min_valid_version
800+
BEGIN
785801
UPDATE {GlobalStateTableName}
786802
SET LastSyncVersion = @min_valid_version
787803
WHERE UserFunctionID = '{this._userFunctionId}' AND UserTableID = {this._userTableId};
804+
SELECT 'Updated LastSyncVersion from ' + CAST(@last_sync_version AS NVARCHAR) + ' to ' + CAST(@min_valid_version AS NVARCHAR);
805+
END
788806
";
789807

790808
return new SqlCommand(updateTablesPreInvocationQuery, connection, transaction);
@@ -842,44 +860,53 @@ LEFT OUTER JOIN {this._userTable.BracketQuotedFullName} AS u ON {userTableJoinCo
842860
}
843861

844862
/// <summary>
845-
/// Returns the number of changes(rows) on the user's table that are actively locked by other leases OR returns -1 on exception.
863+
/// Returns a message indicating the number of changes(rows) on the user's table that are actively locked by other leases or have
864+
/// reached the max attempts allowed for the row.
846865
/// </summary>
847866
/// <param name="connection">The connection to add to the SqlCommand</param>
848867
/// <param name="transaction">The transaction to add to the SqlCommand</param>
849-
/// <returns>The number of rows locked by leases or -1 on exception</returns>
850-
private async Task<int> GetLeaseLockedRowCount(SqlConnection connection, SqlTransaction transaction)
868+
/// <param name="token">Cancellation token</param>
869+
/// <returns>The message with the number of rows that were lease locked or were at the max attempt limit. Null if no such rows exist</returns>
870+
private async Task<string> GetLeaseLockedOrMaxAttemptRowCountMessage(SqlConnection connection, SqlTransaction transaction, CancellationToken token)
851871
{
852872
string leasesTableJoinCondition = string.Join(" AND ", this._primaryKeyColumns.Select(col => $"c.{col.name.AsBracketQuotedString()} = l.{col.name.AsBracketQuotedString()}"));
853-
int leaseLockedRowsCount = 0;
854-
long getLockedRowCountDurationMs = 0L;
855873
// Get the count of changes from CHANGETABLE that meet the following criteria:
856-
// * Not Null LeaseExpirationTime AND
857-
// * LeaseExpirationTime > Current Time
858-
string getLeaseLockedrowCountQuery = $@"
874+
// Lease locked:
875+
// * Have attempts remaining (Attempt count < Max attempts)
876+
// * NOT NULL LeaseExpirationTime
877+
// * LeaseExpirationTime > Current Time
878+
// Max Attempts reached:
879+
// * NULL LeaseExpirationTime OR LeaseExpirationTime <= Current Time
880+
// * No attempts remaining (Attempt count = Max attempts)
881+
string getLeaseLockedOrMaxAttemptRowCountQuery = $@"
859882
{AppLockStatements}
860883
861884
DECLARE @last_sync_version bigint;
862885
SELECT @last_sync_version = LastSyncVersion
863886
FROM {GlobalStateTableName}
864887
WHERE UserFunctionID = '{this._userFunctionId}' AND UserTableID = {this._userTableId};
865-
866-
SELECT COUNT(*)
888+
DECLARE @lease_locked_count int;
889+
DECLARE @max_attempts_count int;
890+
SELECT
891+
@lease_locked_count = COUNT(CASE WHEN l.{LeasesTableAttemptCountColumnName} < {MaxChangeProcessAttemptCount} AND l.{LeasesTableLeaseExpirationTimeColumnName} IS NOT NULL AND l.{LeasesTableLeaseExpirationTimeColumnName} > SYSDATETIME() THEN 1 ELSE NULL END),
892+
@max_attempts_count = COUNT(CASE WHEN (l.{LeasesTableLeaseExpirationTimeColumnName} IS NULL OR l.{LeasesTableLeaseExpirationTimeColumnName} <= SYSDATETIME()) AND l.{LeasesTableAttemptCountColumnName} = {MaxChangeProcessAttemptCount} THEN 1 ELSE NULL END)
867893
FROM CHANGETABLE(CHANGES {this._userTable.BracketQuotedFullName}, @last_sync_version) AS c
868-
LEFT OUTER JOIN {this._bracketedLeasesTableName} AS l ON {leasesTableJoinCondition}
869-
WHERE l.{LeasesTableLeaseExpirationTimeColumnName} IS NOT NULL AND l.{LeasesTableLeaseExpirationTimeColumnName} > SYSDATETIME()";
894+
LEFT OUTER JOIN {this._bracketedLeasesTableName} AS l ON {leasesTableJoinCondition};
895+
IF @lease_locked_count > 0 OR @max_attempts_count > 0
896+
BEGIN
897+
SELECT '(' + CAST(@lease_locked_count AS NVARCHAR) + ' found with lease locks and ' + CAST(@max_attempts_count AS NVARCHAR) + ' ignored because they''ve reached the max attempt limit)';
898+
END";
870899
try
871900
{
872-
using (var getLeaseLockedRowCountCommand = new SqlCommand(getLeaseLockedrowCountQuery, connection, transaction))
901+
using (var getLeaseLockedOrMaxAttemptsRowCountCommand = new SqlCommand(getLeaseLockedOrMaxAttemptRowCountQuery, connection, transaction))
873902
{
874-
var commandSw = Stopwatch.StartNew();
875-
leaseLockedRowsCount = (int)await getLeaseLockedRowCountCommand.ExecuteScalarAsyncWithLogging(this._logger, CancellationToken.None);
876-
getLockedRowCountDurationMs = commandSw.ElapsedMilliseconds;
903+
return (await getLeaseLockedOrMaxAttemptsRowCountCommand.ExecuteScalarAsyncWithLogging(this._logger, token))?.ToString();
877904
}
878905
}
879906
catch (Exception ex)
880907
{
881-
this._logger.LogError($"Failed to query count of lease locked changes for table '{this._userTable.FullName}' due to exception: {ex.GetType()}. Exception message: {ex.Message}");
882-
TelemetryInstance.TrackException(TelemetryErrorName.GetLeaseLockedRowCount, ex, null, new Dictionary<TelemetryMeasureName, double>() { { TelemetryMeasureName.GetLockedRowCountDurationMs, getLockedRowCountDurationMs } });
908+
this._logger.LogError($"Failed to query count of lease locked or max attempt changes for table '{this._userTable.FullName}' due to exception: {ex.GetType()}. Exception message: {ex.Message}");
909+
TelemetryInstance.TrackException(TelemetryErrorName.GetLeaseLockedOrMaxAttemptRowCount, ex);
883910
// This is currently only used for debugging, so ignore the exception if we can. If the error is a fatal one though then the connection or transaction will be
884911
// unusable so we have to let this bubble up so we can attempt to reconnect
885912
if (ex.IsFatalSqlException() || ex.IsDeadlockException() || connection.IsBrokenOrClosed())
@@ -888,11 +915,10 @@ LEFT OUTER JOIN {this._bracketedLeasesTableName} AS l ON {leasesTableJoinConditi
888915
}
889916
else
890917
{
891-
// If it's non-fatal though return a -1 instead of throwing since it isn't necessary to get the value
892-
leaseLockedRowsCount = -1;
918+
// If it's non-fatal though return null instead of throwing since it isn't necessary to get the value
919+
return null;
893920
}
894921
}
895-
return leaseLockedRowsCount;
896922
}
897923

898924
/// <summary>
@@ -952,7 +978,10 @@ WHEN NOT MATCHED THEN
952978
private SqlCommand BuildRenewLeasesCommand(SqlConnection connection, SqlTransaction transaction)
953979
{
954980
string matchCondition = string.Join(" OR ", this._rowMatchConditions.Take(this._rowsToProcess.Count));
955-
981+
if (string.IsNullOrEmpty(matchCondition))
982+
{
983+
this._logger.LogError($"MatchCondition resolved to empty with '{this._rowsToProcess.Count}' rowsToProcess.");
984+
}
956985
string renewLeasesQuery = $@"
957986
{AppLockStatements}
958987
@@ -1043,7 +1072,12 @@ LEFT OUTER JOIN {this._bracketedLeasesTableName} AS l ON {leasesTableJoinConditi
10431072
SET LastSyncVersion = {newLastSyncVersion}, LastAccessTime = GETUTCDATE()
10441073
WHERE UserFunctionID = '{this._userFunctionId}' AND UserTableID = {this._userTableId};
10451074
1075+
DECLARE @max_attempt_rows_to_be_deleted int;
1076+
SELECT @max_attempt_rows_to_be_deleted = COUNT(*) FROM {this._bracketedLeasesTableName} WHERE {LeasesTableChangeVersionColumnName} <= {newLastSyncVersion} AND {LeasesTableAttemptCountColumnName} = {MaxChangeProcessAttemptCount};
1077+
10461078
DELETE FROM {this._bracketedLeasesTableName} WHERE {LeasesTableChangeVersionColumnName} <= {newLastSyncVersion};
1079+
1080+
SELECT 'Updated LastSyncVersion from ' + CAST(@current_last_sync_version AS NVARCHAR) + ' to {newLastSyncVersion} MaxAttemptRowsToBeDeleted=' + CAST(@max_attempt_rows_to_be_deleted AS NVARCHAR);
10471081
END
10481082
";
10491083

0 commit comments

Comments
 (0)