Skip to content

opensearch-java: send bulk requests with the OpenSearchAsyncClient - #2225

Merged
rzo1 merged 1 commit into
mainfrom
opensearch-java-async-bulk
Oct 7, 2026
Merged

rzo1 merged 1 commit into
mainfrom
opensearch-java-async-bulk

Conversation

@jnioche

@jnioche jnioche commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

The StatusUpdaterBolt of the opensearch-java module is slower than the one of the legacy opensearch module. A likely cause is the way AsyncBulkProcessor executes the bulk requests.

It ran the blocking OpenSearchClient.bulk() call on a ThreadPoolExecutor bounded to concurrentRequests threads, with a SynchronousQueue and CallerRunsPolicy. The semaphore permit was released before the listener processed the response, so the bolt could acquire it and submit the next request while the only worker (with the default concurrentRequests=1) was still busy in afterBulk. The task was then rejected and the bulk call ran on the bolt's own executor thread, blocking it for the whole HTTP round trip instead of letting it fill the next batch.

Changes

  • AsyncBulkProcessor uses OpenSearchAsyncClient.bulk(), like the legacy BulkProcessor used bulkAsync; the dedicated executor is removed.
  • The permit is released only once the listener has run, as in the legacy implementation. Back-pressure is unchanged: add() blocks only when concurrentRequests bulks are in flight.
  • OpenSearchConnection builds the async client on the same transport as the sync one.
  • Tests updated, plus a new test checking that add() does not wait for the bulk response.

Note that afterBulk now runs on the HTTP client's I/O reactor thread, which is also where the legacy module ran it.

🤖 Generated with Claude Code

AsyncBulkProcessor ran the blocking OpenSearchClient.bulk() call on a
ThreadPoolExecutor bounded to concurrentRequests threads, with a
SynchronousQueue and CallerRunsPolicy. Since the semaphore permit was
released before the listener processed the response, the bolt could
submit the next request while the only worker was still in afterBulk;
the task was then rejected and the bulk call executed on the bolt's
own thread, blocking it for the whole HTTP round trip.

Use OpenSearchAsyncClient instead, as the legacy BulkProcessor did with
bulkAsync, and release the permit only once the listener has run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jnioche

jnioche commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

This brings the performance on par with that of the legacy module.

@jnioche jnioche added this to the 4.0.0 milestone Oct 6, 2026

@dpol1 dpol1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracking this down! The pool with CallerRunsPolicy from #1868 was the culprit: with the permit released before afterBulk, under load the next bulk ended up running on the calling thread. LGTM, good to see it on par with the legacy module.

*/
@Test
@Timeout(10)
void addDoesNotWaitForBulkResponse() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One optional follow-up: I have a small regression test that holds afterBulk on a latch and checks that a second add() waits. It catches the permit release moving back before the callback. Happy to contribute it.

@rzo1
rzo1 merged commit 6b9398b into main Oct 7, 2026
2 checks passed
@rzo1
rzo1 deleted the opensearch-java-async-bulk branch October 7, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants