Skip to content

Commit 24ede2e

Browse files
authored
EF-274: Increase vector waiting timeout and add log that we are waiting (#254)
1 parent 73c6b89 commit 24ede2e

File tree

9 files changed

+139
-16
lines changed

9 files changed

+139
-16
lines changed

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoEventId.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ private enum Id
4949
ColumnAttributeWithTypeUsed,
5050
VectorSearchNeedsIndex,
5151
VectorSearchReturnedZeroResults,
52+
WaitingForVectorIndex,
5253
}
5354

5455
private static EventId MakeDatabaseCommandId(Id id)
@@ -213,4 +214,16 @@ private static EventId MakeQueryId(Id id)
213214
/// <para>This event uses the <see cref="PropertyAndIndexNameEventData" /> payload when used with a <see cref="DiagnosticSource" />.</para>
214215
/// </remarks>
215216
public static readonly EventId VectorSearchReturnedZeroResults = MakeQueryId(Id.VectorSearchReturnedZeroResults);
217+
218+
private static EventId MakeDatabaseId(Id id)
219+
=> new((int)id, DbLoggerCategory.Database.Name + "." + id);
220+
221+
/// <summary>
222+
/// EF Core is waiting for vector indexes to be ready.
223+
/// </summary>
224+
/// <remarks>
225+
/// <para>This event is in the <see cref="DbLoggerCategory.Database" /> category.</para>
226+
/// <para>This event uses the <see cref="TimeSpanEventData" /> payload when used with a <see cref="DiagnosticSource" />.</para>
227+
/// </remarks>
228+
public static readonly EventId WaitingForVectorIndex = MakeDatabaseId(Id.WaitingForVectorIndex);
216229
}

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoLoggerExtensions.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Globalization;
1718
using System.Linq;
1819
using Microsoft.EntityFrameworkCore;
1920
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -373,4 +374,54 @@ private static EventDefinition<string, string, string> LogVectorSearchReturnedZe
373374
"Consider disabling index creation in 'DbContext.Database.EnsureCreated' and performing initial ingestion " +
374375
"of embeddings, before calling 'DbContext.Database.CreateMissingVectorIndexes' and " +
375376
"'DbContext.Database.WaitForVectorIndexes'.";
377+
378+
379+
380+
public static void WaitingForVectorIndex(
381+
this IDiagnosticsLogger<DbLoggerCategory.Database> diagnostics,
382+
TimeSpan remainingBeforeTimeout)
383+
{
384+
var definition = LogWaitingForVectorIndex(diagnostics);
385+
386+
if (diagnostics.ShouldLog(definition))
387+
{
388+
definition.Log(diagnostics, remainingBeforeTimeout.TotalSeconds.ToString(CultureInfo.InvariantCulture));
389+
}
390+
391+
if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
392+
{
393+
var eventData = new TimeSpanEventData(
394+
definition,
395+
(d, p) => ((EventDefinition<string>)d).GenerateMessage(
396+
((TimeSpanEventData)p).TimeSpan.TotalSeconds.ToString(CultureInfo.InvariantCulture)),
397+
remainingBeforeTimeout);
398+
diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
399+
}
400+
}
401+
402+
private static EventDefinition<string> LogWaitingForVectorIndex(IDiagnosticsLogger logger)
403+
{
404+
var definition = ((MongoLoggingDefinitions)logger.Definitions).LogWaitingForVectorIndex;
405+
if (definition == null)
406+
{
407+
definition = NonCapturingLazyInitializer.EnsureInitialized(
408+
ref ((MongoLoggingDefinitions)logger.Definitions).LogWaitingForVectorIndex,
409+
logger,
410+
static logger => new EventDefinition<string>(
411+
logger.Options,
412+
MongoEventId.WaitingForVectorIndex,
413+
LogLevel.Information,
414+
"MongoEventId.WaitingForVectorIndex",
415+
level => LoggerMessage.Define<string>(
416+
level,
417+
MongoEventId.WaitingForVectorIndex,
418+
WaitingForVectorIndexString)));
419+
}
420+
421+
return (EventDefinition<string>)definition;
422+
}
423+
424+
private const string WaitingForVectorIndexString =
425+
"EF Core is waiting for vector indexes to be ready. The time remaining before failing with a timeout is " +
426+
"{secondsRemaining} seconds.";
376427
}

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoLoggingDefinitions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ internal class MongoLoggingDefinitions : LoggingDefinitions
4545

4646
public EventDefinitionBase? LogVectorSearchNeedsIndex;
4747
public EventDefinitionBase? LogVectorSearchReturnedZeroResults;
48+
49+
public EventDefinitionBase? LogWaitingForVectorIndex;
4850
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* Copyright 2023-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Diagnostics;
18+
using Microsoft.EntityFrameworkCore.Diagnostics;
19+
20+
namespace MongoDB.EntityFrameworkCore.Diagnostics;
21+
22+
/// <summary>
23+
/// A <see cref="DiagnosticSource" /> event payload class for events holding an amount of time.
24+
/// </summary>
25+
/// <remarks>
26+
/// See <see href="https://aka.ms/efcore-docs-diagnostics">Logging, events, and diagnostics</see> for more information and examples.
27+
/// </remarks>
28+
public class TimeSpanEventData : EventData
29+
{
30+
/// <summary>
31+
/// Constructs the event payload.
32+
/// </summary>
33+
/// <param name="eventDefinition">The event definition.</param>
34+
/// <param name="messageGenerator">A delegate that generates a log message for this event.</param>
35+
/// <param name="timeSpan">The amount of time.</param>
36+
public TimeSpanEventData(
37+
EventDefinitionBase eventDefinition,
38+
Func<EventDefinitionBase, EventData, string> messageGenerator,
39+
TimeSpan timeSpan)
40+
: base(eventDefinition, messageGenerator)
41+
{
42+
TimeSpan = timeSpan;
43+
}
44+
45+
/// <summary>
46+
/// The amount of time.
47+
/// </summary>
48+
public virtual TimeSpan TimeSpan { get; }
49+
}

src/MongoDB.EntityFrameworkCore/Extensions/MongoDatabaseFacadeExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static Task CreateMissingVectorIndexesAsync(this DatabaseFacade databaseF
101101
/// </summary>
102102
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
103103
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
104-
/// The default is 15 seconds. Zero seconds means no timeout.</param>
104+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
105105
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>
106106
public static void WaitForVectorIndexes(this DatabaseFacade databaseFacade, TimeSpan? timeout = null)
107107
=> GetDatabaseCreator(databaseFacade).WaitForVectorIndexes(timeout);
@@ -111,7 +111,7 @@ public static void WaitForVectorIndexes(this DatabaseFacade databaseFacade, Time
111111
/// </summary>
112112
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
113113
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
114-
/// The default is 15 seconds. Zero seconds means no timeout.</param>
114+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
115115
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel this asynchronous request.</param>
116116
/// <returns>A <see cref="Task"/> to track this async operation.</returns>
117117
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>

src/MongoDB.EntityFrameworkCore/Metadata/MongoDatabaseCreationOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace MongoDB.EntityFrameworkCore.Metadata;
2727
/// <param name="CreateMissingVectorIndexes">Creates any MongoDB Atlas vector indexes that do not already exist. The default is true.</param>
2828
/// <param name="WaitForVectorIndexes">Waits all MongoDB Atlas vector indexes to be 'READY' before continuing. The default is true.</param>
2929
/// <param name="IndexCreationTimeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
30-
/// The default is 15 seconds. Zero seconds means no timeout.</param>
30+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
3131
public readonly record struct MongoDatabaseCreationOptions(
3232
bool CreateMissingCollections = true,
3333
bool CreateMissingIndexes = true,

src/MongoDB.EntityFrameworkCore/Storage/IMongoDatabaseCreator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ public interface IMongoDatabaseCreator : IDatabaseCreator
107107
/// Blocks until all vector indexes in the mapped collections are reporting the 'READY' state.
108108
/// </summary>
109109
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
110-
/// The default is 15 seconds. Zero seconds means no timeout.</param>
110+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
111111
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>
112112
void WaitForVectorIndexes(TimeSpan? timeout = null);
113113

114114
/// <summary>
115115
/// Blocks until all vector indexes in the mapped collections are reporting the 'READY' state.
116116
/// </summary>
117117
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
118-
/// The default is 15 seconds. Zero seconds means no timeout.</param>
118+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
119119
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel this asynchronous request.</param>
120120
/// <returns>A <see cref="Task"/> to track this async operation.</returns>
121121
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>

src/MongoDB.EntityFrameworkCore/Storage/MongoDatabaseCreator.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
using System.Threading;
2121
using System.Threading.Tasks;
2222
using Microsoft.EntityFrameworkCore;
23+
using Microsoft.EntityFrameworkCore.Diagnostics;
2324
using Microsoft.EntityFrameworkCore.Metadata;
2425
using Microsoft.EntityFrameworkCore.Metadata.Internal;
2526
using Microsoft.EntityFrameworkCore.Storage;
2627
using Microsoft.EntityFrameworkCore.Update;
2728
using MongoDB.Bson;
2829
using MongoDB.Driver;
30+
using MongoDB.EntityFrameworkCore.Diagnostics;
2931
using MongoDB.EntityFrameworkCore.Extensions;
3032
using MongoDB.EntityFrameworkCore.Metadata;
3133

@@ -41,7 +43,8 @@ public class MongoDatabaseCreator(
4143
IMongoClientWrapper clientWrapper,
4244
IDesignTimeModel designTimeModel,
4345
IUpdateAdapterFactory updateAdapterFactory,
44-
IDatabase database)
46+
IDatabase database,
47+
IDiagnosticsLogger<DbLoggerCategory.Database> logger)
4548
: IMongoDatabaseCreator
4649
{
4750
private bool _useDatabaseNameFilter = true;
@@ -376,8 +379,11 @@ public void WaitForVectorIndexes(TimeSpan? timeout = null)
376379
$"Failed to build the vector index '{indexModel["name"]}' for path '{indexModel["latestDefinition"]["fields"][0]["path"]}'.");
377380
}
378381

379-
if (status != "READY")
382+
var remainingBeforeTimeout = failAfter - DateTime.UtcNow;
383+
if (status != "READY" && remainingBeforeTimeout > TimeSpan.Zero)
380384
{
385+
logger.WaitingForVectorIndex(remainingBeforeTimeout);
386+
381387
isReady = false;
382388
Thread.Sleep(delay *= 2);
383389
break;
@@ -395,11 +401,10 @@ public void WaitForVectorIndexes(TimeSpan? timeout = null)
395401

396402
private static DateTimeOffset CalculateTimeoutDateTime(TimeSpan? timeout)
397403
{
398-
timeout ??= TimeSpan.FromSeconds(15);
399-
var failAfter = timeout.Value == TimeSpan.Zero
404+
timeout ??= TimeSpan.FromSeconds(60);
405+
return timeout.Value == TimeSpan.Zero
400406
? DateTime.MaxValue
401-
: DateTimeOffset.UtcNow.Add(timeout.Value);
402-
return failAfter;
407+
: DateTime.UtcNow.Add(timeout.Value);
403408
}
404409

405410
/// <inheritdoc />
@@ -508,8 +513,11 @@ public async Task WaitForVectorIndexesAsync(TimeSpan? timeout = null, Cancellati
508513
$"Failed to build the vector index '{indexModel["name"]}' for path '{indexModel["latestDefinition"]["fields"][0]["path"]}'.");
509514
}
510515

511-
if (status != "READY")
516+
var remainingBeforeTimeout = failAfter - DateTime.UtcNow;
517+
if (status != "READY" && remainingBeforeTimeout > TimeSpan.Zero)
512518
{
519+
logger.WaitingForVectorIndex(remainingBeforeTimeout);
520+
513521
isReady = false;
514522
await Task.Delay(delay *= 2, cancellationToken).ConfigureAwait(false);
515523
break;

tests/MongoDB.EntityFrameworkCore.FunctionalTests/Storage/IndexTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ public async Task Create_vector_index_after_EnsureCreated(bool async)
114114

115115
Assert.Equal(0, GetVectorIndexCount(collection));
116116

117-
await db.Database.CreateMissingVectorIndexesAsync();
118-
await db.Database.WaitForVectorIndexesAsync();
117+
db.Database.CreateMissingVectorIndexes();
118+
db.Database.WaitForVectorIndexes();
119119
}
120120

121121
ValidateIndex("FloatsVectorIndex", collection, "Floats", 2, "cosine", expectedFilterPaths: null);
@@ -189,8 +189,8 @@ public async Task Create_vector_index_on_nested_entity_after_EnsureCreated(bool
189189

190190
Assert.Equal(0, GetVectorIndexCount(collection));
191191

192-
await db.Database.CreateMissingVectorIndexesAsync();
193-
await db.Database.WaitForVectorIndexesAsync();
192+
db.Database.CreateMissingVectorIndexes();
193+
db.Database.WaitForVectorIndexes();
194194
}
195195

196196
ValidateIndex("DoublesVectorIndex", collection, "Nested.Nested.Doubles", 2, "cosine", expectedFilterPaths: null);

0 commit comments

Comments
 (0)