Skip to content

Commit ef3ef7a

Browse files
authored
Use Lucene search engine for new RavenDB databases and flag indexes still on Corax (#5833)
* ✨ Use Lucene search engine for newly created RavenDB databases New databases are created with Indexing.Static/Auto.SearchEngineType=Lucene: Lucene indexes are smaller, use less memory and are faster for the index definitions ServiceControl uses. Existing databases keep the engine they currently have configured, so no full index rebuild is triggered on upgrade. * ✨ Log a warning at startup when RavenDB indexes still use Corax New databases use Lucene, existing databases keep their configured search engine. Operators of existing databases now get a WARN at startup listing the indexes still on Corax so they can plan the transition to Lucene. * 📝 Clarify rebuild impact in Corax search engine warning * ✨ Add custom check reporting RavenDB indexes still using Corax Surfaces the same information as the startup warning in ServicePulse via a custom check on both the Primary (main + throughput database) and Audit instances, so operators see it without inspecting logs.
1 parent d3a92b6 commit ef3ef7a

10 files changed

Lines changed: 143 additions & 12 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks;
2+
3+
using System;
4+
using System.Linq;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using NServiceBus.CustomChecks;
8+
using ServiceControl.RavenDB;
9+
10+
class CheckRavenDBSearchEngine(IRavenDocumentStoreProvider documentStoreProvider, DatabaseConfiguration databaseConfiguration) : CustomCheck("Audit Database Search Engine", "ServiceControl.Audit Health", TimeSpan.FromHours(1))
11+
{
12+
public override async Task<CheckResult> PerformCheck(CancellationToken cancellationToken = default)
13+
{
14+
var documentStore = await documentStoreProvider.GetDocumentStore(cancellationToken);
15+
16+
var coraxIndexes = await StartupChecks.FindIndexesUsingCorax(documentStore, databaseConfiguration.Name, cancellationToken);
17+
18+
return coraxIndexes.Length == 0
19+
? CheckResult.Pass
20+
: CheckResult.Failed(StartupChecks.CoraxIndexesMessage(coraxIndexes.Select(i => $"{databaseConfiguration.Name}/{i}")));
21+
}
22+
}

‎src/ServiceControl.Audit.Persistence.RavenDB/DatabaseSetup.cs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Raven.Client.ServerWide.Operations;
1616
using Raven.Client.ServerWide.Operations.Configuration;
1717
using Indexes;
18+
using ServiceControl.RavenDB;
1819
using SagaAudit;
1920

2021
class DatabaseSetup(DatabaseConfiguration configuration)
@@ -27,6 +28,8 @@ public async Task Execute(IDocumentStore documentStore, CancellationToken cancel
2728

2829
await CreateIndexes(documentStore, configuration.EnableFullTextSearch, cancellationToken);
2930

31+
await StartupChecks.WarnIfIndexesUseCorax(documentStore, configuration.Name, cancellationToken);
32+
3033
await LicenseStatusCheck.WaitForLicenseOrThrow(documentStore, cancellationToken);
3134
await ConfigureExpiration(documentStore, cancellationToken);
3235
}
@@ -41,7 +44,9 @@ async Task CreateDatabase(IDocumentStore documentStore, string databaseName, Can
4144
{
4245
var databaseRecord = new DatabaseRecord(databaseName);
4346

44-
SetSearchEngineType(databaseRecord, SearchEngineType.Corax);
47+
// New databases use Lucene: smaller indexes, lower memory usage and faster for our index definitions.
48+
// Existing databases keep the engine they were created with, see UpdateDatabaseSettings.
49+
SetSearchEngineType(databaseRecord, SearchEngineType.Lucene);
4550

4651
await documentStore.Maintenance.Server.SendAsync(new CreateDatabaseOperation(databaseRecord), cancellationToken);
4752
}
@@ -56,6 +61,9 @@ async Task UpdateDatabaseSettings(IDocumentStore documentStore, string databaseN
5661
{
5762
var databaseRecord = await documentStore.Maintenance.Server.SendAsync(new GetDatabaseRecordOperation(databaseName), cancellationToken) ?? throw new InvalidOperationException($"Database '{databaseName}' does not exist.");
5863

64+
// Existing databases keep their configured search engine. Changing it would trigger a full rebuild of all
65+
// indexes, which can take a long time and a lot of resources on large databases. Databases created before the
66+
// search engine was pinned explicitly get Corax, which was the default at the time.
5967
if (!SetSearchEngineType(databaseRecord, SearchEngineType.Corax))
6068
{
6169
return;

‎src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistence.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public void AddPersistence(IServiceCollection services)
2222
endpointConfiguration.AddCustomCheck<CheckFreeDiskSpace>();
2323
endpointConfiguration.AddCustomCheck<CheckMinimumStorageRequiredForIngestion>();
2424
endpointConfiguration.AddCustomCheck<CheckRavenDBIndexLag>();
25+
endpointConfiguration.AddCustomCheck<CheckRavenDBSearchEngine>();
2526
}
2627

2728
services.AddSingleton<IAuditDataStore, RavenAuditDataStore>();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ServiceControl.Audit Health: Audit Database Index Lag
2+
ServiceControl.Audit Health: Audit Database Search Engine
23
ServiceControl.Audit Health: Audit Message Ingestion Process
34
ServiceControl.Audit Health: RavenDB dirty memory
45
Storage space: ServiceControl.Audit database

‎src/ServiceControl.Audit.Persistence.Tests.RavenDB/IndexSetupTests.cs‎

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,43 @@ namespace ServiceControl.Audit.Persistence.Tests;
99
using Raven.Client.Documents.Operations.Indexes;
1010
using Raven.Client.Exceptions;
1111
using Raven.Client.Exceptions.Documents.Indexes;
12+
using ServiceControl.RavenDB;
1213

1314
[TestFixture]
1415
class IndexSetupTests : PersistenceTestFixture
1516
{
1617
[Test]
17-
public async Task Corax_should_be_the_default_search_engine_type()
18+
public async Task Lucene_should_be_the_default_search_engine_type_for_new_databases()
1819
{
1920
var indexes = await configuration.DocumentStore.Maintenance.SendAsync(new GetIndexesOperation(0, int.MaxValue));
2021

2122
foreach (var index in indexes)
2223
{
2324
var indexStats = await configuration.DocumentStore.Maintenance.SendAsync(new GetIndexStatisticsOperation(DatabaseSetup.MessagesViewIndexWithFulltextSearchName));
24-
Assert.That(indexStats.SearchEngineType, Is.EqualTo(SearchEngineType.Corax), $"{index.Name} is not using Corax");
25+
Assert.That(indexStats.SearchEngineType, Is.EqualTo(SearchEngineType.Lucene), $"{index.Name} is not using Lucene");
2526
}
2627
}
2728

29+
[Test]
30+
public async Task Startup_check_should_not_report_corax_indexes_for_new_database()
31+
{
32+
var coraxIndexes = await StartupChecks.FindIndexesUsingCorax(configuration.DocumentStore, configuration.DocumentStore.Database, TestTimeoutCancellationToken);
33+
34+
Assert.That(coraxIndexes, Is.Empty);
35+
}
36+
37+
[Test]
38+
public async Task Startup_check_should_report_indexes_using_corax()
39+
{
40+
var index = new MessagesViewIndexWithFullTextSearch { Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Corax.ToString() } };
41+
42+
await UpdateIndex(index);
43+
44+
var coraxIndexes = await StartupChecks.FindIndexesUsingCorax(configuration.DocumentStore, configuration.DocumentStore.Database, TestTimeoutCancellationToken);
45+
46+
Assert.That(coraxIndexes, Is.EqualTo(new[] { index.IndexName }));
47+
}
48+
2849
[Test]
2950
public async Task Free_text_search_index_should_be_used_by_default()
3051
{
@@ -50,49 +71,49 @@ public async Task Free_text_search_index_can_be_opted_out_from()
5071
[Test]
5172
public async Task Indexes_should_be_reset_on_setup()
5273
{
53-
var index = new MessagesViewIndexWithFullTextSearch { Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Lucene.ToString() } };
74+
var index = new MessagesViewIndexWithFullTextSearch { Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Corax.ToString() } };
5475

5576
var indexWithCustomConfigStats = await UpdateIndex(index);
5677

57-
Assert.That(indexWithCustomConfigStats.SearchEngineType, Is.EqualTo(SearchEngineType.Lucene));
78+
Assert.That(indexWithCustomConfigStats.SearchEngineType, Is.EqualTo(SearchEngineType.Corax));
5879

5980
await DatabaseSetup.CreateIndexes(configuration.DocumentStore, true, TestTimeoutCancellationToken);
6081

6182
await WaitForIndexDefinitionUpdate(indexWithCustomConfigStats);
6283

6384
var indexAfterResetStats = await configuration.DocumentStore.Maintenance.SendAsync(new GetIndexStatisticsOperation(index.IndexName));
6485

65-
Assert.That(indexAfterResetStats.SearchEngineType, Is.EqualTo(SearchEngineType.Corax));
86+
Assert.That(indexAfterResetStats.SearchEngineType, Is.EqualTo(SearchEngineType.Lucene));
6687
}
6788

6889
[Test]
6990
public async Task Indexes_should_not_be_reset_on_setup_when_locked_as_ignore()
7091
{
7192
var index = new MessagesViewIndexWithFullTextSearch
7293
{
73-
Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Lucene.ToString() },
94+
Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Corax.ToString() },
7495
LockMode = IndexLockMode.LockedIgnore
7596
};
7697

7798
var indexStatsBefore = await UpdateIndex(index);
7899

79-
Assert.That(indexStatsBefore.SearchEngineType, Is.EqualTo(SearchEngineType.Lucene));
100+
Assert.That(indexStatsBefore.SearchEngineType, Is.EqualTo(SearchEngineType.Corax));
80101

81102
await DatabaseSetup.CreateIndexes(configuration.DocumentStore, true, TestTimeoutCancellationToken);
82103

83104
// raven will ignore the update since index was locked, so best we can do is wait a bit and check that settings hasn't changed
84105
await Task.Delay(1000);
85106

86107
var indexStatsAfter = await configuration.DocumentStore.Maintenance.SendAsync(new GetIndexStatisticsOperation(index.IndexName));
87-
Assert.That(indexStatsAfter.SearchEngineType, Is.EqualTo(SearchEngineType.Lucene));
108+
Assert.That(indexStatsAfter.SearchEngineType, Is.EqualTo(SearchEngineType.Corax));
88109
}
89110

90111
[Test]
91112
public async Task Indexes_should_not_be_reset_on_setup_when_locked_as_error()
92113
{
93114
var index = new MessagesViewIndexWithFullTextSearch
94115
{
95-
Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Lucene.ToString() },
116+
Configuration = { ["Indexing.Static.SearchEngineType"] = SearchEngineType.Corax.ToString() },
96117
LockMode = IndexLockMode.LockedError
97118
};
98119

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace ServiceControl.Persistence.RavenDB.CustomChecks;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using NServiceBus.CustomChecks;
8+
using ServiceControl.RavenDB;
9+
10+
class CheckRavenDBSearchEngine(IRavenDocumentStoreProvider documentStoreProvider, RavenPersisterSettings settings) : CustomCheck("Error Database Search Engine", "ServiceControl Health", TimeSpan.FromHours(1))
11+
{
12+
public override async Task<CheckResult> PerformCheck(CancellationToken cancellationToken = default)
13+
{
14+
var documentStore = await documentStoreProvider.GetDocumentStore(cancellationToken);
15+
16+
var coraxIndexes = new List<string>();
17+
18+
foreach (var databaseName in new[] { settings.DatabaseName, settings.ThroughputDatabaseName })
19+
{
20+
foreach (var indexName in await StartupChecks.FindIndexesUsingCorax(documentStore, databaseName, cancellationToken))
21+
{
22+
coraxIndexes.Add($"{databaseName}/{indexName}");
23+
}
24+
}
25+
26+
return coraxIndexes.Count == 0
27+
? CheckResult.Pass
28+
: CheckResult.Failed(StartupChecks.CoraxIndexesMessage(coraxIndexes));
29+
}
30+
}

‎src/ServiceControl.Persistence.RavenDB/DatabaseSetup.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace ServiceControl.Persistence.RavenDB
1010
using Raven.Client.ServerWide;
1111
using Raven.Client.ServerWide.Operations;
1212
using Raven.Client.ServerWide.Operations.Configuration;
13+
using ServiceControl.RavenDB;
1314

1415
class DatabaseSetup(RavenPersisterSettings settings, IDocumentStore documentStore)
1516
{
@@ -23,6 +24,9 @@ public async Task Execute(CancellationToken cancellationToken = default)
2324

2425
await IndexCreation.CreateIndexesAsync(typeof(DatabaseSetup).Assembly, documentStore, null, null, cancellationToken);
2526

27+
await StartupChecks.WarnIfIndexesUseCorax(documentStore, settings.DatabaseName, cancellationToken);
28+
await StartupChecks.WarnIfIndexesUseCorax(documentStore, settings.ThroughputDatabaseName, cancellationToken);
29+
2630
await LicenseStatusCheck.WaitForLicenseOrThrow(documentStore, cancellationToken);
2731
await ConfigureExpiration(settings, cancellationToken);
2832
}
@@ -36,8 +40,11 @@ async Task CreateDatabase(string databaseName, CancellationToken cancellationTok
3640
try
3741
{
3842
var databaseRecord = new DatabaseRecord(databaseName);
39-
databaseRecord.Settings.Add("Indexing.Auto.SearchEngineType", "Corax");
40-
databaseRecord.Settings.Add("Indexing.Static.SearchEngineType", "Corax");
43+
44+
// New databases use Lucene: smaller indexes, lower memory usage and faster for our index definitions.
45+
// Existing databases keep the engine they were created with, see UpdateDatabaseSettings.
46+
databaseRecord.Settings.Add("Indexing.Auto.SearchEngineType", "Lucene");
47+
databaseRecord.Settings.Add("Indexing.Static.SearchEngineType", "Lucene");
4148

4249
await documentStore.Maintenance.Server.SendAsync(new CreateDatabaseOperation(databaseRecord), cancellationToken);
4350
}
@@ -57,6 +64,9 @@ async Task UpdateDatabaseSettings(string databaseName, CancellationToken cancell
5764
throw new InvalidOperationException($"Database '{databaseName}' does not exist.");
5865
}
5966

67+
// Existing databases keep their configured search engine. Changing it would trigger a full rebuild of all
68+
// indexes, which can take a long time and a lot of resources on large databases. Databases created before the
69+
// search engine was pinned explicitly get Corax, which was the default at the time.
6070
var updated = false;
6171

6272
updated |= dbRecord.Settings.TryAdd("Indexing.Auto.SearchEngineType", "Corax");

‎src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public void AddPersistence(IServiceCollection services)
4848
services.AddCustomCheck<CheckFreeDiskSpace>();
4949
services.AddCustomCheck<CheckMinimumStorageRequiredForIngestion>();
5050
services.AddCustomCheck<CheckDirtyMemory>();
51+
services.AddCustomCheck<CheckRavenDBSearchEngine>();
5152

5253
services.AddSingleton<MemoryInformationRetriever>();
5354
services.AddSingleton<OperationsManager>();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
ServiceControl Health: Error Database Index Errors
22
ServiceControl Health: Error Database Index Lag
3+
ServiceControl Health: Error Database Search Engine
34
ServiceControl Health: Message Ingestion Process
45
ServiceControl Health: RavenDB dirty memory
56
Storage space: ServiceControl database

‎src/ServiceControl.RavenDB/StartupChecks.cs‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
namespace ServiceControl.RavenDB
22
{
3+
using System.Collections.Generic;
34
using System.Reflection;
45
using System.Threading;
6+
using Microsoft.Extensions.Logging;
57
using Raven.Client.Documents;
8+
using Raven.Client.Documents.Indexes;
9+
using Raven.Client.Documents.Operations.Indexes;
610
using Raven.Client.ServerWide.Operations;
11+
using ServiceControl.Infrastructure;
712

813
public static class StartupChecks
914
{
15+
public static async Task WarnIfIndexesUseCorax(IDocumentStore store, string databaseName, CancellationToken cancellationToken = default)
16+
{
17+
// New databases are created with Lucene, existing databases keep whatever search engine they were created
18+
// with as switching would trigger a full rebuild of all indexes. Let the operator know so they can plan the
19+
// transition to Lucene themselves.
20+
var coraxIndexes = await FindIndexesUsingCorax(store, databaseName, cancellationToken);
21+
22+
if (coraxIndexes.Length > 0)
23+
{
24+
Logger.LogWarning(CoraxIndexesMessage(coraxIndexes.Select(i => $"{databaseName}/{i}")));
25+
}
26+
}
27+
28+
public static string CoraxIndexesMessage(IEnumerable<string> indexes) =>
29+
$"The following RavenDB index(es) use the Corax search engine: {string.Join(", ", indexes)}. " +
30+
"Lucene indexes are smaller, use less memory and perform better for ServiceControl workloads, and are the default for new databases. " +
31+
"Consider switching these indexes to Lucene. Note that switching triggers a full rebuild of the index: on very large databases this can take days depending on the available compute, " +
32+
"and while the rebuild is running ingestion and indexing rates can be degraded. Plan the switch accordingly.";
33+
34+
public static async Task<string[]> FindIndexesUsingCorax(IDocumentStore store, string databaseName, CancellationToken cancellationToken = default)
35+
{
36+
var indexStats = await store.Maintenance.ForDatabase(databaseName).SendAsync(new GetIndexesStatisticsOperation(), cancellationToken);
37+
38+
return indexStats
39+
.Where(i => i.SearchEngineType == SearchEngineType.Corax)
40+
.Select(i => i.Name)
41+
.ToArray();
42+
}
43+
1044
public static async Task EnsureServerVersion(IDocumentStore store, CancellationToken cancellationToken = default)
1145
{
1246
// RavenDB compatibility policy is that the major/minor version of the server must be
@@ -31,5 +65,7 @@ public static async Task EnsureServerVersion(IDocumentStore store, CancellationT
3165
throw new Exception($"ServiceControl expects RavenDB Server version {clientProductVersion} or higher, but the server is using {serverProductVersion}.");
3266
}
3367
}
68+
69+
static readonly ILogger Logger = LoggerUtil.CreateStaticLogger(typeof(StartupChecks));
3470
}
3571
}

0 commit comments

Comments
 (0)