Skip to content

Batch index creation#144074

Merged
inespot merged 20 commits intoelastic:mainfrom
inespot:feature/batch-index-creation
Mar 20, 2026
Merged

Batch index creation#144074
inespot merged 20 commits intoelastic:mainfrom
inespot:feature/batch-index-creation

Conversation

@inespot
Copy link
Copy Markdown
Contributor

@inespot inespot commented Mar 12, 2026

Processes all the waiting create-index tasks in a single cluster-state update.

Relates to ES-13198

@inespot inespot force-pushed the feature/batch-index-creation branch 2 times, most recently from 9ef9e00 to a1395e1 Compare March 12, 2026 17:38
inespot added a commit to inespot/elasticsearch that referenced this pull request Mar 12, 2026
The reroute behavior was embedded in CreateIndexClusterStateUpdateRequest and CreateDataStreamClusterStateUpdateRequest, while the reroute listener was passed separately. This change clarifies the relationship between the two by passing RerouteBehavior alongside the listener as a method parameter. It also enables the batched reroute logic in elastic#144074.

Relates to ES-13198.
@inespot inespot force-pushed the feature/batch-index-creation branch from a1395e1 to 115beed Compare March 13, 2026 02:20
inespot added a commit that referenced this pull request Mar 13, 2026
* Extract reroute behavior from create-index request classes

The reroute behavior was embedded in CreateIndexClusterStateUpdateRequest and CreateDataStreamClusterStateUpdateRequest, while the reroute listener was passed separately. This change clarifies the relationship between the two by passing RerouteBehavior alongside the listener as a method parameter. It also enables the batched reroute logic in #144074.

Relates to ES-13198.

* Re-add deleted comment

* Small fixes

* Missing param in javadoc
DaveCTurner and others added 3 commits March 13, 2026 12:35
Processes all the waiting create-index tasks in a single cluster-state
update.
@inespot inespot force-pushed the feature/batch-index-creation branch from 115beed to afad4a3 Compare March 13, 2026 16:35
RerouteBehavior.SKIP_REROUTE,
rerouteCompletionIsNotRequired()
);
taskContext.success(task.getAckListener(allocationActionMultiListener.delay(task.listener)));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The N ack listeners feel slightly wasteful since there's ultimately a single cluster state publication, but the batch executor API doesn't expose a batch-level ack listener. Combined with the need to delay all task listener responses until reroute() completes and the already existing AllocationActionMultiListener, your original proposal seems like the cleanest way to wire this up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I'd like to move this whole acking thing elsewhere tbh but not today. Struggling to imagine a way we could be creating enough indices at once that the number of listeners here is a problem without having already hit some other way more serious problems first tho.

/**
* Service responsible for submitting create index requests
*/
public class MetadataCreateIndexService {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also noticed MetadataCreateIndexService is getting quite large and difficult to read. Outside the scope of this PR but I was wondering if it would make sense to extract some of the validation logic (which constitutes a large part of the code) into a dedicated class. Maybe a utils-style helper (like SnapshotUtils) or a validation-focused class (like AliasValidator). Any thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmaybe, I mean I agree it's not very nice to work with, but I'm not sure simply moving the code elsewhere will help. There's some underlying structure that I feel we're missing which would simplify this. Possibly some amount of Strategy pattern would help? Not sure really.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, thanks for the link! Maybe that could work, although I'm not certain strategies would separate so cleanly here. I'll try to look into it a little more as a FLUP.

@inespot inespot marked this pull request as ready for review March 14, 2026 04:08
@elasticsearchmachine elasticsearchmachine added the needs:triage Requires assignment of a team area label label Mar 14, 2026
@inespot inespot added >non-issue :Distributed/Distributed A catch all label for anything in the Distributed Area. Please avoid if you can. and removed needs:triage Requires assignment of a team area label labels Mar 14, 2026
@elasticsearchmachine elasticsearchmachine added the Team:Distributed Meta label for distributed team. label Mar 14, 2026
@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

@inespot inespot requested a review from DaveCTurner March 16, 2026 02:25
ncordon pushed a commit to ncordon/elasticsearch that referenced this pull request Mar 16, 2026
…44140)

* Extract reroute behavior from create-index request classes

The reroute behavior was embedded in CreateIndexClusterStateUpdateRequest and CreateDataStreamClusterStateUpdateRequest, while the reroute listener was passed separately. This change clarifies the relationship between the two by passing RerouteBehavior alongside the listener as a method parameter. It also enables the batched reroute logic in elastic#144074.

Relates to ES-13198.

* Re-add deleted comment

* Small fixes

* Missing param in javadoc
@inespot
Copy link
Copy Markdown
Contributor Author

inespot commented Mar 16, 2026

ncordon pushed a commit to ncordon/elasticsearch that referenced this pull request [2 hours ago]

I think this noise is caused by the fact that I added a ref to this PR in the actual commit messages of PR 144140 (oops). Will know to avoid adding those types of refs next time

Copy link
Copy Markdown
Member

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good - a couple of top-level requests:

  1. Could you pull the change to testValidateIndexName out to a separate PR
  2. Could you fix the position of the RerouteBehavior argument (either revert its changes here, or leave them as-is here but bring main in line with a separate PR)

Other than that, a few inline comments mostly around testing.

Comment on lines +213 to +216
assertThat(successCount, equalTo(validIndicesNames.size()));
assertThat(invalidNameExceptionCount, equalTo(invalidNameCount));
assertThat(alreadyExistsExceptionCount, equalTo(duplicateCount));
assertThat(indexCreationExceptionCount, equalTo(invalidSettingsCount));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than just checking the counts, could we check that each request gets the outcome we expect? You can use ActionTestUtils#assertNoFailureListener for the expected successes, and ActionTestUtils#assertNoSuccessListener for the expected failures. Then we can wait on all the responses with a single CountDownLatch rather than multiple Future.get() calls each of which might take 30s to time out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the tip! Refactored in 3a5295

allRequestNames.add(indexName);
}
// No collisions
assertThat(validIndicesNames.size(), equalTo(validRequestCount));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this could fail, with very low probability, but it's a little unclear to the reader what's happening here. We want N distinct strings, so I'd suggest adding a random string to a set repeatedly until the set's size is as desired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the possible collision in 3a5295

invalidNameCount++;
}
case 1 -> {
final var indexName = randomIndexName();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here, we need this not to be one of the valid index names. But it can match an invalid one since we won't be creating those indices.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3a5295

int failureType = validIndicesNames.isEmpty() ? randomIntBetween(0, 1) : randomIntBetween(0, 2);
switch (failureType) {
case 0 -> {
allRequestNames.add("INVALID_" + randomAlphaOfLength(6).toLowerCase(Locale.ROOT));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe randomIdentifier("INVALID_")? Or even randomIdentifier("INVALID_BECAUSE_UPPER_CASE_")?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3a5295

/**
* Service responsible for submitting create index requests
*/
public class MetadataCreateIndexService {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmaybe, I mean I agree it's not very nice to work with, but I'm not sure simply moving the code elsewhere will help. There's some underlying structure that I feel we're missing which would simplify this. Possibly some amount of Strategy pattern would help? Not sure really.

RerouteBehavior.SKIP_REROUTE,
rerouteCompletionIsNotRequired()
);
taskContext.success(task.getAckListener(allocationActionMultiListener.delay(task.listener)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I'd like to move this whole acking thing elsewhere tbh but not today. Struggling to imagine a way we could be creating enough indices at once that the number of listeners here is a problem without having already hit some other way more serious problems first tho.

Collections.shuffle(allRequestNames, random());

final ClusterStateListener listener = event -> {
final var projectMetadata = event.state().metadata().getProject(ProjectId.DEFAULT);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes also any chance we can test batching across several projects? Not sure quite what that might entail for this test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's possible, see 23eabb.
That said, it does require overriding the project resolver to use TestOnlyMultiProjectResolver (instead of the DefaultProjectResolver), which isn't used in production. So we're exercising a somewhat artificial code path to test a future feature. It also requires a new gradle import (MP IT testing does not appear to be leveraged other tests in this package). We could consider splitting into two test classes (one for single-project and one for multi-project) to keep coverage for both or we could drop the multi-project testing for now and revisit when multi-project is closer to production-ready. Let me know your thoughts on this.
Side note, I have not yet managed to make the multi project setup work without disabling routing via routing.allocation.enable: none and using waitForActiveShards(NONE). Still looking into it though

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see ok, thanks for investigating so deeply! The need for the new Gradle import tells us that nothing else is testing in this way today and there's nothing particularly special about these tests in terms of multi-project support so let's back this out and leave it for the multi-project team to strengthen all these tests when the time is right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

final var indexName = addRandomIndexNameNoCollision(allIndexNames);
validIndicesByProject.putIfAbsent(projectId, new HashSet<>());
validIndicesByProject.get(projectId).add(indexName);
allRequests.add(new CreateIndexRequestSpec(indexName, false, CreateIndexResult.SUCCESS, projectId));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to track all the request-specs in a separate data structure like this? I would expect we can do this with one loop, after the master service is blocked, which constructs each request together with its expected-result listener, and immediately sends it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, and that's much nicer, thanks! Refactored as suggested in d1e63e

@inespot
Copy link
Copy Markdown
Contributor Author

inespot commented Mar 20, 2026

part-1 test failure in TsidExtractingIdFieldMapperTests, which should be unrelated to this PR. Previous runs were passing. I'll merge in main later today, CI tends to be more stable during US evenings 🙂

@inespot inespot requested a review from DaveCTurner March 20, 2026 14:41
Copy link
Copy Markdown
Member

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM great stuff. I think this should be an >enhancement rather than >non-issue- it very much deserves a mention in the release notes.

@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Hi @inespot, I've created a changelog YAML for you.

@inespot inespot merged commit 248bbe1 into elastic:main Mar 20, 2026
36 checks passed
michalborek pushed a commit to michalborek/elasticsearch that referenced this pull request Mar 23, 2026
…44140)

* Extract reroute behavior from create-index request classes

The reroute behavior was embedded in CreateIndexClusterStateUpdateRequest and CreateDataStreamClusterStateUpdateRequest, while the reroute listener was passed separately. This change clarifies the relationship between the two by passing RerouteBehavior alongside the listener as a method parameter. It also enables the batched reroute logic in elastic#144074.

Relates to ES-13198.

* Re-add deleted comment

* Small fixes

* Missing param in javadoc
michalborek pushed a commit to michalborek/elasticsearch that referenced this pull request Mar 23, 2026
* Batch create-index tasks

Processes all the waiting create-index tasks in a single cluster-state
update.

* Small nits and fixes

* Add small comment

* Revert param position move

* Add failure testing + fix unused variables

* More testing + style fixes

* Rename + small javadoc

* More nits

* Further strengthen tests

* Tests improvement: no collision, latches & nits

* testCreateIndexBatching supports multi projects

* Revert "testCreateIndexBatching supports multi projects"

This reverts commit 23eabb5.

* Test consolidate request build and send

* Missed this one

* Update docs/changelog/144074.yaml

---------

Co-authored-by: David Turner <david.turner@elastic.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Distributed/Distributed A catch all label for anything in the Distributed Area. Please avoid if you can. >enhancement Team:Distributed Meta label for distributed team. v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants