Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,27 @@ public List<String> searchChunksByParent(
/**
* Begins a staged recreate for a full-recreate run: pre-flights the embedding client (a broken
* client would make the whole run pointless), sweeps generations orphaned by crashed runs, and
* creates the next generation bare — no aliases, so reads keep hitting the old chunks. Throws on
* any failure; nothing has been destroyed at that point.
* creates the next generation bare — no aliases, so reads keep hitting the old chunks.
*
* <p>Returns {@code null} when the embedding client cannot serve a request, meaning "not staged":
* the caller carries on with a normal in-place reindex and the existing chunks stay live, exactly
* as they do for a partial recreate. An unreachable embedding provider is a reason not to stage a
* generation we could never finish — it is not a reason to fail the entity reindex, which does
* not need embeddings at all. Note the provider is only exercised here: client construction makes
* no call to it, and live indexing logs and continues on embedding errors, so a deployment with
* semantic search enabled but no working provider looks healthy right up until a reindex.
*
* <p>Still throws on genuine staging failures (indeterminate live-target probe, index create) —
* those mean the cluster is in a state where continuing could destroy live chunks.
*/
public String beginStagedChunkRecreate() {
preflightEmbedding();
if (!isEmbeddingAvailable()) {
LOG.warn(
"Embedding pre-flight failed — skipping the staged chunk recreate. The entity reindex "
+ "continues and existing chunks stay live; orphaned chunks, if any, are swept by the "
+ "next recreate that runs with a working embedding provider.");
return null;
}
Comment on lines 461 to +468
synchronized (stagedChunkLock) {
String base = getChunkIndexName();
String liveTarget = requireResolvedLiveChunkTarget(base);
Expand Down Expand Up @@ -490,12 +506,18 @@ public void clearStagedChunkState(String generation) {
}
}

private void preflightEmbedding() {
/**
* Whether the embedding client can actually serve a request. Logged rather than thrown: the only
* caller treats an unavailable provider as "do not stage", and the stack trace belongs in the log
* next to the provider's own error, not wrapped in a reindex failure.
*/
private boolean isEmbeddingAvailable() {
try {
embeddingClient.embedQuery("chunk index recreate pre-flight");
return true;
} catch (Exception e) {
throw new RuntimeException(
"Refusing to start a staged chunk-index recreate: embedding client pre-flight failed", e);
LOG.warn("Embedding client pre-flight failed: {}", e.getMessage(), e);
return false;
}
}
Comment on lines +514 to 522

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -113,4 +114,26 @@ void beginStagedChunkRecreate_abortsWhenTheLiveTargetProbeIsIndeterminate() thro
"abort must name the unresolved live target. Got: " + failure.getMessage());
verify(client, never()).generic();
}

@Test
void beginStagedChunkRecreate_skipsStagingWhenTheEmbeddingProviderIsUnavailable()
throws IOException {
// An unreachable embedding provider must not fail the reindex. Semantic search ships enabled
// with the provider defaulting to bedrock, and the client constructs without ever calling it,
// so any deployment that never set Bedrock up reaches this pre-flight — and the entity reindex
// it would abort does not need embeddings at all. Skip staging, leave the old chunks live, and
// touch nothing in the cluster.
OpenSearchClient client = mock(OpenSearchClient.class);
EmbeddingClient embeddingClient = mock(EmbeddingClient.class);
when(embeddingClient.embedQuery(any(String.class)))
.thenThrow(new RuntimeException("Bedrock embedding generation failed (AWS service error)"));

OpenSearchVectorService service = new OpenSearchVectorService(client, embeddingClient);

assertNull(
service.beginStagedChunkRecreate(),
"an unavailable embedding provider must read as 'not staged', not as a failure");
verify(client, never()).indices();
verify(client, never()).generic();
}
}
Loading