@@ -41,6 +41,7 @@ import (
4141 "time"
4242
4343 "github.com/opensearch-project/opensearch-go/v5/opensearchapi"
44+ "github.com/opensearch-project/opensearch-go/v5/opensearchtransport"
4445 "github.com/opensearch-project/opensearch-go/v5/opensearchutil/shardhash"
4546)
4647
@@ -169,6 +170,7 @@ type BulkIndexerDebugLogger interface {
169170type bulkIndexer struct {
170171 wg sync.WaitGroup
171172 queues []chan BulkIndexerItem
173+ docRouter * opensearchtransport.DocRouter
172174 rrCounter atomic.Uint64 // added for round-robin fallback
173175 workers []* worker
174176 ticker * time.Ticker
@@ -237,10 +239,15 @@ func NewBulkIndexer(cfg BulkIndexerConfig) (BulkIndexer, error) {
237239 if cfg .MetaBufferPoolMaxBytes == 0 {
238240 cfg .MetaBufferPoolMaxBytes = defaultMetaBufferPoolMaxBytes
239241 }
242+ docRouter , err := opensearchtransport .NewDocRouter ()
243+ if err != nil {
244+ return nil , err
245+ }
240246
241247 bi := bulkIndexer {
242248 config : cfg ,
243249 stats : & bulkIndexerStats {},
250+ docRouter : docRouter ,
244251 metaPoolMaxBytes : cfg .MetaBufferPoolMaxBytes ,
245252 implicitClient : implicitClient ,
246253 metaPool : sync.Pool {
@@ -256,21 +263,37 @@ func NewBulkIndexer(cfg BulkIndexerConfig) (BulkIndexer, error) {
256263 return & bi , nil
257264}
258265
259- // Add adds an item to the indexer.
266+ // Add adds an item to the indexer and routes it to the correct worker queue .
260267//
261268// Adding an item after a call to Close() will panic.
262269func (bi * bulkIndexer ) Add (ctx context.Context , item BulkIndexerItem ) error {
263270 var targetQueue chan BulkIndexerItem
264271
272+ //nolint:nestif // keep routing logic inline for simplicity
265273 if item .DocumentID != "" {
266- // Route by murmur3 hash to ensure all actions for the same DocumentID go to the same worker
267- hashValue := shardhash .Hash (item .DocumentID )
274+ idx := item .Index
275+ if idx == "" {
276+ idx = bi .config .Index
277+ }
278+
279+ hashInput := item .DocumentID
280+ path := fmt .Sprintf ("/%s/_doc/%s" , idx , item .DocumentID )
281+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , path , nil )
282+
283+ if err == nil {
284+ hop , evalErr := bi .docRouter .Eval (ctx , req )
285+ if evalErr == nil && hop .Conn != nil {
286+ hashInput = fmt .Sprintf ("%p" , hop .Conn )
287+ }
288+ }
289+
268290 //nolint:gosec // intentional conversion from signed to unsigned for modulo
269- workerIndex := uint32 (hashValue ) % uint32 (bi .config .NumWorkers )
291+ workerIndex := uint32 (shardhash . Hash ( hashInput ) ) % uint32 (bi .config .NumWorkers )
270292 targetQueue = bi .queues [workerIndex ]
271293 } else {
272294 // Round-robin distribution for items without a DocumentID
273- workerIndex := bi .rrCounter .Add (1 ) % uint64 (bi .config .NumWorkers ) //nolint:gosec // NumWorkers is strictly positive
295+ //nolint:gosec // NumWorkers is strictly positive
296+ workerIndex := bi .rrCounter .Add (1 ) % uint64 (bi .config .NumWorkers )
274297 targetQueue = bi .queues [workerIndex ]
275298 }
276299
0 commit comments