From c4f6fe2d4934dfb0f384334430dca56d814341bf Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 14 Oct 2025 22:35:13 +0200 Subject: [PATCH 1/5] Stage --- Directory.Packages.props | 2 +- .../DocumentationEndpoints.cs | 1 + src/Elastic.Documentation/Exporter.cs | 1 - .../Search/DocumentationDocument.cs | 11 +- .../ElasticsearchMarkdownExporter.cs | 308 +++++++++++------- .../Exporters/ExporterExtensions.cs | 4 +- .../Indexing/AssemblerIndexService.cs | 5 +- .../IsolatedIndexService.cs | 5 +- 8 files changed, 217 insertions(+), 120 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a33c0da98..958a09fdc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,7 +27,7 @@ - + diff --git a/src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs b/src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs index 21afc6ac4..c3fc345f1 100644 --- a/src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs +++ b/src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs @@ -43,4 +43,5 @@ public class ElasticsearchEndpoint public X509Certificate? Certificate { get; set; } public bool CertificateIsNotRoot { get; set; } public int? BootstrapTimeout { get; set; } + public bool NoSemantic { get; set; } } diff --git a/src/Elastic.Documentation/Exporter.cs b/src/Elastic.Documentation/Exporter.cs index 6af9ed524..ef4f23d72 100644 --- a/src/Elastic.Documentation/Exporter.cs +++ b/src/Elastic.Documentation/Exporter.cs @@ -11,7 +11,6 @@ public enum Exporter Html, LLMText, Elasticsearch, - ElasticsearchNoSemantic, Configuration, DocumentationState, LinkMetadata, diff --git a/src/Elastic.Documentation/Search/DocumentationDocument.cs b/src/Elastic.Documentation/Search/DocumentationDocument.cs index b6264a442..8a0eb6781 100644 --- a/src/Elastic.Documentation/Search/DocumentationDocument.cs +++ b/src/Elastic.Documentation/Search/DocumentationDocument.cs @@ -23,6 +23,15 @@ public record DocumentationDocument [JsonPropertyName("url")] public string Url { get; set; } = string.Empty; + /// The date of the batch update this document was part of last. + /// This date could be higher than the date_last_updated. + [JsonPropertyName("batch_index_date")] + public DateTimeOffset BatchIndexDate { get; set; } + + /// The date this document was last updated, + [JsonPropertyName("last_updated")] + public DateTimeOffset LastUpdated { get; set; } + // TODO make this required once all doc_sets have published again [JsonPropertyName("hash")] public string Hash { get; set; } = string.Empty; @@ -45,7 +54,7 @@ public record DocumentationDocument [JsonPropertyName("body")] public string? Body { get; set; } - // Stripped body is the body with markdown removed, suitable for search indexing + // Stripped body is the body with Markdown removed, suitable for search indexing [JsonPropertyName("stripped_body")] public string? StrippedBody { get; set; } diff --git a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs index 8c3367a95..1cbe1b5c5 100644 --- a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs @@ -11,72 +11,102 @@ using Elastic.Documentation.Serialization; using Elastic.Ingest.Elasticsearch; using Elastic.Ingest.Elasticsearch.Catalog; +using Elastic.Ingest.Elasticsearch.Indices; using Elastic.Ingest.Elasticsearch.Semantic; using Elastic.Markdown.Helpers; using Elastic.Markdown.IO; using Elastic.Transport; using Elastic.Transport.Products.Elasticsearch; using Markdig.Syntax; +using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.Extensions.Logging; namespace Elastic.Markdown.Exporters; -public class ElasticsearchMarkdownExporter(ILoggerFactory logFactory, IDiagnosticsCollector collector, string indexNamespace, DocumentationEndpoints endpoints) - : ElasticsearchMarkdownExporterBase, CatalogIndexChannel> - (logFactory, collector, endpoints) +public interface IElasticsearchExporter { - /// - protected override CatalogIndexChannelOptions NewOptions(DistributedTransport transport) => new(transport) - { - BulkOperationIdLookup = d => d.Url, - GetMapping = () => CreateMapping(null), - GetMappingSettings = CreateMappingSetting, - IndexFormat = $"{Endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", - ActiveSearchAlias = $"{Endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}", - }; - - /// - protected override CatalogIndexChannel NewChannel(CatalogIndexChannelOptions options) => new(options); + ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default); + ValueTask RefreshAsync(Cancel ctx = default); + ValueTask StopAsync(Cancel ctx = default); } -public class ElasticsearchMarkdownSemanticExporter(ILoggerFactory logFactory, IDiagnosticsCollector collector, string indexNamespace, DocumentationEndpoints endpoints) - : ElasticsearchMarkdownExporterBase, SemanticIndexChannel> - (logFactory, collector, endpoints) +public abstract class ElasticsearchExporter : IDisposable, IElasticsearchExporter where TChannelOptions : CatalogIndexChannelOptionsBase + where TChannel : CatalogIndexChannel { - /// - protected override SemanticIndexChannelOptions NewOptions(DistributedTransport transport) => new(transport) + private readonly IDiagnosticsCollector _collector; + public TChannel Channel { get; } + private readonly ILogger _logger; + + protected ElasticsearchExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + DistributedTransport transport, + Func createChannel, + Func createOptions + ) { - BulkOperationIdLookup = d => d.Url, - GetMapping = (inferenceId, _) => CreateMapping(inferenceId), - GetMappingSettings = (_, _) => CreateMappingSetting(), - IndexFormat = $"{Endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", - ActiveSearchAlias = $"{Endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}", - IndexNumThreads = Endpoint.IndexNumThreads, - SearchNumThreads = Endpoint.SearchNumThreads, - InferenceCreateTimeout = TimeSpan.FromMinutes(Endpoint.BootstrapTimeout ?? 4), - }; + _collector = collector; + _logger = logFactory.CreateLogger>(); + //The max num threads per allocated node, from testing its best to limit our max concurrency + //producing to this number as well + var options = createOptions(transport); + var i = 0; + options.BufferOptions = new BufferOptions + { + OutboundBufferMaxSize = endpoint.BufferSize, + ExportMaxConcurrency = endpoint.IndexNumThreads, + ExportMaxRetries = endpoint.MaxRetries + }; + options.SerializerContext = SourceGenerationContext.Default; + options.ExportBufferCallback = () => + { + var count = Interlocked.Increment(ref i); + _logger.LogInformation("Exported {Count} documents to Elasticsearch index {Format}", + count * endpoint.BufferSize, options.IndexFormat); + }; + options.ExportExceptionCallback = e => + { + _logger.LogError(e, "Failed to export document"); + _collector.EmitGlobalError("Elasticsearch export: failed to export document", e); + }; + options.ServerRejectionCallback = items => _logger.LogInformation("Server rejection: {Rejection}", items.First().Item2); + Channel = createChannel(options); + _logger.LogInformation($"Bootstrapping {nameof(SemanticIndexChannel)} Elasticsearch target for indexing"); + } - /// - protected override SemanticIndexChannel NewChannel(SemanticIndexChannelOptions options) => new(options); -} + public async ValueTask StopAsync(Cancel ctx = default) + { + _logger.LogInformation("Waiting to drain all inflight exports to Elasticsearch"); + var drained = await Channel.WaitForDrainAsync(null, ctx); + if (!drained) + _collector.EmitGlobalError("Elasticsearch export: failed to complete indexing in a timely fashion while shutting down"); + _logger.LogInformation("Refreshing target index {Index}", Channel.IndexName); + var refreshed = await Channel.RefreshAsync(ctx); + if (!refreshed) + _collector.EmitGlobalError($"Refreshing target index {Channel.IndexName} did not complete successfully"); -public abstract class ElasticsearchMarkdownExporterBase( - ILoggerFactory logFactory, - IDiagnosticsCollector collector, - DocumentationEndpoints endpoints -) - : IMarkdownExporter, IDisposable - where TChannelOptions : CatalogIndexChannelOptionsBase - where TChannel : CatalogIndexChannel -{ - private TChannel? _channel; - private readonly ILogger _logger = logFactory.CreateLogger(); + _logger.LogInformation("Applying aliases to {Index}", Channel.IndexName); + var swapped = await Channel.ApplyAliasesAsync(ctx); + if (!swapped) + _collector.EmitGlobalError($"${nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {Channel.IndexName}"); + + return drained && refreshed && swapped; + } - protected abstract TChannelOptions NewOptions(DistributedTransport transport); - protected abstract TChannel NewChannel(TChannelOptions options); + public async ValueTask RefreshAsync(Cancel ctx = default) => await Channel.RefreshAsync(ctx); + + public async ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default) + { + if (Channel.TryWrite(document)) + return true; + + if (await Channel.WaitToWriteAsync(ctx)) + return Channel.TryWrite(document); + return false; + } - protected ElasticsearchEndpoint Endpoint { get; } = endpoints.Elasticsearch; protected static string CreateMappingSetting() => // language=json @@ -180,10 +210,97 @@ private static string AbstractInferenceMapping(string inferenceId) => } """; - public async ValueTask StartAsync(Cancel ctx = default) + + public void Dispose() { - if (_channel != null) - return; + Channel.Complete(); + Channel.Dispose(); + + GC.SuppressFinalize(this); + } + +} + +public class ElasticsearchLexicalExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + string indexNamespace, + DistributedTransport transport, + DateTimeOffset batchIndexDate +) + : ElasticsearchExporter, CatalogIndexChannel> + (logFactory, collector, endpoint, transport, o => new(o), t => new(t) + { + BulkOperationIdLookup = d => d.Url, + ScriptedHashBulkUpsertLookup = (d, channelHash) => + { + var rand = string.Empty; + //if (d.Url.StartsWith("/docs/reference/search-connectors")) + // rand = Guid.NewGuid().ToString("N"); + d.Hash = HashedBulkUpdate.CreateHash(channelHash, rand, d.Url, d.Body ?? string.Empty, string.Join(",", d.Headings.OrderBy(h => h))); + d.LastUpdated = batchIndexDate; + d.BatchIndexDate = batchIndexDate; + return new HashedBulkUpdate("hash", d.Hash, "ctx._source.batch_index_date = params.batch_index_date", + new Dictionary + { + { "batch_index_date", d.BatchIndexDate.ToString("o") } + }); + }, + GetMapping = () => CreateMapping(null), + GetMappingSettings = CreateMappingSetting, + IndexFormat = + $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", + ActiveSearchAlias = $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}" + }); + +public class ElasticsearchSemanticExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + string indexNamespace, + DistributedTransport transport +) + : ElasticsearchExporter, SemanticIndexChannel> + (logFactory, collector, endpoint, transport, o => new(o), t => new(t) + { + BulkOperationIdLookup = d => d.Url, + GetMapping = (inferenceId, _) => CreateMapping(inferenceId), + GetMappingSettings = (_, _) => CreateMappingSetting(), + IndexFormat = $"{endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", + ActiveSearchAlias = $"{endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}", + IndexNumThreads = endpoint.IndexNumThreads, + SearchNumThreads = endpoint.SearchNumThreads, + InferenceCreateTimeout = TimeSpan.FromMinutes(endpoint.BootstrapTimeout ?? 4) + }); + +public class ElasticsearchMarkdownExporter : IMarkdownExporter, IDisposable +{ +#pragma warning disable IDE0052 + private readonly IDiagnosticsCollector _collector; +#pragma warning restore IDE0052 + private readonly ILogger _logger; + private readonly ElasticsearchLexicalExporter _lexicalChannel; +#pragma warning disable IDE0052 + private readonly ElasticsearchSemanticExporter _semanticChannel; +#pragma warning restore IDE0052 + private readonly IElasticsearchExporter _channel; + + protected ElasticsearchEndpoint Endpoint { get; } + + private readonly DateTimeOffset _batchIndexDate = DateTimeOffset.UtcNow; + private readonly DistributedTransport _transport; + + public ElasticsearchMarkdownExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + DocumentationEndpoints endpoints, + string indexNamespace + ) + { + _collector = collector; + _logger = logFactory.CreateLogger(); + Endpoint = endpoints.Elasticsearch; var es = endpoints.Elasticsearch; @@ -209,75 +326,36 @@ public async ValueTask StartAsync(Cancel ctx = default) : null }; - var transport = new DistributedTransport(configuration); + _transport = new DistributedTransport(configuration); - //The max num threads per allocated node, from testing its best to limit our max concurrency - //producing to this number as well - var options = NewOptions(transport); - var i = 0; - options.BufferOptions = new BufferOptions - { - OutboundBufferMaxSize = Endpoint.BufferSize, - ExportMaxConcurrency = Endpoint.IndexNumThreads, - ExportMaxRetries = Endpoint.MaxRetries, - }; - options.SerializerContext = SourceGenerationContext.Default; - options.ExportBufferCallback = () => - { - var count = Interlocked.Increment(ref i); - _logger.LogInformation("Exported {Count} documents to Elasticsearch index {Format}", - count * Endpoint.BufferSize, options.IndexFormat); - }; - options.ExportExceptionCallback = e => _logger.LogError(e, "Failed to export document"); - options.ServerRejectionCallback = items => _logger.LogInformation("Server rejection: {Rejection}", items.First().Item2); - _channel = NewChannel(options); - _logger.LogInformation($"Bootstrapping {nameof(SemanticIndexChannel)} Elasticsearch target for indexing"); - _ = await _channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); - } + _lexicalChannel = new ElasticsearchLexicalExporter(logFactory, collector, es, indexNamespace, _transport, _batchIndexDate); + _semanticChannel = new ElasticsearchSemanticExporter(logFactory, collector, es, indexNamespace, _transport); + _channel = _lexicalChannel; - public async ValueTask StopAsync(Cancel ctx = default) - { - if (_channel is null) - return; - - _logger.LogInformation("Waiting to drain all inflight exports to Elasticsearch"); - var drained = await _channel.WaitForDrainAsync(null, ctx); - if (!drained) - collector.EmitGlobalError("Elasticsearch export: failed to complete indexing in a timely fashion while shutting down"); - - _logger.LogInformation("Refreshing target index {Index}", _channel.IndexName); - var refreshed = await _channel.RefreshAsync(ctx); - if (!refreshed) - _logger.LogError("Refreshing target index {Index} did not complete successfully", _channel.IndexName); - - _logger.LogInformation("Applying aliases to {Index}", _channel.IndexName); - var swapped = await _channel.ApplyAliasesAsync(ctx); - if (!swapped) - collector.EmitGlobalError($"${nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {_channel.IndexName}"); } - public void Dispose() + /// + public async ValueTask StartAsync(Cancel ctx = default) { - if (_channel is not null) - { - _channel.Complete(); - _channel.Dispose(); - } - - GC.SuppressFinalize(this); + _ = await _lexicalChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); + return; } - private async ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default) + /// + public async ValueTask StopAsync(Cancel ctx = default) { - if (_channel is null) - return false; + var stopped = await _channel.StopAsync(ctx); - if (_channel.TryWrite(document)) - return true; - - if (await _channel.WaitToWriteAsync(ctx)) - return _channel.TryWrite(document); - return false; + var semanticIndex = _semanticChannel.Channel.IndexName; + var semanticIndexHead = await _transport.HeadAsync(semanticIndex, ctx); + if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) + { + _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); + var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); + if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) + throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); + _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); + } } public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Cancel ctx) @@ -314,7 +392,6 @@ public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, var doc = new DocumentationDocument { Url = url, - Hash = ShortId.Create(url, body), Title = file.Title, Body = body, StrippedBody = body.StripMarkdown(), @@ -327,9 +404,9 @@ public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Title = i.NavigationTitle, Url = i.Url }).Reverse().ToArray(), - Headings = headings, + Headings = headings }; - return await TryWrite(doc, ctx); + return await _channel.TryWrite(doc, ctx); } /// @@ -340,4 +417,11 @@ public async ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Canc return await _channel.RefreshAsync(ctx); } + + /// + public void Dispose() + { + _lexicalChannel.Dispose(); + GC.SuppressFinalize(this); + } } diff --git a/src/Elastic.Markdown/Exporters/ExporterExtensions.cs b/src/Elastic.Markdown/Exporters/ExporterExtensions.cs index c544d887a..8cb801b12 100644 --- a/src/Elastic.Markdown/Exporters/ExporterExtensions.cs +++ b/src/Elastic.Markdown/Exporters/ExporterExtensions.cs @@ -24,9 +24,7 @@ string indexNamespace if (exportOptions.Contains(Exporter.Configuration)) markdownExporters.Add(new ConfigurationExporter(logFactory, context.ConfigurationFileProvider, context)); if (exportOptions.Contains(Exporter.Elasticsearch)) - markdownExporters.Add(new ElasticsearchMarkdownSemanticExporter(logFactory, context.Collector, indexNamespace, context.Endpoints)); - if (exportOptions.Contains(Exporter.ElasticsearchNoSemantic)) - markdownExporters.Add(new ElasticsearchMarkdownExporter(logFactory, context.Collector, indexNamespace, context.Endpoints)); + markdownExporters.Add(new ElasticsearchMarkdownExporter(logFactory, context.Collector, context.Endpoints, indexNamespace)); return markdownExporters; } } diff --git a/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs b/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs index a892816e1..42ff53ba2 100644 --- a/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs +++ b/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs @@ -131,7 +131,10 @@ public async Task Index(IDiagnosticsCollector collector, if (bootstrapTimeout.HasValue) cfg.BootstrapTimeout = bootstrapTimeout.Value; - var exporters = new HashSet { noSemantic.GetValueOrDefault(false) ? ElasticsearchNoSemantic : Elasticsearch }; + if (noSemantic.HasValue) + cfg.NoSemantic = noSemantic.Value; + + var exporters = new HashSet { Elasticsearch }; return await BuildAll(collector, strict: false, environment, metadataOnly: true, showHints: false, exporters, fileSystem, ctx); } diff --git a/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs b/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs index b7ef38baf..b4a4ee656 100644 --- a/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs +++ b/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs @@ -128,7 +128,10 @@ public async Task Index(IDiagnosticsCollector collector, if (bootstrapTimeout.HasValue) cfg.BootstrapTimeout = bootstrapTimeout.Value; - var exporters = new HashSet { noSemantic.GetValueOrDefault(false) ? ElasticsearchNoSemantic : Elasticsearch }; + if (noSemantic.HasValue) + cfg.NoSemantic = noSemantic.Value; + + var exporters = new HashSet { Elasticsearch }; return await Build(collector, fileSystem, metadataOnly: true, strict: false, path: path, output: null, pathPrefix: null, From 33af4ccddb29bdbbc9760bc804c0689423b8ae68 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Oct 2025 11:03:26 +0200 Subject: [PATCH 2/5] Stage --- .../ElasticsearchMarkdownExporter.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs index 1cbe1b5c5..6271bf823 100644 --- a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs @@ -356,6 +356,41 @@ public async ValueTask StopAsync(Cancel ctx = default) throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); } + + var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); + var lexicalWriteAlias = string.Format(_lexicalChannel.Channel.Options.IndexFormat, "latest"); + + var reindexUrl = "/_reindex?wait_for_completion=false&require_alias=true&scroll=10m"; + var request = PostData.Serializable(new + { + source = new + { + index = lexicalWriteAlias, + size = 100, + query = new + { + range = new + { + last_updated = new { gte = _batchIndexDate.ToString("o") } + } + }, + dest = new { index = semanticWriteAlias } + } + }); + var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); + var taskId = reindexNewChanges.Body.Get("task"); + var completed = false; + do + { + var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); + completed = reindexTask.Body.Get("completed"); + var total = reindexTask.Body.Get("task.status.total"); + var updated = reindexTask.Body.Get("task.status.updated"); + var deleted = reindexTask.Body.Get("task.status.deleted"); + var deleted = reindexTask.Body.Get("task.status.deleted"); + + } while (!completed); + } public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Cancel ctx) From cc65eaa2ddab0a468d5afc7153448eb4e8a6b0db Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Oct 2025 15:00:21 +0200 Subject: [PATCH 3/5] Support multiple index strategies --- .../ElasticsearchMarkdownExporter.cs | 157 +++++++++++++++--- 1 file changed, 132 insertions(+), 25 deletions(-) diff --git a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs index 6271bf823..3943c7254 100644 --- a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs @@ -276,15 +276,13 @@ DistributedTransport transport public class ElasticsearchMarkdownExporter : IMarkdownExporter, IDisposable { -#pragma warning disable IDE0052 + private enum IndexStrategy { ReindexSync, Multiplex } + private readonly IDiagnosticsCollector _collector; -#pragma warning restore IDE0052 private readonly ILogger _logger; private readonly ElasticsearchLexicalExporter _lexicalChannel; -#pragma warning disable IDE0052 private readonly ElasticsearchSemanticExporter _semanticChannel; -#pragma warning restore IDE0052 - private readonly IElasticsearchExporter _channel; + private IndexStrategy _indexStrategy = IndexStrategy.ReindexSync; protected ElasticsearchEndpoint Endpoint { get; } @@ -330,7 +328,6 @@ string indexNamespace _lexicalChannel = new ElasticsearchLexicalExporter(logFactory, collector, es, indexNamespace, _transport, _batchIndexDate); _semanticChannel = new ElasticsearchSemanticExporter(logFactory, collector, es, indexNamespace, _transport); - _channel = _lexicalChannel; } @@ -338,18 +335,50 @@ string indexNamespace public async ValueTask StartAsync(Cancel ctx = default) { _ = await _lexicalChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); - return; + + var semanticIndex = _semanticChannel.Channel.IndexName; + var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); + var semanticIndexHead = await _transport.HeadAsync(semanticWriteAlias, ctx); + if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) + { + _indexStrategy = IndexStrategy.Multiplex; + _logger.LogInformation("No semantic index exists yet, creating index {Index} for semantic search", semanticIndex); + _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); + var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); + if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) + throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); + _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); + _logger.LogInformation("Switch indexing strategy to multiplex writes to {SemanticIndex}", semanticIndex); + } } /// public async ValueTask StopAsync(Cancel ctx = default) { - var stopped = await _channel.StopAsync(ctx); + var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); + var lexicalWriteAlias = string.Format(_lexicalChannel.Channel.Options.IndexFormat, "latest"); var semanticIndex = _semanticChannel.Channel.IndexName; - var semanticIndexHead = await _transport.HeadAsync(semanticIndex, ctx); + var semanticIndexHead = await _transport.HeadAsync(semanticWriteAlias, ctx); + + var stopped = await _lexicalChannel.StopAsync(ctx); + if (!stopped) + throw new Exception($"Failed to stop {_lexicalChannel.GetType().Name}"); + + if (_indexStrategy == IndexStrategy.Multiplex) + { + stopped = await _semanticChannel.StopAsync(ctx); + if (!stopped) + throw new Exception($"Failed to stop {_lexicalChannel.GetType().Name}"); + + // we still need to clean up the lexical index + await DoDeleteByQuery(lexicalWriteAlias, ctx); + return; + } + if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) { + _logger.LogInformation("No semantic index exists yet, creating index {Index} for semantic search", semanticIndex); _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) @@ -357,12 +386,10 @@ public async ValueTask StopAsync(Cancel ctx = default) _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); } - var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); - var lexicalWriteAlias = string.Format(_lexicalChannel.Channel.Options.IndexFormat, "latest"); - - var reindexUrl = "/_reindex?wait_for_completion=false&require_alias=true&scroll=10m"; + _logger.LogInformation("_reindex updates: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); var request = PostData.Serializable(new { + dest = new { index = semanticWriteAlias }, source = new { index = lexicalWriteAlias, @@ -373,24 +400,107 @@ public async ValueTask StopAsync(Cancel ctx = default) { last_updated = new { gte = _batchIndexDate.ToString("o") } } - }, - dest = new { index = semanticWriteAlias } + } } }); + await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "updates", ctx); + + _logger.LogInformation("_reindex deletions: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); + request = PostData.Serializable(new + { + dest = new { index = semanticWriteAlias }, + script = new { source = "ctx.op = \"delete\"" }, + source = new + { + index = lexicalWriteAlias, + size = 100, + query = new + { + range = new + { + batch_index_date = new { lt = _batchIndexDate.ToString("o") } + } + } + } + }); + await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "deletions", ctx); + + await DoDeleteByQuery(lexicalWriteAlias, ctx); + } + + private async ValueTask DoDeleteByQuery(string lexicalWriteAlias, Cancel ctx) + { + // delete all documents with batch_index_date < _batchIndexDate + // they weren't part of the current export + _logger.LogInformation("Delete data in '{SourceIndex}' not part of batch date: {Date}", lexicalWriteAlias, _batchIndexDate.ToString("o")); + var request = PostData.Serializable(new + { + query = new + { + range = new + { + batch_index_date = new { lt = _batchIndexDate.ToString("o") } + } + } + }); + var reindexUrl = $"/{lexicalWriteAlias}/_delete_by_query?wait_for_completion=false"; var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); var taskId = reindexNewChanges.Body.Get("task"); - var completed = false; + if (string.IsNullOrWhiteSpace(taskId)) + { + _collector.EmitGlobalError($"Failed to delete data in '{lexicalWriteAlias}' not part of batch date: {_batchIndexDate:o}"); + return; + } + _logger.LogInformation("_delete_by_query task id: {TaskId}", taskId); + bool completed; do { var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); completed = reindexTask.Body.Get("completed"); var total = reindexTask.Body.Get("task.status.total"); var updated = reindexTask.Body.Get("task.status.updated"); + var created = reindexTask.Body.Get("task.status.created"); var deleted = reindexTask.Body.Get("task.status.deleted"); - var deleted = reindexTask.Body.Get("task.status.deleted"); + var batches = reindexTask.Body.Get("task.status.batches"); + var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); + var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); + _logger.LogInformation("_delete_by_query '{SourceIndex}': {RunningTimeInNanos} Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", + lexicalWriteAlias, time.ToString(@"hh\:mm\:ss"), total, updated, created, deleted, batches); + if (!completed) + await Task.Delay(TimeSpan.FromSeconds(5), ctx); } while (!completed); + } + + private async ValueTask DoReindex(PostData request, string lexicalWriteAlias, string semanticWriteAlias, string typeOfSync, Cancel ctx) + { + var reindexUrl = "/_reindex?wait_for_completion=false&require_alias=true&scroll=10m"; + var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); + var taskId = reindexNewChanges.Body.Get("task"); + if (string.IsNullOrWhiteSpace(taskId)) + { + _collector.EmitGlobalError($"Failed to reindex {typeOfSync} data to '{semanticWriteAlias}'"); + return; + } + _logger.LogInformation("_reindex {Type} task id: {TaskId}", typeOfSync, taskId); + bool completed; + do + { + var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); + completed = reindexTask.Body.Get("completed"); + var total = reindexTask.Body.Get("task.status.total"); + var updated = reindexTask.Body.Get("task.status.updated"); + var created = reindexTask.Body.Get("task.status.created"); + var deleted = reindexTask.Body.Get("task.status.deleted"); + var batches = reindexTask.Body.Get("task.status.batches"); + var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); + var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); + _logger.LogInformation("_reindex {Type}: {RunningTimeInNanos} '{SourceIndex}' => '{DestinationIndex}'. Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", + typeOfSync, time.ToString(@"hh\:mm\:ss"), lexicalWriteAlias, semanticWriteAlias, total, updated, created, deleted, batches); + if (!completed) + await Task.Delay(TimeSpan.FromSeconds(5), ctx); + } while (!completed); } public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Cancel ctx) @@ -441,22 +551,19 @@ public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, }).Reverse().ToArray(), Headings = headings }; - return await _channel.TryWrite(doc, ctx); + if (_indexStrategy is IndexStrategy.Multiplex) + return await _lexicalChannel.TryWrite(doc, ctx) && await _semanticChannel.TryWrite(doc, ctx); + return await _lexicalChannel.TryWrite(doc, ctx); } /// - public async ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancel ctx) - { - if (_channel is null) - return false; - - return await _channel.RefreshAsync(ctx); - } + public ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancel ctx) => ValueTask.FromResult(true); /// public void Dispose() { _lexicalChannel.Dispose(); + _semanticChannel.Dispose(); GC.SuppressFinalize(this); } } From 198de19fb07ef5522079b6098558a3b4118dacfa Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Oct 2025 16:02:03 +0200 Subject: [PATCH 4/5] Small cleanups --- .../Elasticsearch/ElasticsearchExporter.cs | 262 ++++++++ .../ElasticsearchMarkdownExporter.cs | 284 +++++++++ .../ElasticsearchMarkdownExporter.cs | 569 ------------------ .../Exporters/ExporterExtensions.cs | 1 + 4 files changed, 547 insertions(+), 569 deletions(-) create mode 100644 src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchExporter.cs create mode 100644 src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchMarkdownExporter.cs delete mode 100644 src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs diff --git a/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchExporter.cs b/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchExporter.cs new file mode 100644 index 000000000..f731b9b10 --- /dev/null +++ b/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchExporter.cs @@ -0,0 +1,262 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Channels; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Search; +using Elastic.Documentation.Serialization; +using Elastic.Ingest.Elasticsearch.Catalog; +using Elastic.Ingest.Elasticsearch.Indices; +using Elastic.Ingest.Elasticsearch.Semantic; +using Elastic.Transport; +using Microsoft.Extensions.Logging; + +namespace Elastic.Markdown.Exporters.Elasticsearch; + +public class ElasticsearchLexicalExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + string indexNamespace, + DistributedTransport transport, + DateTimeOffset batchIndexDate +) + : ElasticsearchExporter, CatalogIndexChannel> + (logFactory, collector, endpoint, transport, o => new(o), t => new(t) + { + BulkOperationIdLookup = d => d.Url, + ScriptedHashBulkUpsertLookup = (d, channelHash) => + { + var rand = string.Empty; + //if (d.Url.StartsWith("/docs/reference/search-connectors")) + // rand = Guid.NewGuid().ToString("N"); + d.Hash = HashedBulkUpdate.CreateHash(channelHash, rand, d.Url, d.Body ?? string.Empty, string.Join(",", d.Headings.OrderBy(h => h))); + d.LastUpdated = batchIndexDate; + d.BatchIndexDate = batchIndexDate; + return new HashedBulkUpdate("hash", d.Hash, "ctx._source.batch_index_date = params.batch_index_date", + new Dictionary + { + { "batch_index_date", d.BatchIndexDate.ToString("o") } + }); + }, + GetMapping = () => CreateMapping(null), + GetMappingSettings = CreateMappingSetting, + IndexFormat = + $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", + ActiveSearchAlias = $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}" + }); + +public class ElasticsearchSemanticExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + string indexNamespace, + DistributedTransport transport +) + : ElasticsearchExporter, SemanticIndexChannel> + (logFactory, collector, endpoint, transport, o => new(o), t => new(t) + { + BulkOperationIdLookup = d => d.Url, + GetMapping = (inferenceId, _) => CreateMapping(inferenceId), + GetMappingSettings = (_, _) => CreateMappingSetting(), + IndexFormat = $"{endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", + ActiveSearchAlias = $"{endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}", + IndexNumThreads = endpoint.IndexNumThreads, + SearchNumThreads = endpoint.SearchNumThreads, + InferenceCreateTimeout = TimeSpan.FromMinutes(endpoint.BootstrapTimeout ?? 4) + }); + + +public abstract class ElasticsearchExporter : IDisposable + where TChannelOptions : CatalogIndexChannelOptionsBase + where TChannel : CatalogIndexChannel +{ + private readonly IDiagnosticsCollector _collector; + public TChannel Channel { get; } + private readonly ILogger _logger; + + protected ElasticsearchExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + ElasticsearchEndpoint endpoint, + DistributedTransport transport, + Func createChannel, + Func createOptions + ) + { + _collector = collector; + _logger = logFactory.CreateLogger>(); + //The max num threads per allocated node, from testing its best to limit our max concurrency + //producing to this number as well + var options = createOptions(transport); + var i = 0; + options.BufferOptions = new BufferOptions + { + OutboundBufferMaxSize = endpoint.BufferSize, + ExportMaxConcurrency = endpoint.IndexNumThreads, + ExportMaxRetries = endpoint.MaxRetries + }; + options.SerializerContext = SourceGenerationContext.Default; + options.ExportBufferCallback = () => + { + var count = Interlocked.Increment(ref i); + _logger.LogInformation("Exported {Count} documents to Elasticsearch index {Format}", + count * endpoint.BufferSize, string.Format(options.IndexFormat, "latest")); + }; + options.ExportExceptionCallback = e => + { + _logger.LogError(e, "Failed to export document"); + _collector.EmitGlobalError("Elasticsearch export: failed to export document", e); + }; + options.ServerRejectionCallback = items => _logger.LogInformation("Server rejection: {Rejection}", items.First().Item2); + Channel = createChannel(options); + _logger.LogInformation($"Bootstrapping {nameof(SemanticIndexChannel)} Elasticsearch target for indexing"); + } + + public async ValueTask StopAsync(Cancel ctx = default) + { + _logger.LogInformation("Waiting to drain all inflight exports to Elasticsearch"); + var drained = await Channel.WaitForDrainAsync(null, ctx); + if (!drained) + _collector.EmitGlobalError("Elasticsearch export: failed to complete indexing in a timely fashion while shutting down"); + + _logger.LogInformation("Refreshing target index {Index}", Channel.IndexName); + var refreshed = await Channel.RefreshAsync(ctx); + if (!refreshed) + _collector.EmitGlobalError($"Refreshing target index {Channel.IndexName} did not complete successfully"); + + _logger.LogInformation("Applying aliases to {Index}", Channel.IndexName); + var swapped = await Channel.ApplyAliasesAsync(ctx); + if (!swapped) + _collector.EmitGlobalError($"${nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {Channel.IndexName}"); + + return drained && refreshed && swapped; + } + + public async ValueTask RefreshAsync(Cancel ctx = default) => await Channel.RefreshAsync(ctx); + + public async ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default) + { + if (Channel.TryWrite(document)) + return true; + + if (await Channel.WaitToWriteAsync(ctx)) + return Channel.TryWrite(document); + return false; + } + + + protected static string CreateMappingSetting() => + // language=json + """ + { + "analysis": { + "analyzer": { + "synonyms_analyzer": { + "tokenizer": "whitespace", + "filter": [ + "lowercase", + "synonyms_filter" + ] + }, + "highlight_analyzer": { + "tokenizer": "standard", + "filter": [ + "lowercase", + "english_stop" + ] + }, + "hierarchy_analyzer": { "tokenizer": "path_tokenizer" } + }, + "filter": { + "synonyms_filter": { + "type": "synonym", + "synonyms_set": "docs", + "updateable": true + }, + "english_stop": { + "type": "stop", + "stopwords": "_english_" + } + }, + "tokenizer": { + "path_tokenizer": { + "type": "path_hierarchy", + "delimiter": "/" + } + } + } + } + """; + + protected static string CreateMapping(string? inferenceId) => + $$""" + { + "properties": { + "url" : { + "type": "keyword", + "fields": { + "match": { "type": "text" }, + "prefix": { "type": "text", "analyzer" : "hierarchy_analyzer" } + } + }, + "hash" : { "type" : "keyword" }, + "title": { + "type": "text", + "search_analyzer": "synonyms_analyzer", + "fields": { + "keyword": { + "type": "keyword" + } + {{(!string.IsNullOrWhiteSpace(inferenceId) ? $$""", "semantic_text": {{{InferenceMapping(inferenceId)}}}""" : "")}} + } + }, + "url_segment_count": { + "type": "integer" + }, + "body": { + "type": "text" + }, + "stripped_body": { + "type": "text", + "search_analyzer": "highlight_analyzer", + "term_vector": "with_positions_offsets" + } + {{(!string.IsNullOrWhiteSpace(inferenceId) ? AbstractInferenceMapping(inferenceId) : AbstractMapping())}} + } + } + """; + + private static string AbstractMapping() => + """ + , "abstract": { + "type": "text" + } + """; + + private static string InferenceMapping(string inferenceId) => + $""" + "type": "semantic_text", + "inference_id": "{inferenceId}" + """; + + private static string AbstractInferenceMapping(string inferenceId) => + // langugage=json + $$""" + , "abstract": { + {{InferenceMapping(inferenceId)}} + } + """; + + + public void Dispose() + { + Channel.Complete(); + Channel.Dispose(); + + GC.SuppressFinalize(this); + } + +} diff --git a/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchMarkdownExporter.cs new file mode 100644 index 000000000..804b46f13 --- /dev/null +++ b/src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchMarkdownExporter.cs @@ -0,0 +1,284 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Search; +using Elastic.Ingest.Elasticsearch; +using Elastic.Markdown.Helpers; +using Elastic.Markdown.IO; +using Elastic.Transport; +using Elastic.Transport.Products.Elasticsearch; +using Markdig.Syntax; +using Microsoft.Extensions.Logging; + +namespace Elastic.Markdown.Exporters.Elasticsearch; + +public class ElasticsearchMarkdownExporter : IMarkdownExporter, IDisposable +{ + private readonly IDiagnosticsCollector _collector; + private readonly ILogger _logger; + private readonly ElasticsearchLexicalExporter _lexicalChannel; + private readonly ElasticsearchSemanticExporter _semanticChannel; + + private readonly ElasticsearchEndpoint _endpoint; + + private readonly DateTimeOffset _batchIndexDate = DateTimeOffset.UtcNow; + private readonly DistributedTransport _transport; + + public ElasticsearchMarkdownExporter( + ILoggerFactory logFactory, + IDiagnosticsCollector collector, + DocumentationEndpoints endpoints, + string indexNamespace + ) + { + _collector = collector; + _logger = logFactory.CreateLogger(); + _endpoint = endpoints.Elasticsearch; + + var es = endpoints.Elasticsearch; + + var configuration = new ElasticsearchConfiguration(es.Uri) + { + Authentication = es.ApiKey is { } apiKey + ? new ApiKey(apiKey) + : es is { Username: { } username, Password: { } password } + ? new BasicAuthentication(username, password) + : null, + EnableHttpCompression = true, + DebugMode = _endpoint.DebugMode, + CertificateFingerprint = _endpoint.CertificateFingerprint, + ProxyAddress = _endpoint.ProxyAddress, + ProxyPassword = _endpoint.ProxyPassword, + ProxyUsername = _endpoint.ProxyUsername, + ServerCertificateValidationCallback = _endpoint.DisableSslVerification + ? CertificateValidations.AllowAll + : _endpoint.Certificate is { } cert + ? _endpoint.CertificateIsNotRoot + ? CertificateValidations.AuthorityPartOfChain(cert) + : CertificateValidations.AuthorityIsRoot(cert) + : null + }; + + _transport = new DistributedTransport(configuration); + + _lexicalChannel = new ElasticsearchLexicalExporter(logFactory, collector, es, indexNamespace, _transport, _batchIndexDate); + _semanticChannel = new ElasticsearchSemanticExporter(logFactory, collector, es, indexNamespace, _transport); + + } + + /// + public async ValueTask StartAsync(Cancel ctx = default) => + await _lexicalChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); + + /// + public async ValueTask StopAsync(Cancel ctx = default) + { + var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); + var lexicalWriteAlias = string.Format(_lexicalChannel.Channel.Options.IndexFormat, "latest"); + + var semanticIndex = _semanticChannel.Channel.IndexName; + var semanticIndexHead = await _transport.HeadAsync(semanticWriteAlias, ctx); + + if (_endpoint.NoSemantic) + { + _logger.LogInformation("--no-semantic was specified so exiting early before syncing to {Index}", semanticIndex); + return; + } + + var stopped = await _lexicalChannel.StopAsync(ctx); + if (!stopped) + throw new Exception($"Failed to stop {_lexicalChannel.GetType().Name}"); + + if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) + { + _logger.LogInformation("No semantic index exists yet, creating index {Index} for semantic search", semanticIndex); + _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); + var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); + if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) + throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); + _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); + } + + _logger.LogInformation("_reindex updates: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); + var request = PostData.Serializable(new + { + dest = new { index = semanticWriteAlias }, + source = new + { + index = lexicalWriteAlias, + size = 100, + query = new + { + range = new + { + last_updated = new { gte = _batchIndexDate.ToString("o") } + } + } + } + }); + await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "updates", ctx); + + _logger.LogInformation("_reindex deletions: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); + request = PostData.Serializable(new + { + dest = new { index = semanticWriteAlias }, + script = new { source = "ctx.op = \"delete\"" }, + source = new + { + index = lexicalWriteAlias, + size = 100, + query = new + { + range = new + { + batch_index_date = new { lt = _batchIndexDate.ToString("o") } + } + } + } + }); + await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "deletions", ctx); + + await DoDeleteByQuery(lexicalWriteAlias, ctx); + } + + private async ValueTask DoDeleteByQuery(string lexicalWriteAlias, Cancel ctx) + { + // delete all documents with batch_index_date < _batchIndexDate + // they weren't part of the current export + _logger.LogInformation("Delete data in '{SourceIndex}' not part of batch date: {Date}", lexicalWriteAlias, _batchIndexDate.ToString("o")); + var request = PostData.Serializable(new + { + query = new + { + range = new + { + batch_index_date = new { lt = _batchIndexDate.ToString("o") } + } + } + }); + var reindexUrl = $"/{lexicalWriteAlias}/_delete_by_query?wait_for_completion=false"; + var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); + var taskId = reindexNewChanges.Body.Get("task"); + if (string.IsNullOrWhiteSpace(taskId)) + { + _collector.EmitGlobalError($"Failed to delete data in '{lexicalWriteAlias}' not part of batch date: {_batchIndexDate:o}"); + return; + } + _logger.LogInformation("_delete_by_query task id: {TaskId}", taskId); + bool completed; + do + { + var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); + completed = reindexTask.Body.Get("completed"); + var total = reindexTask.Body.Get("task.status.total"); + var updated = reindexTask.Body.Get("task.status.updated"); + var created = reindexTask.Body.Get("task.status.created"); + var deleted = reindexTask.Body.Get("task.status.deleted"); + var batches = reindexTask.Body.Get("task.status.batches"); + var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); + var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); + _logger.LogInformation("_delete_by_query '{SourceIndex}': {RunningTimeInNanos} Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", + lexicalWriteAlias, time.ToString(@"hh\:mm\:ss"), total, updated, created, deleted, batches); + if (!completed) + await Task.Delay(TimeSpan.FromSeconds(5), ctx); + + } while (!completed); + } + + private async ValueTask DoReindex(PostData request, string lexicalWriteAlias, string semanticWriteAlias, string typeOfSync, Cancel ctx) + { + var reindexUrl = "/_reindex?wait_for_completion=false&require_alias=true&scroll=10m"; + var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); + var taskId = reindexNewChanges.Body.Get("task"); + if (string.IsNullOrWhiteSpace(taskId)) + { + _collector.EmitGlobalError($"Failed to reindex {typeOfSync} data to '{semanticWriteAlias}'"); + return; + } + _logger.LogInformation("_reindex {Type} task id: {TaskId}", typeOfSync, taskId); + bool completed; + do + { + var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); + completed = reindexTask.Body.Get("completed"); + var total = reindexTask.Body.Get("task.status.total"); + var updated = reindexTask.Body.Get("task.status.updated"); + var created = reindexTask.Body.Get("task.status.created"); + var deleted = reindexTask.Body.Get("task.status.deleted"); + var batches = reindexTask.Body.Get("task.status.batches"); + var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); + var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); + _logger.LogInformation("_reindex {Type}: {RunningTimeInNanos} '{SourceIndex}' => '{DestinationIndex}'. Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", + typeOfSync, time.ToString(@"hh\:mm\:ss"), lexicalWriteAlias, semanticWriteAlias, total, updated, created, deleted, batches); + if (!completed) + await Task.Delay(TimeSpan.FromSeconds(5), ctx); + + } while (!completed); + } + + public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Cancel ctx) + { + var file = fileContext.SourceFile; + var url = file.Url; + + if (url is "/docs" or "/docs/404") + { + // Skip the root and 404 pages + _logger.LogInformation("Skipping export for {Url}", url); + return true; + } + + IPositionalNavigation navigation = fileContext.DocumentationSet; + + // Remove the first h1 because we already have the title + // and we don't want it to appear in the body + var h1 = fileContext.Document.Descendants().FirstOrDefault(h => h.Level == 1); + if (h1 is not null) + _ = fileContext.Document.Remove(h1); + + var body = LlmMarkdownExporter.ConvertToLlmMarkdown(fileContext.Document, fileContext.BuildContext); + + var headings = fileContext.Document.Descendants() + .Select(h => h.GetData("header") as string ?? string.Empty) // TODO: Confirm that 'header' data is correctly set for all HeadingBlock instances and that this extraction is reliable. + .Where(text => !string.IsNullOrEmpty(text)) + .ToArray(); + + var @abstract = !string.IsNullOrEmpty(body) + ? body[..Math.Min(body.Length, 400)] + " " + string.Join(" \n- ", headings) + : string.Empty; + + var doc = new DocumentationDocument + { + Url = url, + Title = file.Title, + Body = body, + StrippedBody = body.StripMarkdown(), + Description = fileContext.SourceFile.YamlFrontMatter?.Description, + Abstract = @abstract, + Applies = fileContext.SourceFile.YamlFrontMatter?.AppliesTo, + UrlSegmentCount = url.Split('/', StringSplitOptions.RemoveEmptyEntries).Length, + Parents = navigation.GetParentsOfMarkdownFile(file).Select(i => new ParentDocument + { + Title = i.NavigationTitle, + Url = i.Url + }).Reverse().ToArray(), + Headings = headings + }; + return await _lexicalChannel.TryWrite(doc, ctx); + } + + /// + public ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancel ctx) => ValueTask.FromResult(true); + + /// + public void Dispose() + { + _lexicalChannel.Dispose(); + _semanticChannel.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs deleted file mode 100644 index 3943c7254..000000000 --- a/src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs +++ /dev/null @@ -1,569 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.IO.Abstractions; -using Elastic.Channels; -using Elastic.Documentation.Configuration; -using Elastic.Documentation.Diagnostics; -using Elastic.Documentation.Extensions; -using Elastic.Documentation.Search; -using Elastic.Documentation.Serialization; -using Elastic.Ingest.Elasticsearch; -using Elastic.Ingest.Elasticsearch.Catalog; -using Elastic.Ingest.Elasticsearch.Indices; -using Elastic.Ingest.Elasticsearch.Semantic; -using Elastic.Markdown.Helpers; -using Elastic.Markdown.IO; -using Elastic.Transport; -using Elastic.Transport.Products.Elasticsearch; -using Markdig.Syntax; -using Microsoft.AspNetCore.Server.Kestrel; -using Microsoft.Extensions.Logging; - -namespace Elastic.Markdown.Exporters; - -public interface IElasticsearchExporter -{ - ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default); - ValueTask RefreshAsync(Cancel ctx = default); - ValueTask StopAsync(Cancel ctx = default); -} - -public abstract class ElasticsearchExporter : IDisposable, IElasticsearchExporter where TChannelOptions : CatalogIndexChannelOptionsBase - where TChannel : CatalogIndexChannel -{ - private readonly IDiagnosticsCollector _collector; - public TChannel Channel { get; } - private readonly ILogger _logger; - - protected ElasticsearchExporter( - ILoggerFactory logFactory, - IDiagnosticsCollector collector, - ElasticsearchEndpoint endpoint, - DistributedTransport transport, - Func createChannel, - Func createOptions - ) - { - _collector = collector; - _logger = logFactory.CreateLogger>(); - //The max num threads per allocated node, from testing its best to limit our max concurrency - //producing to this number as well - var options = createOptions(transport); - var i = 0; - options.BufferOptions = new BufferOptions - { - OutboundBufferMaxSize = endpoint.BufferSize, - ExportMaxConcurrency = endpoint.IndexNumThreads, - ExportMaxRetries = endpoint.MaxRetries - }; - options.SerializerContext = SourceGenerationContext.Default; - options.ExportBufferCallback = () => - { - var count = Interlocked.Increment(ref i); - _logger.LogInformation("Exported {Count} documents to Elasticsearch index {Format}", - count * endpoint.BufferSize, options.IndexFormat); - }; - options.ExportExceptionCallback = e => - { - _logger.LogError(e, "Failed to export document"); - _collector.EmitGlobalError("Elasticsearch export: failed to export document", e); - }; - options.ServerRejectionCallback = items => _logger.LogInformation("Server rejection: {Rejection}", items.First().Item2); - Channel = createChannel(options); - _logger.LogInformation($"Bootstrapping {nameof(SemanticIndexChannel)} Elasticsearch target for indexing"); - } - - public async ValueTask StopAsync(Cancel ctx = default) - { - _logger.LogInformation("Waiting to drain all inflight exports to Elasticsearch"); - var drained = await Channel.WaitForDrainAsync(null, ctx); - if (!drained) - _collector.EmitGlobalError("Elasticsearch export: failed to complete indexing in a timely fashion while shutting down"); - - _logger.LogInformation("Refreshing target index {Index}", Channel.IndexName); - var refreshed = await Channel.RefreshAsync(ctx); - if (!refreshed) - _collector.EmitGlobalError($"Refreshing target index {Channel.IndexName} did not complete successfully"); - - _logger.LogInformation("Applying aliases to {Index}", Channel.IndexName); - var swapped = await Channel.ApplyAliasesAsync(ctx); - if (!swapped) - _collector.EmitGlobalError($"${nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {Channel.IndexName}"); - - return drained && refreshed && swapped; - } - - public async ValueTask RefreshAsync(Cancel ctx = default) => await Channel.RefreshAsync(ctx); - - public async ValueTask TryWrite(DocumentationDocument document, Cancel ctx = default) - { - if (Channel.TryWrite(document)) - return true; - - if (await Channel.WaitToWriteAsync(ctx)) - return Channel.TryWrite(document); - return false; - } - - - protected static string CreateMappingSetting() => - // language=json - """ - { - "analysis": { - "analyzer": { - "synonyms_analyzer": { - "tokenizer": "whitespace", - "filter": [ - "lowercase", - "synonyms_filter" - ] - }, - "highlight_analyzer": { - "tokenizer": "standard", - "filter": [ - "lowercase", - "english_stop" - ] - }, - "hierarchy_analyzer": { "tokenizer": "path_tokenizer" } - }, - "filter": { - "synonyms_filter": { - "type": "synonym", - "synonyms_set": "docs", - "updateable": true - }, - "english_stop": { - "type": "stop", - "stopwords": "_english_" - } - }, - "tokenizer": { - "path_tokenizer": { - "type": "path_hierarchy", - "delimiter": "/" - } - } - } - } - """; - - protected static string CreateMapping(string? inferenceId) => - $$""" - { - "properties": { - "url" : { - "type": "keyword", - "fields": { - "match": { "type": "text" }, - "prefix": { "type": "text", "analyzer" : "hierarchy_analyzer" } - } - }, - "hash" : { "type" : "keyword" }, - "title": { - "type": "text", - "search_analyzer": "synonyms_analyzer", - "fields": { - "keyword": { - "type": "keyword" - } - {{(!string.IsNullOrWhiteSpace(inferenceId) ? $$""", "semantic_text": {{{InferenceMapping(inferenceId)}}}""" : "")}} - } - }, - "url_segment_count": { - "type": "integer" - }, - "body": { - "type": "text" - }, - "stripped_body": { - "type": "text", - "search_analyzer": "highlight_analyzer", - "term_vector": "with_positions_offsets" - } - {{(!string.IsNullOrWhiteSpace(inferenceId) ? AbstractInferenceMapping(inferenceId) : AbstractMapping())}} - } - } - """; - - private static string AbstractMapping() => - """ - , "abstract": { - "type": "text" - } - """; - - private static string InferenceMapping(string inferenceId) => - $""" - "type": "semantic_text", - "inference_id": "{inferenceId}" - """; - - private static string AbstractInferenceMapping(string inferenceId) => - // langugage=json - $$""" - , "abstract": { - {{InferenceMapping(inferenceId)}} - } - """; - - - public void Dispose() - { - Channel.Complete(); - Channel.Dispose(); - - GC.SuppressFinalize(this); - } - -} - -public class ElasticsearchLexicalExporter( - ILoggerFactory logFactory, - IDiagnosticsCollector collector, - ElasticsearchEndpoint endpoint, - string indexNamespace, - DistributedTransport transport, - DateTimeOffset batchIndexDate -) - : ElasticsearchExporter, CatalogIndexChannel> - (logFactory, collector, endpoint, transport, o => new(o), t => new(t) - { - BulkOperationIdLookup = d => d.Url, - ScriptedHashBulkUpsertLookup = (d, channelHash) => - { - var rand = string.Empty; - //if (d.Url.StartsWith("/docs/reference/search-connectors")) - // rand = Guid.NewGuid().ToString("N"); - d.Hash = HashedBulkUpdate.CreateHash(channelHash, rand, d.Url, d.Body ?? string.Empty, string.Join(",", d.Headings.OrderBy(h => h))); - d.LastUpdated = batchIndexDate; - d.BatchIndexDate = batchIndexDate; - return new HashedBulkUpdate("hash", d.Hash, "ctx._source.batch_index_date = params.batch_index_date", - new Dictionary - { - { "batch_index_date", d.BatchIndexDate.ToString("o") } - }); - }, - GetMapping = () => CreateMapping(null), - GetMappingSettings = CreateMappingSetting, - IndexFormat = - $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", - ActiveSearchAlias = $"{endpoint.IndexNamePrefix.Replace("semantic", "lexical").ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}" - }); - -public class ElasticsearchSemanticExporter( - ILoggerFactory logFactory, - IDiagnosticsCollector collector, - ElasticsearchEndpoint endpoint, - string indexNamespace, - DistributedTransport transport -) - : ElasticsearchExporter, SemanticIndexChannel> - (logFactory, collector, endpoint, transport, o => new(o), t => new(t) - { - BulkOperationIdLookup = d => d.Url, - GetMapping = (inferenceId, _) => CreateMapping(inferenceId), - GetMappingSettings = (_, _) => CreateMappingSetting(), - IndexFormat = $"{endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}", - ActiveSearchAlias = $"{endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}", - IndexNumThreads = endpoint.IndexNumThreads, - SearchNumThreads = endpoint.SearchNumThreads, - InferenceCreateTimeout = TimeSpan.FromMinutes(endpoint.BootstrapTimeout ?? 4) - }); - -public class ElasticsearchMarkdownExporter : IMarkdownExporter, IDisposable -{ - private enum IndexStrategy { ReindexSync, Multiplex } - - private readonly IDiagnosticsCollector _collector; - private readonly ILogger _logger; - private readonly ElasticsearchLexicalExporter _lexicalChannel; - private readonly ElasticsearchSemanticExporter _semanticChannel; - private IndexStrategy _indexStrategy = IndexStrategy.ReindexSync; - - protected ElasticsearchEndpoint Endpoint { get; } - - private readonly DateTimeOffset _batchIndexDate = DateTimeOffset.UtcNow; - private readonly DistributedTransport _transport; - - public ElasticsearchMarkdownExporter( - ILoggerFactory logFactory, - IDiagnosticsCollector collector, - DocumentationEndpoints endpoints, - string indexNamespace - ) - { - _collector = collector; - _logger = logFactory.CreateLogger(); - Endpoint = endpoints.Elasticsearch; - - var es = endpoints.Elasticsearch; - - var configuration = new ElasticsearchConfiguration(es.Uri) - { - Authentication = es.ApiKey is { } apiKey - ? new ApiKey(apiKey) - : es is { Username: { } username, Password: { } password } - ? new BasicAuthentication(username, password) - : null, - EnableHttpCompression = true, - DebugMode = Endpoint.DebugMode, - CertificateFingerprint = Endpoint.CertificateFingerprint, - ProxyAddress = Endpoint.ProxyAddress, - ProxyPassword = Endpoint.ProxyPassword, - ProxyUsername = Endpoint.ProxyUsername, - ServerCertificateValidationCallback = Endpoint.DisableSslVerification - ? CertificateValidations.AllowAll - : Endpoint.Certificate is { } cert - ? Endpoint.CertificateIsNotRoot - ? CertificateValidations.AuthorityPartOfChain(cert) - : CertificateValidations.AuthorityIsRoot(cert) - : null - }; - - _transport = new DistributedTransport(configuration); - - _lexicalChannel = new ElasticsearchLexicalExporter(logFactory, collector, es, indexNamespace, _transport, _batchIndexDate); - _semanticChannel = new ElasticsearchSemanticExporter(logFactory, collector, es, indexNamespace, _transport); - - } - - /// - public async ValueTask StartAsync(Cancel ctx = default) - { - _ = await _lexicalChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); - - var semanticIndex = _semanticChannel.Channel.IndexName; - var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); - var semanticIndexHead = await _transport.HeadAsync(semanticWriteAlias, ctx); - if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) - { - _indexStrategy = IndexStrategy.Multiplex; - _logger.LogInformation("No semantic index exists yet, creating index {Index} for semantic search", semanticIndex); - _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); - var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); - if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) - throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); - _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); - _logger.LogInformation("Switch indexing strategy to multiplex writes to {SemanticIndex}", semanticIndex); - } - } - - /// - public async ValueTask StopAsync(Cancel ctx = default) - { - var semanticWriteAlias = string.Format(_semanticChannel.Channel.Options.IndexFormat, "latest"); - var lexicalWriteAlias = string.Format(_lexicalChannel.Channel.Options.IndexFormat, "latest"); - - var semanticIndex = _semanticChannel.Channel.IndexName; - var semanticIndexHead = await _transport.HeadAsync(semanticWriteAlias, ctx); - - var stopped = await _lexicalChannel.StopAsync(ctx); - if (!stopped) - throw new Exception($"Failed to stop {_lexicalChannel.GetType().Name}"); - - if (_indexStrategy == IndexStrategy.Multiplex) - { - stopped = await _semanticChannel.StopAsync(ctx); - if (!stopped) - throw new Exception($"Failed to stop {_lexicalChannel.GetType().Name}"); - - // we still need to clean up the lexical index - await DoDeleteByQuery(lexicalWriteAlias, ctx); - return; - } - - if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) - { - _logger.LogInformation("No semantic index exists yet, creating index {Index} for semantic search", semanticIndex); - _ = await _semanticChannel.Channel.BootstrapElasticsearchAsync(BootstrapMethod.Failure, null, ctx); - var semanticIndexPut = await _transport.PutAsync(semanticIndex, PostData.String("{}"), ctx); - if (!semanticIndexPut.ApiCallDetails.HasSuccessfulStatusCode) - throw new Exception($"Failed to create index {semanticIndex}: {semanticIndexPut}"); - _ = await _semanticChannel.Channel.ApplyAliasesAsync(ctx); - } - - _logger.LogInformation("_reindex updates: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); - var request = PostData.Serializable(new - { - dest = new { index = semanticWriteAlias }, - source = new - { - index = lexicalWriteAlias, - size = 100, - query = new - { - range = new - { - last_updated = new { gte = _batchIndexDate.ToString("o") } - } - } - } - }); - await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "updates", ctx); - - _logger.LogInformation("_reindex deletions: '{SourceIndex}' => '{DestinationIndex}'", lexicalWriteAlias, semanticWriteAlias); - request = PostData.Serializable(new - { - dest = new { index = semanticWriteAlias }, - script = new { source = "ctx.op = \"delete\"" }, - source = new - { - index = lexicalWriteAlias, - size = 100, - query = new - { - range = new - { - batch_index_date = new { lt = _batchIndexDate.ToString("o") } - } - } - } - }); - await DoReindex(request, lexicalWriteAlias, semanticWriteAlias, "deletions", ctx); - - await DoDeleteByQuery(lexicalWriteAlias, ctx); - } - - private async ValueTask DoDeleteByQuery(string lexicalWriteAlias, Cancel ctx) - { - // delete all documents with batch_index_date < _batchIndexDate - // they weren't part of the current export - _logger.LogInformation("Delete data in '{SourceIndex}' not part of batch date: {Date}", lexicalWriteAlias, _batchIndexDate.ToString("o")); - var request = PostData.Serializable(new - { - query = new - { - range = new - { - batch_index_date = new { lt = _batchIndexDate.ToString("o") } - } - } - }); - var reindexUrl = $"/{lexicalWriteAlias}/_delete_by_query?wait_for_completion=false"; - var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); - var taskId = reindexNewChanges.Body.Get("task"); - if (string.IsNullOrWhiteSpace(taskId)) - { - _collector.EmitGlobalError($"Failed to delete data in '{lexicalWriteAlias}' not part of batch date: {_batchIndexDate:o}"); - return; - } - _logger.LogInformation("_delete_by_query task id: {TaskId}", taskId); - bool completed; - do - { - var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); - completed = reindexTask.Body.Get("completed"); - var total = reindexTask.Body.Get("task.status.total"); - var updated = reindexTask.Body.Get("task.status.updated"); - var created = reindexTask.Body.Get("task.status.created"); - var deleted = reindexTask.Body.Get("task.status.deleted"); - var batches = reindexTask.Body.Get("task.status.batches"); - var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); - var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); - _logger.LogInformation("_delete_by_query '{SourceIndex}': {RunningTimeInNanos} Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", - lexicalWriteAlias, time.ToString(@"hh\:mm\:ss"), total, updated, created, deleted, batches); - if (!completed) - await Task.Delay(TimeSpan.FromSeconds(5), ctx); - - } while (!completed); - } - - private async ValueTask DoReindex(PostData request, string lexicalWriteAlias, string semanticWriteAlias, string typeOfSync, Cancel ctx) - { - var reindexUrl = "/_reindex?wait_for_completion=false&require_alias=true&scroll=10m"; - var reindexNewChanges = await _transport.PostAsync(reindexUrl, request, ctx); - var taskId = reindexNewChanges.Body.Get("task"); - if (string.IsNullOrWhiteSpace(taskId)) - { - _collector.EmitGlobalError($"Failed to reindex {typeOfSync} data to '{semanticWriteAlias}'"); - return; - } - _logger.LogInformation("_reindex {Type} task id: {TaskId}", typeOfSync, taskId); - bool completed; - do - { - var reindexTask = await _transport.GetAsync($"/_tasks/{taskId}", ctx); - completed = reindexTask.Body.Get("completed"); - var total = reindexTask.Body.Get("task.status.total"); - var updated = reindexTask.Body.Get("task.status.updated"); - var created = reindexTask.Body.Get("task.status.created"); - var deleted = reindexTask.Body.Get("task.status.deleted"); - var batches = reindexTask.Body.Get("task.status.batches"); - var runningTimeInNanos = reindexTask.Body.Get("task.running_time_in_nanos"); - var time = TimeSpan.FromMicroseconds(runningTimeInNanos / 1000); - _logger.LogInformation("_reindex {Type}: {RunningTimeInNanos} '{SourceIndex}' => '{DestinationIndex}'. Documents {Total}: {Updated} updated, {Created} created, {Deleted} deleted, {Batches} batches", - typeOfSync, time.ToString(@"hh\:mm\:ss"), lexicalWriteAlias, semanticWriteAlias, total, updated, created, deleted, batches); - if (!completed) - await Task.Delay(TimeSpan.FromSeconds(5), ctx); - - } while (!completed); - } - - public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, Cancel ctx) - { - var file = fileContext.SourceFile; - var url = file.Url; - - if (url is "/docs" or "/docs/404") - { - // Skip the root and 404 pages - _logger.LogInformation("Skipping export for {Url}", url); - return true; - } - - IPositionalNavigation navigation = fileContext.DocumentationSet; - - // Remove the first h1 because we already have the title - // and we don't want it to appear in the body - var h1 = fileContext.Document.Descendants().FirstOrDefault(h => h.Level == 1); - if (h1 is not null) - _ = fileContext.Document.Remove(h1); - - var body = LlmMarkdownExporter.ConvertToLlmMarkdown(fileContext.Document, fileContext.BuildContext); - - var headings = fileContext.Document.Descendants() - .Select(h => h.GetData("header") as string ?? string.Empty) // TODO: Confirm that 'header' data is correctly set for all HeadingBlock instances and that this extraction is reliable. - .Where(text => !string.IsNullOrEmpty(text)) - .ToArray(); - - var @abstract = !string.IsNullOrEmpty(body) - ? body[..Math.Min(body.Length, 400)] + " " + string.Join(" \n- ", headings) - : string.Empty; - - var doc = new DocumentationDocument - { - Url = url, - Title = file.Title, - Body = body, - StrippedBody = body.StripMarkdown(), - Description = fileContext.SourceFile.YamlFrontMatter?.Description, - Abstract = @abstract, - Applies = fileContext.SourceFile.YamlFrontMatter?.AppliesTo, - UrlSegmentCount = url.Split('/', StringSplitOptions.RemoveEmptyEntries).Length, - Parents = navigation.GetParentsOfMarkdownFile(file).Select(i => new ParentDocument - { - Title = i.NavigationTitle, - Url = i.Url - }).Reverse().ToArray(), - Headings = headings - }; - if (_indexStrategy is IndexStrategy.Multiplex) - return await _lexicalChannel.TryWrite(doc, ctx) && await _semanticChannel.TryWrite(doc, ctx); - return await _lexicalChannel.TryWrite(doc, ctx); - } - - /// - public ValueTask FinishExportAsync(IDirectoryInfo outputFolder, Cancel ctx) => ValueTask.FromResult(true); - - /// - public void Dispose() - { - _lexicalChannel.Dispose(); - _semanticChannel.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/src/Elastic.Markdown/Exporters/ExporterExtensions.cs b/src/Elastic.Markdown/Exporters/ExporterExtensions.cs index 8cb801b12..298244330 100644 --- a/src/Elastic.Markdown/Exporters/ExporterExtensions.cs +++ b/src/Elastic.Markdown/Exporters/ExporterExtensions.cs @@ -5,6 +5,7 @@ using Elastic.Documentation; using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; +using Elastic.Markdown.Exporters.Elasticsearch; using Microsoft.Extensions.Logging; namespace Elastic.Markdown.Exporters; From c41defe686fa6f66e5e1a8a8a907d02fcde3f39e Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Oct 2025 18:12:43 +0200 Subject: [PATCH 5/5] Add documentation --- docs/development/ingest/images/step1.png | Bin 0 -> 27826 bytes docs/development/ingest/images/step2.png | Bin 0 -> 29927 bytes docs/development/ingest/images/step3.png | Bin 0 -> 31627 bytes docs/development/ingest/images/step4.png | Bin 0 -> 32141 bytes docs/development/ingest/images/step5.png | Bin 0 -> 30805 bytes docs/development/ingest/images/step6.png | Bin 0 -> 28254 bytes docs/development/ingest/index.md | 190 +++++++++++++++++++++++ docs/development/toc.yml | 3 + 8 files changed, 193 insertions(+) create mode 100644 docs/development/ingest/images/step1.png create mode 100644 docs/development/ingest/images/step2.png create mode 100644 docs/development/ingest/images/step3.png create mode 100644 docs/development/ingest/images/step4.png create mode 100644 docs/development/ingest/images/step5.png create mode 100644 docs/development/ingest/images/step6.png create mode 100644 docs/development/ingest/index.md diff --git a/docs/development/ingest/images/step1.png b/docs/development/ingest/images/step1.png new file mode 100644 index 0000000000000000000000000000000000000000..c3f42e50bfd4dd2606f85238bda3c6d85137614c GIT binary patch literal 27826 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 XA9&^=ZitU;BPq$L%hpJL{QCa@x-$s+ literal 0 HcmV?d00001 diff --git a/docs/development/ingest/images/step4.png b/docs/development/ingest/images/step4.png new file mode 100644 index 0000000000000000000000000000000000000000..d1daa6ea0a899e78b2dce5eba6659e9eeda217e8 GIT binary patch literal 32141 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 k5FS*$28~^|S literal 0 HcmV?d00001 diff --git a/docs/development/ingest/images/step5.png b/docs/development/ingest/images/step5.png new file mode 100644 index 0000000000000000000000000000000000000000..b98f35570d6853cdd4eecc22c0ef97b6d86fea8d GIT binary patch literal 30805 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 ie=^sE|If)VxKE)>@tB(MrHB8VS literal 0 HcmV?d00001 diff --git a/docs/development/ingest/index.md b/docs/development/ingest/index.md new file mode 100644 index 000000000..72ae839f6 --- /dev/null +++ b/docs/development/ingest/index.md @@ -0,0 +1,190 @@ +--- +navigation_title: "Elasticsearch Ingest" +--- + +# Elasticsearch Ingestion + +## Elasticsearch Integration + +The Elasticsearch integration consists of two primary exporters that work together to maintain both lexical and semantic search indices: + +1. **ElasticsearchLexicalExporter** - Handles traditional full-text search indexing with hash-based change detection +2. **ElasticsearchSemanticExporter** - Manages semantic search indices using inference models for vector embeddings + +These exporters are coordinated by the `ElasticsearchMarkdownExporter` class, which implements the `IMarkdownExporter` interface. + +### Architecture Overview + +Both exporters inherit from the abstract `ElasticsearchExporter` base class (defined in `src/Elastic.Markdown/Exporters/Elasticsearch/ElasticsearchExporter.cs`), which provides: + +- **Channel-based ingestion**: Uses the `Elastic.Channels` library for high-performance buffered writes to Elasticsearch +- **Configurable concurrency**: Respects the `IndexNumThreads` setting to optimize throughput +- **Error handling**: Callbacks for export failures, server rejections, and retries +- **Progress tracking**: Logs buffer exports at configured intervals + +### Hash-Based Change Detection + +The lexical exporter implements an intelligent hash-based upsert strategy (`ScriptedHashBulkUpsertLookup`) that: + +1. Computes a hash from the document's URL, body content, and headings +2. On index, compares the computed hash with the stored hash +3. If hashes match: Only updates the `batch_index_date` field (minimal overhead) +4. If hashes differ: Performs a full document update with new `last_updated` timestamp + +This approach allows us to incrementally synchronize only the changed documents and deletions over to our semantic index. + +### Shutdown and Synchronization Logic + +The `StopAsync` method in `ElasticsearchMarkdownExporter` orchestrates a complex multi-phase synchronization sequence: + +#### Phase 1: Drain and Finalize Lexical Index + +```csharp +var stopped = await _lexicalChannel.StopAsync(ctx); +``` + +This calls the base `ElasticsearchExporter.StopAsync` method, which performs three critical operations: + +1. **Drain in-flight exports** (`WaitForDrainAsync`): Waits for all buffered documents to be flushed to Elasticsearch +2. **Refresh the index** (`RefreshAsync`): Makes all indexed documents immediately searchable +3. **Apply aliases** (`ApplyAliasesAsync`): Swaps index aliases to point to the newly created time-stamped index + +#### Phase 2: Semantic Index Bootstrap + +```csharp +if (!semanticIndexHead.ApiCallDetails.HasSuccessfulStatusCode) +{ + // Bootstrap semantic index if it doesn't exist + await _semanticChannel.Channel.BootstrapElasticsearchAsync(...); + await _transport.PutAsync(semanticIndex, ...); + await _semanticChannel.Channel.ApplyAliasesAsync(ctx); +} +``` + +If the semantic index doesn't exist yet, it's created and configured with the appropriate inference model settings. + +#### Phase 3: Incremental Sync - Updates + +```csharp +_reindex updates: '{SourceIndex}' => '{DestinationIndex}' +``` + +Uses Elasticsearch's `_reindex` API to copy **only changed documents** from the lexical index to the semantic index: + +- **Query filter**: `last_updated >= _batchIndexDate` +- **Result**: Only documents that were actually modified (not just batch-tracked) are synced +- This triggers semantic embedding generation for new/changed content + +#### Phase 4: Incremental Sync - Deletions + +```csharp +_reindex deletions: '{SourceIndex}' => '{DestinationIndex}' +``` + +Uses `_reindex` with a script to propagate deletions: + +- **Query filter**: `batch_index_date < _batchIndexDate` (documents not in current batch) +- **Script**: `ctx.op = "delete"` - converts reindex operations to deletions +- **Result**: Documents removed from the documentation are deleted from semantic index + +#### Phase 5: Cleanup Lexical Index + +```csharp +await DoDeleteByQuery(lexicalWriteAlias, ctx); +``` + +Removes stale documents from the lexical index using `_delete_by_query`: + +- **Query filter**: `batch_index_date < _batchIndexDate` +- **Result**: Lexical index only contains documents from the current batch + +### Task Monitoring + +Both `DoReindex` and `DoDeleteByQuery` methods use Elasticsearch's task API to monitor long-running operations: + +1. Submit the operation with `wait_for_completion=false` to get a task ID +2. Poll the `/_tasks/{taskId}` endpoint every 5 seconds +3. Log progress metrics: total documents, created, updated, deleted, batches, and elapsed time +4. Continue until `completed: true` + +This provides real-time visibility into large-scale index operations without blocking the application. + +### Index Naming Strategy + +Both exporters use time-stamped index names with write aliases: + +- **Lexical**: `{prefix}-lexical-{namespace}-{timestamp}` with alias `{prefix}-lexical-{namespace}` +- **Semantic**: `{prefix}-semantic-{namespace}-{timestamp}` with alias `{prefix}-semantic-{namespace}` + +The `-latest` formatted alias (e.g., `...-{yyyy.MM.dd.HHmmss}`) is used as a write alias during the current indexing operation, then swapped to the read alias upon completion. This enables zero-downtime reindexing. + +### Error Handling + +The `StopAsync` sequence includes comprehensive error tracking: + +- Failed drains, refreshes, or alias operations emit global errors via `IDiagnosticsCollector` +- The lexical channel stop must succeed (`stopped == true`) or an exception is thrown +- Task failures during reindex/delete operations are logged and recorded as global errors + +This ensures that indexing problems are visible and prevent silent data corruption. + +## Indexing Flow Visualization + +::::{stepper} + + + +::::{stepper} + +:::{step} Initial state: Both indexes contain existing documents + +![images/step1.png](images/step1.png) +::: +:::{step} Lexical Index processing + +![images/step2.png](images/step2.png) + +* ID 1: Hash matches → Only batch_index_date updated (blue) +* ID 2: Hash changed → Full upsert with new last_updated (green) +* ID 3: No incoming data → Untouched (gray) +* ID 4: New document → Insert (green) +* ID 5: Not included in current batch → Untouched (gray) + +::: + +:::{step} Sync updates to Semantic Index + +![images/step3.png](images/step3.png) + +* Copy documents from Lexical Index where last_updated >= 2024-10-15 +* Only IDs 2 and 4 synced (ID 1 has old last_updated date) + +::: + +:::{step} Mark deletions in both indexes + +![images/step4.png](images/step4.png) + +* Lexical Index: Mark IDs 3 and 5 (batch_index_date < 2024-10-15) as red +* Semantic Index: Sync deletion of ID 5 from Lexical Index, mark as red + +::: + +:::{step} Delete from Semantic Index first + +![images/step5.png](images/step5.png) + +* Remove ID 5 from Semantic Index +* Lexical Index still has IDs 3 and 5 marked for deletion + +::: + +:::{step} Complete deletion and final sync + +![images/step6.png](images/step6.png) + +* Delete IDs 3 and 5 from Lexical Index +* Semantic Index remains as-is (batch_index_date not updated there) +* Both indexes now synchronized with same document IDs +::: +:::: \ No newline at end of file diff --git a/docs/development/toc.yml b/docs/development/toc.yml index f9ff0490e..8e571cd12 100644 --- a/docs/development/toc.yml +++ b/docs/development/toc.yml @@ -1,3 +1,6 @@ toc: - file: index.md + - folder: ingest + children: + - file: index.md - toc: link-validation