Skip to content

Commit 4cd08b5

Browse files
reakaleekclaude
andcommitted
Search: Fix content date enrichment initialization for existing environments
The staging index + alias swap pattern introduced in #3098 fails in environments that already have a concrete index with the alias name. This adds a migration path that reindexes data into a timestamped backing index, removes the legacy index, and creates the alias. Also makes enrich policy creation idempotent (delete-before-put) and adds a last_updated fallback in the sitemap reader for documents indexed before the content date pipeline existed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aa23769 commit 4cd08b5

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

src/Elastic.Markdown/Exporters/Elasticsearch/ContentDateEnrichment.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,39 @@ private async Task EnsureLookupIndexAsync(Cancel ct)
106106

107107
var indexName = GenerateStagingName();
108108
await CreateLookupIndexAsync(indexName, ct);
109-
await SwapAliasAsync(null, indexName, ct);
110109

110+
// A concrete index may exist with the alias name from before the alias-swap pattern.
111+
// Preserve its data in the new timestamped index and remove it so the alias can be created.
112+
if (await IndexExistsAsync(_lookupAlias, ct))
113+
{
114+
logger.LogInformation("Migrating legacy concrete index {Alias} to alias-backed pattern", _lookupAlias);
115+
await ReindexToLookupAsync(_lookupAlias, indexName, ct);
116+
await RefreshIndexAsync(indexName, ct);
117+
118+
var deleteResponse = await operations.WithRetryAsync(
119+
() => transport.DeleteAsync<StringResponse>(_lookupAlias, new DefaultRequestParameters(), PostData.Empty, ct),
120+
$"DELETE {_lookupAlias}",
121+
ct
122+
);
123+
if (!deleteResponse.ApiCallDetails.HasSuccessfulStatusCode)
124+
throw new InvalidOperationException(
125+
$"Failed to delete legacy index {_lookupAlias} during migration: {deleteResponse.ApiCallDetails.DebugInformation}");
126+
}
127+
128+
await SwapAliasAsync(null, indexName, ct);
111129
logger.LogInformation("Created content date lookup index {Index} with alias {Alias}", indexName, _lookupAlias);
112130
}
113131

132+
private async Task<bool> IndexExistsAsync(string indexName, Cancel ct)
133+
{
134+
var response = await operations.WithRetryAsync(
135+
() => transport.GetAsync<StringResponse>(indexName, ct),
136+
$"GET {indexName}",
137+
ct
138+
);
139+
return response.ApiCallDetails.HasSuccessfulStatusCode;
140+
}
141+
114142
private async Task CreateLookupIndexAsync(string indexName, Cancel ct)
115143
{
116144
var mapping = new JsonObject
@@ -180,6 +208,13 @@ private async Task DeleteIndexAsync(string indexName, Cancel ct)
180208

181209
private async Task PutEnrichPolicyAsync(Cancel ct)
182210
{
211+
// Enrich policy PUT does not support upsert; delete first if it already exists.
212+
_ = await operations.WithRetryAsync(
213+
() => transport.DeleteAsync<StringResponse>($"/_enrich/policy/{PolicyName}", new DefaultRequestParameters(), PostData.Empty, ct),
214+
$"DELETE _enrich/policy/{PolicyName}",
215+
ct
216+
);
217+
183218
var policy = new JsonObject
184219
{
185220
["match"] = new JsonObject

src/services/Elastic.Documentation.Assembler/Building/EsSitemapReader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public async IAsyncEnumerable<SitemapEntry> ReadAllAsync([EnumeratorCancellation
6161
continue;
6262

6363
var url = source["url"]?.GetValue<string>();
64-
var lastUpdatedStr = source["content_last_updated"]?.GetValue<string>();
64+
var lastUpdatedStr = source["content_last_updated"]?.GetValue<string>()
65+
?? source["last_updated"]?.GetValue<string>();
6566

6667
if (url is null || lastUpdatedStr is null)
6768
continue;
@@ -131,7 +132,7 @@ internal static string BuildSearchBody(string pitId, string[]? searchAfter)
131132
var body = new JsonObject
132133
{
133134
["size"] = PageSize,
134-
["_source"] = new JsonArray("url", "content_last_updated"),
135+
["_source"] = new JsonArray("url", "content_last_updated", "last_updated"),
135136
["query"] = new JsonObject
136137
{
137138
["bool"] = new JsonObject

0 commit comments

Comments
 (0)