Skip to content

feat[opensearchutil]: route subsequent bulk requests on the same documentID to the same worker. - #950

Draft
yanaix10 wants to merge 7 commits into
opensearch-project:mainfrom
yanaix10:fix-#464
Draft

feat[opensearchutil]: route subsequent bulk requests on the same documentID to the same worker.#950
yanaix10 wants to merge 7 commits into
opensearch-project:mainfrom
yanaix10:fix-#464

Conversation

@yanaix10

Copy link
Copy Markdown

Changes made :

  • Struct Update - queue chan BulkIndexerItem -> queues []chan BulkIndexerItem sized to NumWorkers
  • Implemented OpenSearch shardhash.Hash() for documentID.
  • Item routed to hashValue % NumWorkers.
  • If no DocumentID is present, it falls back to a lock-free round-robin distribution via atomic.Uint64.
  • Updated close function : Iterates through and closes all worker channels.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.18182% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.46%. Comparing base (2f11b1b) to head (57b0b4f).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
opensearchutil/bulk_indexer.go 68.18% 12 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #950      +/-   ##
==========================================
- Coverage   55.47%   55.46%   -0.02%     
==========================================
  Files         660      660              
  Lines       64338    64378      +40     
==========================================
+ Hits        35694    35705      +11     
- Misses      26804    26828      +24     
- Partials     1840     1845       +5     
Flag Coverage Δ
integration 29.54% <68.18%> (+<0.01%) ⬆️
unit 54.18% <68.18%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
opensearchutil/bulk_indexer.go 85.18% <68.18%> (-2.85%) ⬇️

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@yanaix10

Copy link
Copy Markdown
Author

hey @sean- I am not very sure about the test case I wrote. I hardcorded the documentID.

@yanaix10
yanaix10 force-pushed the fix-#464 branch 2 times, most recently from 5155e4f to e7d9f58 Compare July 12, 2026 23:52
@yanaix10 yanaix10 changed the title feat(opensearchutil): route subsequent bulk requests on the same documentID to the same worker. feat[opensearchutil]: route subsequent bulk requests on the same documentID to the same worker. Jul 12, 2026
@sean- sean- added the enhancement New feature or request label Jul 15, 2026

@sean- sean- left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Admittedly, I haven't looked into the opensearchutil/BulkIndexer much, but I'm not a fan of it in its current incarnation because it's interface is not particularly rigid. That said, I think its internals should be adapted to use the opensearchtransport/DocRouter until a v2 of this package can be put together to tighten up its interface.

Comment thread opensearchutil/bulk_indexer.go
//
// Adding an item after a call to Close() will panic.
func (bi *bulkIndexer) Add(ctx context.Context, item BulkIndexerItem) error {
var targetQueue chan BulkIndexerItem

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here, we'd take a BulkIndexerItem, encapsulate it as an http.Request, then pass it along to DocRouter.Eval(). In fact, there may be a chance that the new routing code in v5 (and if opted-in, v4) may just "do this" out of the box on a best-effort basis, but to force this, we should have opensearchtuil/ construct a DocRouter and firehose things into the DocRouter.

Comment thread CHANGELOG.md
- Default the benchmark pprof server to an ephemeral loopback port so back-to-back `go test -bench` runs no longer collide on a `TIME_WAIT` socket held by the prior run. The startup logic moves into an `internal/pprofutil` package that registers the pprof handlers on a private mux (off `http.DefaultServeMux`); `PPROF_ADDR` pins an explicit `host:port` when needed. ([#864](https://github.com/opensearch-project/opensearch-go/issues/864))

- Consistently route bulk requests for the same DocumentID to the same worker to prevent race conditions ([#950](https://github.com/opensearch-project/opensearch-go/pull/950)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@sean-
sean- marked this pull request as draft July 17, 2026 15:12
@yanaix10

Copy link
Copy Markdown
Author

hey @sean- I have made required changes based on the feedback. Please review.

Comment thread opensearchutil/bulk_indexer.go Outdated
Comment thread opensearchutil/bulk_indexer.go Outdated
Comment thread opensearchutil/bulk_indexer.go Outdated
Comment thread opensearchutil/bulk_indexer.go Outdated
@yanaix10
yanaix10 requested a review from sean- July 25, 2026 19:12
Comment thread opensearchutil/bulk_indexer.go Outdated
idx = bi.config.Index
}

u := url.URL{Path: path.Join("/", idx, "_doc", item.DocumentID)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's nothing here that escapes idx: we're just trusting the user input blindly. Can you look into url.PathUnescape() and look at the ./internal/build/ package and how it is used.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I have used url.PathEscape() on idx and documentID to handle user input. manually constructing the http.Request and explicitly setting Path (via url.PathUnescape()) and RawPath. Due to manual construction I could drop http.NewRequest entirely - bypassing url.Parse allocation overhead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Because this is in the bulk-path, I'm not too worried about the performance, but it's up to you.

@sean-
sean- self-requested a review July 31, 2026 00:45

@sean- sean- left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for adapting to using the router!

yanaix10 added 7 commits July 30, 2026 17:50
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
…retain Eval

Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>
Signed-off-by: Naitik Yadav <naitik.yadav641@gmail.com>

encodedPath := "/" + url.PathEscape(idx) + "/_doc/" + url.PathEscape(item.DocumentID)

if p, err := url.PathUnescape(encodedPath); err == nil {

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.

Question about the escape/unescape pair here: does the escaping survive to Eval?
Would putting the escaped form straight into Path work for what you were after, dropping PathUnescape and RawPath?

},
}).WithContext(ctx)

if hop, evalErr := bi.docRouter.Eval(ctx, req); evalErr == nil && hop.Conn != nil {

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.

Does this Eval ever return a connection? I think NewDocRouter() gets its connections through DiscoveryUpdate(), which only the owning transport calls, so activeConns looks like it stays empty and Eval returns early at policy_doc_router.go:144-150.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants