diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/UnitOfWork/EFRecoverabilityIngestionUnitOfWork.cs b/src/ServiceControl.Persistence.EFCore/Implementation/UnitOfWork/EFRecoverabilityIngestionUnitOfWork.cs index 762ecbb7fe..6c5c4de880 100644 --- a/src/ServiceControl.Persistence.EFCore/Implementation/UnitOfWork/EFRecoverabilityIngestionUnitOfWork.cs +++ b/src/ServiceControl.Persistence.EFCore/Implementation/UnitOfWork/EFRecoverabilityIngestionUnitOfWork.cs @@ -40,7 +40,7 @@ public Task RecordFailedProcessingAttempt(MessageContext context, Groups = groups, HeadersJson = MessageHeaders.Write(processingAttempt.Headers), MessageId = processingAttempt.MessageId, - MessageType = TruncateForColumn(GetMetadata(processingAttempt, "MessageType")), + MessageType = TruncateTypeName(GetMetadata(processingAttempt, "MessageType")), TimeSent = GetMetadata(processingAttempt, "TimeSent"), ConversationId = GetMetadata(processingAttempt, "ConversationId"), SendingEndpointName = sendingEndpoint?.Name, @@ -70,13 +70,10 @@ public Task RecordSuccessfulRetry(string retriedMessageUniqueId, DateTime succee } // The MessageType column is length-bounded (ColumnLengths.ShortTextLength) so that it can be - // an index key serving sort=message_type. The cap is enforced here, like the body size limit - // above, because it is an EFCore storage limit the persister owns rather than a shared - // ingestion rule. EnclosedMessageTypes' first comma token is a type's full name, so the cap can - // only ever bite on pathological generic names, where a truncated sort key still sorts and - // groups consistently. - static string? TruncateForColumn(string? value) => - value is { Length: > ColumnLengths.ShortTextLength } ? value[..ColumnLengths.ShortTextLength] : value; + // an index key serving sort=message_type. This field contains a type name, so it's more useful to + // truncate from the start of the name instead of the end. + static string? TruncateTypeName(string? value) => + value is { Length: > ColumnLengths.ShortTextLength } ? value[^ColumnLengths.ShortTextLength..] : value; static T? GetMetadata(FailedMessage.ProcessingAttempt processingAttempt, string key) => processingAttempt.MessageMetadata.TryGetValue(key, out var value) && value is T typed ? typed : default; diff --git a/src/ServiceControl.Persistence.EFCore/Infrastructure/FailureGroupQueries.cs b/src/ServiceControl.Persistence.EFCore/Infrastructure/FailureGroupQueries.cs index 0c31a96178..e08f1dc240 100644 --- a/src/ServiceControl.Persistence.EFCore/Infrastructure/FailureGroupQueries.cs +++ b/src/ServiceControl.Persistence.EFCore/Infrastructure/FailureGroupQueries.cs @@ -8,7 +8,7 @@ static class FailureGroupQueries public const int MaxGroups = 200; /// - /// Aggregate with Title in the group key. Used by + /// Aggregate with Title in the group key. Used by /// where a single group is fetched and the nvarchar(max) Title cost is negligible. /// public static IQueryable AggregateGroups(this IQueryable groups, IQueryable messages) => @@ -53,8 +53,8 @@ into aggregate /// sealed class GroupSummary { - public string Id { get; set; } = null!; - public string Type { get; set; } = null!; + public required string Id { get; set; } + public required string Type { get; set; } public int Count { get; set; } public DateTime First { get; set; } public DateTime Last { get; set; } diff --git a/src/ServiceControl.Persistence.Tests/EFCore/ErrorIngestionTests.cs b/src/ServiceControl.Persistence.Tests/EFCore/ErrorIngestionTests.cs index 3dda29fcf2..b926be9e25 100644 --- a/src/ServiceControl.Persistence.Tests/EFCore/ErrorIngestionTests.cs +++ b/src/ServiceControl.Persistence.Tests/EFCore/ErrorIngestionTests.cs @@ -75,7 +75,8 @@ public async Task An_over_length_message_type_is_capped_in_the_column_but_kept_i using (Assert.EnterMultipleScope()) { - Assert.That(row.MessageType, Is.EqualTo(overLengthType[..450])); + Assert.That(row.MessageType!.Length, Is.EqualTo(450)); + Assert.That(overLengthType.EndsWith(row.MessageType), Is.True, "the column is truncated from the start"); Assert.That(row.HeadersJson, Does.Contain(overLengthType), "the headers keep the complete type"); } }