-
Notifications
You must be signed in to change notification settings - Fork 427
OAK-12249: lazy ES index provisioning — skip creation for empty reindex #3085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bhabegger
wants to merge
3
commits into
apache:trunk
Choose a base branch
from
oak-indexing:OAK-12249-lazy-provisioning
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
2f564a4
OAK-12249: lazy ES index provisioning — skip creation for empty reindex
bhabegger 2017575
OAK-12249: unalias stale index when a lazy reindex produces zero docu…
bhabegger 68c2956
OAK-12249: fix OSGi baseline versioning for elastic.index/query/util
bhabegger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,46 +62,46 @@ class ElasticIndexWriter implements FulltextIndexWriter<ElasticDocument> { | |
| private final ElasticConnection elasticConnection; | ||
| private final ElasticIndexDefinition indexDefinition; | ||
| private final ElasticBulkProcessorHandler bulkProcessorHandler; | ||
| private final boolean reindex; | ||
| private final boolean requiresProvisioning; | ||
| private final String indexName; | ||
| private final ElasticRetryPolicy retryPolicy; | ||
| private final NodeBuilder definitionBuilder; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only needed in the constructor. Can we remove it? |
||
|
|
||
| ElasticIndexWriter(@NotNull ElasticIndexTracker indexTracker, | ||
| @NotNull ElasticConnection elasticConnection, | ||
| @NotNull ElasticIndexDefinition indexDefinition, | ||
| @NotNull NodeBuilder definitionBuilder, | ||
| boolean reindex, CommitInfo commitInfo, | ||
| boolean requiresProvisioning, CommitInfo commitInfo, | ||
| ElasticBulkProcessorHandler bulkProcessorHandler, | ||
| ElasticRetryPolicy retryPolicy) { | ||
| this.indexTracker = indexTracker; | ||
| this.elasticConnection = elasticConnection; | ||
| this.indexDefinition = indexDefinition; | ||
| this.reindex = reindex; | ||
| this.requiresProvisioning = requiresProvisioning; | ||
| this.bulkProcessorHandler = bulkProcessorHandler; | ||
| this.retryPolicy = retryPolicy; | ||
| this.definitionBuilder = definitionBuilder; | ||
|
|
||
| // We don't use stored index definitions with elastic. Every time a new writer gets created we | ||
| // use the actual index name (based on the current seed) while reindexing, or the alias (pointing to the | ||
| // old index until the new one gets enabled) during incremental reindexing | ||
| if (this.reindex) { | ||
| if (requiresProvisioning) { | ||
| // Full provisioning: generate a seed-based backing index, create it in ES, and prepare | ||
| // for alias flip on close(). Applies to both a standard reindex and an incremental write | ||
| // arriving after a lazy reindex that produced zero documents (OAK-12249). | ||
| try { | ||
| //TODO we should observe changes under inference config path. | ||
| InferenceConfig.reInitialize(); | ||
| // refresh inference config on any index reindex. | ||
| long seed = indexDefinition.indexNameSeed == 0L ? UUID.randomUUID().getMostSignificantBits() : indexDefinition.indexNameSeed; | ||
| // merge gets called on node store later in the indexing flow | ||
| definitionBuilder.setProperty(ElasticIndexDefinition.PROP_INDEX_NAME_SEED, seed); | ||
| // let's store the current mapping version in the index definition | ||
| definitionBuilder.setProperty(ElasticIndexDefinition.PROP_INDEX_MAPPING_VERSION, ElasticIndexDefinition.MAPPING_VERSION.toString()); | ||
|
|
||
| definitionBuilder.removeProperty(ElasticIndexDefinition.PROP_REQUIRES_PROVISIONING); | ||
| indexName = ElasticIndexNameHelper. | ||
| getRemoteIndexName(elasticConnection.getIndexPrefix(), indexDefinition.getIndexPath(), seed); | ||
|
|
||
| provisionIndex(); | ||
| } catch (IOException e) { | ||
| throw new IllegalStateException("Unable to provision index", e); | ||
| } | ||
| } else indexName = indexDefinition.getIndexAlias(); | ||
| } else { | ||
| indexName = indexDefinition.getIndexAlias(); | ||
| } | ||
| boolean waitForESAcknowledgement = true; | ||
| PropertyState async = indexDefinition.getDefinitionNodeState().getProperty("async"); | ||
| if (async != null) { | ||
|
|
@@ -132,14 +132,15 @@ class ElasticIndexWriter implements FulltextIndexWriter<ElasticDocument> { | |
| @NotNull ElasticIndexDefinition indexDefinition, | ||
| @NotNull ElasticBulkProcessorHandler bulkProcessorHandler, | ||
| @NotNull ElasticRetryPolicy retryPolicy, | ||
| boolean reindex) { | ||
| boolean requiresProvisioning) { | ||
| this.indexTracker = indexTracker; | ||
| this.elasticConnection = elasticConnection; | ||
| this.indexDefinition = indexDefinition; | ||
| this.bulkProcessorHandler = bulkProcessorHandler; | ||
| this.indexName = indexDefinition.getIndexAlias(); | ||
| this.retryPolicy = retryPolicy; | ||
| this.reindex = reindex; | ||
| this.requiresProvisioning = requiresProvisioning; | ||
| this.definitionBuilder = null; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -155,7 +156,7 @@ public void updateDocument(String path, ElasticDocument doc) throws IOException | |
| AND InferenceIndexConfig is NOOP | ||
| ) | ||
| */ | ||
| if (reindex | ||
| if (requiresProvisioning | ||
| || (!indexDefinition.isExternallyModifiable() | ||
| && !InferenceConfig.getInstance().isInferenceEnabled() | ||
| && (InferenceIndexConfig.NOOP.equals(InferenceConfig.getInstance().getInferenceIndexConfig(jcrIndexName))))) { | ||
|
|
@@ -196,8 +197,7 @@ public void deleteDocument(String path) throws IOException { | |
| @Override | ||
| public boolean close(long timestamp) throws IOException { | ||
| boolean updateStatus = bulkProcessorHandler.flushIndex(indexName); | ||
| if (reindex) { | ||
| // if we are closing a writer in reindex mode, it means we need to open the new index for queries | ||
| if (requiresProvisioning) { | ||
| this.enableIndex(); | ||
| } | ||
| if (updateStatus) { | ||
|
|
@@ -226,43 +226,47 @@ private void saveMetrics() { | |
|
|
||
| private void provisionIndex() throws IOException { | ||
| final ElasticsearchIndicesClient esClient = elasticConnection.getClient().indices(); | ||
| // check if index already exists | ||
| if (esClient.exists(i -> i.index(indexName)).value()) { | ||
| LOG.info("Index {} already exists. Skip index provision", indexName); | ||
| return; | ||
| } | ||
| createIndex(indexName); | ||
| } | ||
|
|
||
| /** | ||
| * Builds a {@link CreateIndexRequest} for {@code backingIndexName} and submits it to | ||
| * Elasticsearch, with debug logging and idempotent handling of concurrent-creation races. | ||
| */ | ||
| private void createIndex(String backingIndexName) throws IOException { | ||
| final ElasticsearchIndicesClient esClient = elasticConnection.getClient().indices(); | ||
| CreateIndexRequest request; | ||
| try { | ||
| request = ElasticIndexHelper.createIndexRequest(indexName, indexDefinition); | ||
| request = ElasticIndexHelper.createIndexRequest(backingIndexName, indexDefinition); | ||
| } catch (Exception e) { | ||
| LOG.error("Failed to create index {}: {}", indexName, e.toString()); | ||
| LOG.error("Failed to create index {}: {}", backingIndexName, e.toString()); | ||
| throw e; | ||
| } | ||
| if (LOG.isDebugEnabled()) { | ||
| int old = JsonpUtils.maxToStringLength(); | ||
| try { | ||
| // temporarily increase the length, to avoid truncation | ||
| JsonpUtils.maxToStringLength(1_000_000); | ||
| LOG.debug("Creating Index with request {}", request); | ||
| } finally { | ||
| JsonpUtils.maxToStringLength(old); | ||
| } | ||
| } | ||
| // create the new index | ||
| try { | ||
| final CreateIndexResponse response = esClient.create(request); | ||
| LOG.info("Created index {}. Response acknowledged: {}", indexName, response.acknowledged()); | ||
| checkResponseAcknowledgement(response, "Create index call not acknowledged for index " + indexName); | ||
| LOG.info("Created index {}. Response acknowledged: {}", backingIndexName, response.acknowledged()); | ||
| checkResponseAcknowledgement(response, "Create index call not acknowledged for index " + backingIndexName); | ||
| } catch (ElasticsearchException ese) { | ||
| // We already check index existence as first thing in this method, if we get here it means we have got into | ||
| // a conflict (eg: multiple cluster nodes provision concurrently). | ||
| // Elasticsearch does not have a CREATE IF NOT EXIST, need to inspect exception | ||
| // We already check index existence as first thing in provisionIndex(); if we get here it | ||
| // means a concurrent cluster node raced us. Elasticsearch has no CREATE IF NOT EXISTS: | ||
| // https://github.com/elastic/elasticsearch/issues/19862 | ||
| if (ese.status() == 400 && ese.getMessage().contains("resource_already_exists_exception")) { | ||
| LOG.warn("Index {} already exists. Ignoring error", indexName); | ||
| LOG.warn("Index {} already exists. Ignoring error", backingIndexName); | ||
| } else { | ||
| LOG.warn("Failed to create index {}", indexName, ese); | ||
| LOG.warn("Failed to create index {}", backingIndexName, ese); | ||
| StringBuilder sb = new StringBuilder(); | ||
| int old = JsonpUtils.maxToStringLength(); | ||
| try { | ||
|
|
@@ -339,4 +343,40 @@ private void deleteOldIndices(ElasticsearchIndicesClient indicesClient, Set<Stri | |
| checkResponseAcknowledgement(deleteIndexResponse, "Delete index call not acknowledged for indices " + indices); | ||
| LOG.info("Deleted indices {}. Response acknowledged: {}", indices, deleteIndexResponse.acknowledged()); | ||
| } | ||
|
|
||
| /** | ||
| * Removes the alias and deletes the backing index for {@code indexDefinition}, if one is | ||
| * currently provisioned. Used when a lazy-provisioning reindex (OAK-12249) closes having | ||
| * written zero documents: without this, a previously-provisioned index would keep its stale | ||
| * alias and backing index, serving pre-reindex content indefinitely instead of going empty. | ||
| * | ||
| * <p>No-op if nothing is currently aliased — that is the state a never-provisioned index is | ||
| * already in, so there is nothing to clean up. | ||
| */ | ||
| static void unaliasIfProvisioned(@NotNull ElasticConnection elasticConnection, | ||
| @NotNull ElasticIndexDefinition indexDefinition) throws IOException { | ||
| ElasticsearchIndicesClient client = elasticConnection.getClient().indices(); | ||
| GetAliasResponse aliasResponse = client.getAlias(garb -> | ||
| garb.index(indexDefinition.getIndexAlias()).ignoreUnavailable(true)); | ||
| if (aliasResponse.result().isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| UpdateAliasesRequest removeAliasesRequest = UpdateAliasesRequest.of(rb -> { | ||
| aliasResponse.result().forEach((idx, idxAliases) -> rb.actions(ab -> | ||
| ab.remove(rab -> rab.index(idx).aliases(new ArrayList<>(idxAliases.aliases().keySet()))))); | ||
| return rb; | ||
| }); | ||
| UpdateAliasesResponse updateAliasesResponse = client.updateAliases(removeAliasesRequest); | ||
| if (!updateAliasesResponse.acknowledged()) { | ||
| throw new IllegalStateException("Remove alias call not acknowledged for alias " + indexDefinition.getIndexAlias()); | ||
| } | ||
|
|
||
| DeleteIndexResponse deleteIndexResponse = client.delete(db -> db.index(new ArrayList<>(aliasResponse.result().keySet()))); | ||
| if (!deleteIndexResponse.acknowledged()) { | ||
| throw new IllegalStateException("Delete index call not acknowledged for indices " + aliasResponse.result().keySet()); | ||
| } | ||
| LOG.info("Reindex produced no documents for a previously-provisioned index — removed stale alias {} and deleted {}", | ||
| indexDefinition.getIndexAlias(), aliasResponse.result().keySet()); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is actually no need for that. We could remove it