Skip to content
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: 3 additions & 2 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ On Windows, [PackagedRavenDBTests](../src/ServiceControlInstaller.Engine.UnitTes

## Persistence tests

Persistence tests check assumptions at the persistence seam level by exercising each persister.
For local setup details, see [Local testing of persistence providers](testing-persistence.md).
Persistence tests check assumptions at the persistence seam level by exercising each persister. For local setup details, see [Local testing of persistence providers](testing-persistence.md).

The RavenDB persistence test projects contain an `IndexDefinitionApprovals` test that approves the definition of every index the persister deploys. RavenDB rebuilds an index side by side when its definition changes, and the rebuild of a large database can take days. When the test fails, record the changed indexes in the upgrade guide of the next release before you approve the new definition. An operator who locked an index must unlock it to receive the change.

The SQL Server and PostgreSQL persistence test projects contain a `SlowMigrationOperationApprovals` test. It approves the migration steps that can block writes on a large table: index, primary key, and unique constraint changes, new foreign keys and check constraints, altered columns, raw SQL, and new columns that are NOT NULL with a default or are stored computed columns. Other steps, such as renames and drops of columns, tables, and constraints, are not listed. The test lists these steps for each migration and leaves out steps on a table that the same migration creates, because that table is empty. Raw SQL is always listed. When the test fails because a step changed, record the change in the upgrade guide of the next release before you approve the new list. If a step changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace ServiceControl.Audit.Persistence.RavenDB
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
using Raven.Client.Documents.Conventions;
using ServiceControl.RavenDB;

sealed class RavenExternalPersistenceLifecycle(DatabaseConfiguration configuration) : IRavenPersistenceLifecycle, IRavenDocumentStoreProvider, IDisposable
Expand Down Expand Up @@ -40,10 +39,7 @@ public async Task Initialize(CancellationToken cancellationToken = default)
Database = configuration.Name,
Urls = [configuration.ServerConfiguration.ConnectionString],
Certificate = RavenClientCertificate.FindClientCertificate(configuration.ServerConfiguration),
Conventions = new DocumentConventions
{
SaveEnumsAsIntegers = true
}
Conventions = DocumentStoreConventions.Create()
};

documentStore = store.Initialize();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
FailedAuditImportIndex
Maps:
docs.FailedAuditImports.Select(cc => new {
Id = Id(cc)
})

MessagesViewIndex
Maps:
docs.ProcessedMessages.Select(message => new {
MessageId = ((string) message.MessageMetadata["MessageId"]),
MessageType = ((string) message.MessageMetadata["MessageType"]),
IsSystemMessage = ((bool) message.MessageMetadata["IsSystemMessage"]),
Status = ((bool) message.MessageMetadata["IsRetried"]) ? 4 : 3,
TimeSent = ((DateTime) message.MessageMetadata["TimeSent"]),
ProcessedAt = message.ProcessedAt,
ReceivingEndpointName = ((message.MessageMetadata["ReceivingEndpoint"])).Name,
SendingEndpointName = ((message.MessageMetadata["SendingEndpoint"])).Name,
CriticalTime = ((TimeSpan ? ) message.MessageMetadata["CriticalTime"]),
ProcessingTime = ((TimeSpan ? ) message.MessageMetadata["ProcessingTime"]),
DeliveryTime = ((TimeSpan ? ) message.MessageMetadata["DeliveryTime"]),
Query = Enumerable.ToArray(DynamicEnumerable.Union(message.MessageMetadata.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(_ => _.Value.ToString()), new string[] {
String.Join(" ", message.Headers.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(x => x.Value))
})),
ConversationId = ((string) message.MessageMetadata["ConversationId"])
})
Fields:
Query: Indexing=Search, Analyzer=Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=85089178b9ac3181

MessagesViewIndexWithFullTextSearch
Maps:
docs.ProcessedMessages.Select(message => new {
MessageId = ((string) message.MessageMetadata["MessageId"]),
MessageType = ((string) message.MessageMetadata["MessageType"]),
IsSystemMessage = ((bool) message.MessageMetadata["IsSystemMessage"]),
Status = ((bool) message.MessageMetadata["IsRetried"]) ? 4 : 3,
TimeSent = ((DateTime) message.MessageMetadata["TimeSent"]),
ProcessedAt = message.ProcessedAt,
ReceivingEndpointName = ((message.MessageMetadata["ReceivingEndpoint"])).Name,
SendingEndpointName = ((message.MessageMetadata["SendingEndpoint"])).Name,
CriticalTime = ((TimeSpan ? ) message.MessageMetadata["CriticalTime"]),
ProcessingTime = ((TimeSpan ? ) message.MessageMetadata["ProcessingTime"]),
DeliveryTime = ((TimeSpan ? ) message.MessageMetadata["DeliveryTime"]),
Query = Enumerable.ToArray(DynamicEnumerable.Union(message.MessageMetadata.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(_ => _.Value.ToString()), new string[] {
String.Join(" ", message.Headers.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(x => x.Value)),
this.LoadAttachment(message, "body").GetContentAsString()
})),
ConversationId = ((string) message.MessageMetadata["ConversationId"])
})
Fields:
Query: Indexing=Search, Analyzer=Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=85089178b9ac3181

SagaDetailsIndex
Maps:
docs.SagaHistories.Select(doc => new {
SagaId = doc.SagaId,
Id = doc.SagaId,
SagaType = doc.SagaType,
Changes = doc.Changes
})
docs.SagaSnapshots.Select(doc => new {
SagaId = doc.SagaId,
Id = doc.SagaId,
SagaType = doc.SagaType,
Changes = new[] {
new {
Endpoint = doc.Endpoint,
FinishTime = doc.FinishTime,
InitiatingMessage = doc.InitiatingMessage,
OutgoingMessages = doc.OutgoingMessages,
StartTime = doc.StartTime,
StateAfterChange = doc.StateAfterChange,
Status = doc.Status
}
}
})
Reduce:
results.GroupBy(result => result.SagaId).Select(g => new {
g = g,
first = DynamicEnumerable.FirstOrDefault(g)
}).Select(this0 => new {
Id = this0.first.SagaId,
SagaId = this0.first.SagaId,
SagaType = this0.first.SagaType,
Changes = Enumerable.ToList(DynamicEnumerable.OrderByDescending(this0.g.SelectMany(x => x.Changes), x0 => x0.FinishTime).Take(50000))
})
Fields:
Changes: Indexing=No
SagaId: Indexing=Exact
SagaType: Indexing=No

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ServiceControl.Audit.Persistence.Tests;

using NUnit.Framework;
using Particular.Approvals;
using Persistence.RavenDB;
using TestHelper;

[TestFixture]
class IndexDefinitionApprovals
{
// A changed definition makes RavenDB rebuild the index side by side at the next start-up, which can take days.
// Record the change in the upgrade guide before approving it, see docs/testing.md.
[Test]
public void Verify_index_definitions_are_unchanged() =>
Approver.Verify(IndexDefinitionText.Render(typeof(DatabaseSetup).Assembly));
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace ServiceControl.Persistence.RavenDB
using System.Threading;
using System.Threading.Tasks;
using Raven.Client.Documents;
using Raven.Client.Documents.Conventions;
using ServiceControl.RavenDB;

sealed class RavenExternalPersistenceLifecycle(RavenPersisterSettings settings) : IRavenPersistenceLifecycle, IRavenDocumentStoreProvider, IDisposable
Expand Down Expand Up @@ -40,10 +39,7 @@ public async Task Initialize(CancellationToken cancellationToken = default)
Database = settings.DatabaseName,
Urls = [settings.ConnectionString],
Certificate = RavenClientCertificate.FindClientCertificate(settings),
Conventions = new DocumentConventions
{
SaveEnumsAsIntegers = true
}
Conventions = DocumentStoreConventions.Create()
};

documentStore = store.Initialize();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
ArchivedGroupsViewIndex
Maps:
docs.FailedMessages.Where(doc => doc.Status == 4).Select(doc => new {
doc = doc,
failureTimes = doc.ProcessingAttempts.Select(x => x.FailureDetails.TimeOfFailure)
}).SelectMany(this0 => this0.doc.FailureGroups, (this0, failureGroup) => new {
Id = failureGroup.Id,
Title = failureGroup.Title,
Count = 1,
First = DynamicEnumerable.Min(this0.failureTimes),
Last = DynamicEnumerable.Max(this0.failureTimes),
Type = failureGroup.Type
})
Reduce:
results.GroupBy(result => new {
Id = result.Id,
Title = result.Title,
Type = result.Type
}).Select(g => new {
Id = g.Key.Id,
Title = g.Key.Title,
Count = Enumerable.Sum(g, x => ((int) x.Count)),
First = DynamicEnumerable.Min(g, x0 => ((DateTime) x0.First)),
Last = DynamicEnumerable.Max(g, x1 => ((DateTime) x1.Last)),
Type = g.Key.Type
})

CustomChecksIndex
Maps:
docs.CustomChecks.Select(cc => new {
Status = cc.Status,
ReportedAt = cc.ReportedAt,
Category = cc.Category,
CustomCheckId = cc.CustomCheckId
})

EndpointIndex
Maps:
docs.EndpointDocuments.Select(message => new {
ThroughputSource = message.EndpointId.ThroughputSource,
Name = message.EndpointId.Name
})

FailedErrorImportIndex
Maps:
docs.FailedErrorImports.Select(cc => new {
Id = Id(cc)
})

FailedMessageFacetsIndex
Maps:
docs.FailedMessages.Where(failure => failure.Status == 1).Select(failure => new {
failure = failure,
t = ((DynamicEnumerable.LastOrDefault(failure.ProcessingAttempts)).MessageMetadata["ReceivingEndpoint"])
}).Select(this0 => new {
Name = this0.t.Name,
Host = this0.t.Host,
MessageType = (DynamicEnumerable.LastOrDefault(this0.failure.ProcessingAttempts)).MessageMetadata["MessageType"]
})
Fields:
Host: Indexing=Exact
MessageType: Indexing=Exact
Name: Indexing=Exact

FailedMessageRetries/ByBatch
Maps:
docs.FailedMessageRetries.Select(doc => new {
RetryBatchId = doc.RetryBatchId
})

FailedMessageViewIndex
Maps:
docs.FailedMessages.Select(message => new {
message = message,
processingAttemptsLast = DynamicEnumerable.LastOrDefault(message.ProcessingAttempts)
}).Select(this0 => new {
MessageId = this0.processingAttemptsLast.MessageMetadata["MessageId"],
MessageType = this0.processingAttemptsLast.MessageMetadata["MessageType"],
Status = this0.message.Status,
TimeSent = ((DateTime) this0.processingAttemptsLast.MessageMetadata["TimeSent"]),
ReceivingEndpointName = ((this0.processingAttemptsLast.MessageMetadata["ReceivingEndpoint"])).Name,
QueueAddress = this0.processingAttemptsLast.FailureDetails.AddressOfFailingEndpoint,
TimeOfFailure = this0.processingAttemptsLast.FailureDetails.TimeOfFailure,
LastModified = (this.MetadataFor(this0.message).Value < DateTime > ("@last-modified")).Ticks
})

FailedMessages/ByGroup
Maps:
docs.FailedMessages.Select(doc => new {
doc = doc,
processingAttemptsLast = DynamicEnumerable.LastOrDefault(doc.ProcessingAttempts)
}).SelectMany(this0 => this0.doc.FailureGroups, (this0, failureGroup) => new {
Id = Id(this0.doc),
MessageId = this0.doc.UniqueMessageId,
FailureGroupId = failureGroup.Id,
FailureGroupName = failureGroup.Title,
Status = this0.doc.Status,
MessageType = ((string) this0.processingAttemptsLast.MessageMetadata["MessageType"]),
TimeSent = ((DateTime) this0.processingAttemptsLast.MessageMetadata["TimeSent"]),
TimeOfFailure = this0.processingAttemptsLast.FailureDetails.TimeOfFailure,
LastModified = (this.MetadataFor(this0.doc).Value < DateTime > ("@last-modified")).Ticks
})
Fields:
__all_fields: Storage=Yes

FailureGroupsViewIndex
Maps:
docs.FailedMessages.Where(doc => doc.Status == 1).Select(doc => new {
doc = doc,
failureTimes = doc.ProcessingAttempts.Select(x => x.FailureDetails.TimeOfFailure)
}).SelectMany(this0 => this0.doc.FailureGroups, (this0, failureGroup) => new {
Id = failureGroup.Id,
Title = failureGroup.Title,
Count = 1,
First = DynamicEnumerable.Min(this0.failureTimes),
Last = DynamicEnumerable.Max(this0.failureTimes),
Type = failureGroup.Type
})
Reduce:
results.GroupBy(result => new {
Id = result.Id,
Title = result.Title,
Type = result.Type
}).Select(g => new {
Id = g.Key.Id,
Title = g.Key.Title,
Count = Enumerable.Sum(g, x => ((int) x.Count)),
First = DynamicEnumerable.Min(g, x0 => ((DateTime) x0.First)),
Last = DynamicEnumerable.Max(g, x1 => ((DateTime) x1.Last)),
Type = g.Key.Type
})

GroupCommentIndex
Maps:
docs.GroupComments.Select(gc => new {
Id = Id(gc),
Comment = gc.Comment
})

KnownEndpointIndex
Maps:
docs.KnownEndpoints.Select(message => new {
EndpointDetails_Name = message.EndpointDetails.Name,
EndpointDetails_Host = message.EndpointDetails.Host,
HostDisplayName = message.HostDisplayName,
Monitored = message.Monitored
})

MessagesViewIndex
Maps:
docs.FailedMessages.Select(message => new {
message = message,
last = DynamicEnumerable.LastOrDefault(message.ProcessingAttempts)
}).Select(this0 => new {
MessageId = this0.last.MessageId,
MessageType = ((string) this0.last.MessageMetadata["MessageType"]),
IsSystemMessage = ((bool) this0.last.MessageMetadata["IsSystemMessage"]),
Status = this0.message.Status == 4 ? 5 : (this0.message.Status == 2 ? 4 : (this0.message.ProcessingAttempts.Count == 1 ? 1 : 2)),
TimeSent = ((DateTime) this0.last.MessageMetadata["TimeSent"]),
ProcessedAt = this0.last.AttemptedAt,
ReceivingEndpointName = ((this0.last.MessageMetadata["ReceivingEndpoint"])).Name,
CriticalTime = ((TimeSpan ? ) this0.last.MessageMetadata["CriticalTime"]),
ProcessingTime = ((TimeSpan ? ) this0.last.MessageMetadata["ProcessingTime"]),
DeliveryTime = ((TimeSpan ? ) this0.last.MessageMetadata["DeliveryTime"]),
Query = Enumerable.ToArray(DynamicEnumerable.Union(this0.last.MessageMetadata.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(_ => _.Value.ToString()), new string[] {
String.Join(" ", this0.last.Headers.ToDictionary(e1 => e1.Key, e1 => e1.Value).Select(x => x.Value))
})),
ConversationId = ((string) this0.last.MessageMetadata["ConversationId"])
})
Fields:
Query: Indexing=Search

QueueAddressIndex
Maps:
docs.FailedMessages.Select(message => new {
message = message,
processingAttemptsLast = DynamicEnumerable.LastOrDefault(message.ProcessingAttempts)
}).Select(this0 => new {
PhysicalAddress = this0.processingAttemptsLast.FailureDetails.AddressOfFailingEndpoint,
FailedMessageCount = 1
})
Reduce:
results.GroupBy(result => result.PhysicalAddress).Select(g => new {
PhysicalAddress = g.Key,
FailedMessageCount = Enumerable.Sum(g, m => ((int) m.FailedMessageCount))
})

RetryBatches/ByStatus/ReduceInitialBatchSize
Maps:
docs.RetryBatches.Select(doc => new {
RequestId = doc.RequestId,
RetryType = doc.RetryType,
HasStagingBatches = doc.Status == 2,
HasForwardingBatches = doc.Status == 3,
InitialBatchSize = doc.InitialBatchSize,
Originator = doc.Originator,
Classifier = doc.Classifier,
StartTime = doc.StartTime,
Last = doc.Last
})
Reduce:
results.GroupBy(result => new {
RequestId = result.RequestId,
RetryType = result.RetryType
}).Select(g => new {
RequestId = g.Key.RequestId,
RetryType = g.Key.RetryType,
Originator = (DynamicEnumerable.FirstOrDefault(g)).Originator,
HasStagingBatches = Enumerable.Any(g, x => x.HasStagingBatches),
HasForwardingBatches = Enumerable.Any(g, x0 => x0.HasForwardingBatches),
InitialBatchSize = Enumerable.Sum(g, x1 => ((int) x1.InitialBatchSize)),
StartTime = (DynamicEnumerable.FirstOrDefault(g)).StartTime,
Last = DynamicEnumerable.Max(g, x2 => ((DateTime) x2.Last)),
Classifier = (DynamicEnumerable.FirstOrDefault(g)).Classifier
})

RetryBatches/ByStatusAndSession
Maps:
docs.RetryBatches.Select(doc => new {
RetrySessionId = doc.RetrySessionId,
Status = doc.Status
})

Loading
Loading