go.temporal.io/server@v1.23.0/common/persistence/visibility/store/elasticsearch/processor_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package elasticsearch 26 27 import ( 28 "context" 29 "encoding/json" 30 "fmt" 31 "sync" 32 "testing" 33 "time" 34 35 "github.com/golang/mock/gomock" 36 "github.com/olivere/elastic/v7" 37 "github.com/stretchr/testify/suite" 38 39 "go.temporal.io/server/common" 40 "go.temporal.io/server/common/collection" 41 "go.temporal.io/server/common/dynamicconfig" 42 "go.temporal.io/server/common/future" 43 "go.temporal.io/server/common/log" 44 "go.temporal.io/server/common/metrics" 45 "go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client" 46 "go.temporal.io/server/common/searchattribute" 47 ) 48 49 type processorSuite struct { 50 suite.Suite 51 controller *gomock.Controller 52 esProcessor *processorImpl 53 mockBulkProcessor *client.MockBulkProcessor 54 mockMetricHandler *metrics.MockHandler 55 mockESClient *client.MockClient 56 } 57 58 var ( 59 testID = "test-doc-id" 60 ) 61 62 func TestElasticsearchProcessorSuite(t *testing.T) { 63 s := new(processorSuite) 64 suite.Run(t, s) 65 } 66 67 func (s *processorSuite) SetupSuite() { 68 } 69 70 func (s *processorSuite) SetupTest() { 71 logger := log.NewTestLogger() 72 73 s.controller = gomock.NewController(s.T()) 74 75 cfg := &ProcessorConfig{ 76 IndexerConcurrency: dynamicconfig.GetIntPropertyFn(32), 77 ESProcessorNumOfWorkers: dynamicconfig.GetIntPropertyFn(1), 78 ESProcessorBulkActions: dynamicconfig.GetIntPropertyFn(10), 79 ESProcessorBulkSize: dynamicconfig.GetIntPropertyFn(2 << 20), 80 ESProcessorFlushInterval: dynamicconfig.GetDurationPropertyFn(1 * time.Minute), 81 } 82 83 s.mockMetricHandler = metrics.NewMockHandler(s.controller) 84 s.mockMetricHandler.EXPECT().WithTags(metrics.OperationTag(metrics.ElasticsearchBulkProcessor)). 85 Return(s.mockMetricHandler).AnyTimes() 86 s.mockBulkProcessor = client.NewMockBulkProcessor(s.controller) 87 s.mockESClient = client.NewMockClient(s.controller) 88 s.esProcessor = NewProcessor(cfg, s.mockESClient, logger, s.mockMetricHandler) 89 90 // esProcessor.Start mock 91 s.esProcessor.mapToAckFuture = collection.NewShardedConcurrentTxMap(1024, s.esProcessor.hashFn) 92 s.esProcessor.bulkProcessor = s.mockBulkProcessor 93 s.esProcessor.status = common.DaemonStatusStarted 94 } 95 96 func (s *processorSuite) TearDownTest() { 97 s.controller.Finish() 98 } 99 100 func (s *processorSuite) TestNewESProcessorAndStartStop() { 101 config := &ProcessorConfig{ 102 IndexerConcurrency: dynamicconfig.GetIntPropertyFn(32), 103 ESProcessorNumOfWorkers: dynamicconfig.GetIntPropertyFn(1), 104 ESProcessorBulkActions: dynamicconfig.GetIntPropertyFn(10), 105 ESProcessorBulkSize: dynamicconfig.GetIntPropertyFn(2 << 20), 106 ESProcessorFlushInterval: dynamicconfig.GetDurationPropertyFn(1 * time.Minute), 107 } 108 109 p := NewProcessor(config, s.mockESClient, s.esProcessor.logger, s.mockMetricHandler) 110 111 s.mockESClient.EXPECT().RunBulkProcessor(gomock.Any(), gomock.Any()). 112 DoAndReturn(func(_ context.Context, input *client.BulkProcessorParameters) (client.BulkProcessor, error) { 113 s.Equal(visibilityProcessorName, input.Name) 114 s.Equal(config.ESProcessorNumOfWorkers(), input.NumOfWorkers) 115 s.Equal(config.ESProcessorBulkActions(), input.BulkActions) 116 s.Equal(config.ESProcessorBulkSize(), input.BulkSize) 117 s.Equal(config.ESProcessorFlushInterval(), input.FlushInterval) 118 s.NotNil(input.AfterFunc) 119 120 bulkProcessor := client.NewMockBulkProcessor(s.controller) 121 bulkProcessor.EXPECT().Stop() 122 return bulkProcessor, nil 123 }). 124 Times(1) 125 126 p.Start() 127 s.NotNil(p.mapToAckFuture) 128 s.NotNil(p.bulkProcessor) 129 130 p.Stop() 131 s.NotNil(p.mapToAckFuture) 132 s.NotNil(p.bulkProcessor) 133 } 134 135 func (s *processorSuite) TestAdd() { 136 request := &client.BulkableRequest{} 137 visibilityTaskKey := "test-key" 138 s.Equal(0, s.esProcessor.mapToAckFuture.Len()) 139 140 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc) 141 s.mockBulkProcessor.EXPECT().Add(request) 142 143 future1 := s.esProcessor.Add(request, visibilityTaskKey) 144 s.Equal(1, s.esProcessor.mapToAckFuture.Len()) 145 if future1.Ready() { 146 s.Fail("1st request shouldn't be acknowledged") 147 } 148 149 // duplicate request returns same future object 150 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorDuplicateRequest.Name()).Return(metrics.NoopCounterMetricFunc) 151 future2 := s.esProcessor.Add(request, visibilityTaskKey) 152 s.Equal(1, s.esProcessor.mapToAckFuture.Len()) 153 154 s.Equal(future1, future2) 155 156 if future1.Ready() { 157 s.Fail("1st request shouldn't be acknowledged") 158 } 159 } 160 161 func (s *processorSuite) TestAdd_ConcurrentAdd() { 162 request := &client.BulkableRequest{} 163 docsCount := 1000 164 parallelFactor := 10 165 futures := make([]future.Future[bool], docsCount) 166 167 wg := sync.WaitGroup{} 168 wg.Add(parallelFactor) 169 s.mockBulkProcessor.EXPECT().Add(request).Times(docsCount) 170 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc).Times(docsCount) 171 for i := 0; i < parallelFactor; i++ { 172 go func(i int) { 173 for j := 0; j < docsCount/parallelFactor; j++ { 174 futures[i*docsCount/parallelFactor+j] = s.esProcessor.Add(request, fmt.Sprintf("test-key-%d-%d", i, j)) 175 } 176 wg.Done() 177 }(i) 178 } 179 wg.Wait() 180 s.Equal(docsCount, s.esProcessor.mapToAckFuture.Len()) 181 182 for i := 0; i < docsCount; i++ { 183 if futures[i].Ready() { 184 s.Fail("all request must be in the bulk") 185 } 186 } 187 } 188 189 func (s *processorSuite) TestAdd_ConcurrentAdd_Duplicates() { 190 request := &client.BulkableRequest{} 191 key := "test-key" 192 duplicates := 100 193 futures := make([]future.Future[bool], duplicates) 194 s.mockMetricHandler.EXPECT(). 195 Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()). 196 Return(metrics.NoopTimerMetricFunc) 197 s.mockMetricHandler.EXPECT(). 198 Counter(metrics.ElasticsearchBulkProcessorDuplicateRequest.Name()). 199 Return(metrics.NoopCounterMetricFunc).Times(duplicates - 1) 200 201 wg := sync.WaitGroup{} 202 wg.Add(duplicates) 203 s.mockBulkProcessor.EXPECT().Add(request) 204 for i := 0; i < duplicates; i++ { 205 go func(i int) { 206 futures[i] = s.esProcessor.Add(request, key) 207 wg.Done() 208 }(i) 209 } 210 wg.Wait() 211 pendingRequestsCount := 0 212 for i := 0; i < duplicates; i++ { 213 if futures[i].Ready() { 214 s.Fail("all request must be in the bulk") 215 } else { 216 pendingRequestsCount++ 217 } 218 } 219 220 s.Equal(100, pendingRequestsCount, "only one request should not be acked") 221 s.Equal(1, s.esProcessor.mapToAckFuture.Len(), "only one request should be in the bulk") 222 } 223 224 func (s *processorSuite) TestAdd_ConcurrentAdd_Shutdown() { 225 request := &client.BulkableRequest{} 226 docsCount := 1000 227 parallelFactor := 10 228 futures := make([]future.Future[bool], docsCount) 229 230 s.mockBulkProcessor.EXPECT().Add(request).MaxTimes(docsCount + 2) // +2 for explicit adds before and after shutdown 231 s.mockBulkProcessor.EXPECT().Stop().Return(nil).Times(1) 232 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc).MaxTimes(docsCount + 2) 233 234 addBefore := s.esProcessor.Add(request, "test-key-before") 235 236 wg := sync.WaitGroup{} 237 wg.Add(parallelFactor + 1) // +1 for separate shutdown goroutine 238 for i := 0; i < parallelFactor; i++ { 239 go func(i int) { 240 for j := 0; j < docsCount/parallelFactor; j++ { 241 futures[i*docsCount/parallelFactor+j] = s.esProcessor.Add(request, fmt.Sprintf("test-key-%d-%d", i, j)) 242 } 243 wg.Done() 244 }(i) 245 } 246 go func() { 247 time.Sleep(1 * time.Millisecond) // slight delay so at least a few docs get added 248 s.esProcessor.Stop() 249 wg.Done() 250 }() 251 252 wg.Wait() 253 addAfter := s.esProcessor.Add(request, "test-key-after") 254 255 s.False(addBefore.Ready()) // first request should be in bulk 256 s.True(addAfter.Ready()) // final request should be only error 257 _, err := addAfter.Get(context.Background()) 258 s.ErrorIs(err, errVisibilityShutdown) 259 } 260 261 func (s *processorSuite) TestBulkAfterAction_Ack() { 262 version := int64(3) 263 testKey := "testKey" 264 request := elastic.NewBulkIndexRequest(). 265 Index(testIndex). 266 Id(testID). 267 Version(version). 268 Doc(map[string]interface{}{searchattribute.VisibilityTaskKey: testKey}) 269 requests := []elastic.BulkableRequest{request} 270 271 mSuccess := map[string]*elastic.BulkResponseItem{ 272 "index": { 273 Index: testIndex, 274 Id: testID, 275 Version: version, 276 Status: 200, 277 }, 278 } 279 response := &elastic.BulkResponse{ 280 Took: 3, 281 Errors: false, 282 Items: []map[string]*elastic.BulkResponseItem{mSuccess}, 283 } 284 285 queuedRequestHistogram := metrics.NewMockHistogramIface(s.controller) 286 s.mockMetricHandler.EXPECT().Histogram( 287 metrics.ElasticsearchBulkProcessorQueuedRequests.Name(), 288 metrics.ElasticsearchBulkProcessorQueuedRequests.Unit(), 289 ).Return(queuedRequestHistogram) 290 queuedRequestHistogram.EXPECT().Record(int64(0)) 291 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorBulkResquestTookLatency.Name()).Return(metrics.NoopTimerMetricFunc) 292 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorRequestLatency.Name()).Return(metrics.NoopTimerMetricFunc) 293 mapVal := newAckFuture() 294 s.esProcessor.mapToAckFuture.Put(testKey, mapVal) 295 s.esProcessor.bulkAfterAction(0, requests, response, nil) 296 result, err := mapVal.future.Get(context.Background()) 297 s.NoError(err) 298 s.True(result) 299 } 300 301 func (s *processorSuite) TestBulkAfterAction_Nack() { 302 version := int64(3) 303 testKey := "testKey" 304 305 wid := "test-workflowID" 306 rid := "test-runID" 307 namespaceID := "test-namespaceID" 308 309 request := elastic.NewBulkIndexRequest(). 310 Index(testIndex). 311 Id(testID). 312 Version(version). 313 Doc(map[string]interface{}{ 314 searchattribute.VisibilityTaskKey: testKey, 315 searchattribute.NamespaceID: namespaceID, 316 searchattribute.WorkflowID: wid, 317 searchattribute.RunID: rid, 318 }) 319 requests := []elastic.BulkableRequest{request} 320 321 mFailed := map[string]*elastic.BulkResponseItem{ 322 "index": { 323 Index: testIndex, 324 Id: testID, 325 Version: version, 326 Status: 400, 327 }, 328 } 329 response := &elastic.BulkResponse{ 330 Took: 3, 331 Errors: false, 332 Items: []map[string]*elastic.BulkResponseItem{mFailed}, 333 } 334 335 queuedRequestHistogram := metrics.NewMockHistogramIface(s.controller) 336 s.mockMetricHandler.EXPECT().Histogram( 337 metrics.ElasticsearchBulkProcessorQueuedRequests.Name(), 338 metrics.ElasticsearchBulkProcessorQueuedRequests.Unit(), 339 ).Return(queuedRequestHistogram) 340 queuedRequestHistogram.EXPECT().Record(int64(0)) 341 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorBulkResquestTookLatency.Name()).Return(metrics.NoopTimerMetricFunc) 342 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorRequestLatency.Name()).Return(metrics.NoopTimerMetricFunc) 343 mapVal := newAckFuture() 344 s.esProcessor.mapToAckFuture.Put(testKey, mapVal) 345 counterMetric := metrics.NewMockCounterIface(s.controller) 346 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorFailures.Name()).Return(counterMetric) 347 counterMetric.EXPECT().Record(int64(1), metrics.HttpStatusTag(400)) 348 349 s.esProcessor.bulkAfterAction(0, requests, response, nil) 350 result, err := mapVal.future.Get(context.Background()) 351 s.NoError(err) 352 s.False(result) 353 } 354 355 func (s *processorSuite) TestBulkAfterAction_Error() { 356 version := int64(3) 357 doc := map[string]interface{}{ 358 searchattribute.VisibilityTaskKey: "str", 359 } 360 361 request := elastic.NewBulkIndexRequest(). 362 Index(testIndex). 363 Id(testID). 364 Version(version). 365 Doc(doc) 366 requests := []elastic.BulkableRequest{request} 367 368 mFailed := map[string]*elastic.BulkResponseItem{ 369 "index": { 370 Index: testIndex, 371 Id: testID, 372 Version: version, 373 Status: 400, 374 }, 375 } 376 response := &elastic.BulkResponse{ 377 Took: 3, 378 Errors: true, 379 Items: []map[string]*elastic.BulkResponseItem{mFailed}, 380 } 381 382 counterMetric := metrics.NewMockCounterIface(s.controller) 383 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorFailures.Name()).Return(counterMetric) 384 counterMetric.EXPECT().Record(int64(1), metrics.HttpStatusTag(400)) 385 s.esProcessor.bulkAfterAction(0, requests, response, &elastic.Error{Status: 400}) 386 } 387 388 func (s *processorSuite) TestBulkBeforeAction() { 389 version := int64(3) 390 testKey := "testKey" 391 request := elastic.NewBulkIndexRequest(). 392 Index(testIndex). 393 Id(testID). 394 Version(version). 395 Doc(map[string]interface{}{searchattribute.VisibilityTaskKey: testKey}) 396 requests := []elastic.BulkableRequest{request} 397 398 counterMetric := metrics.NewMockCounterIface(s.controller) 399 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorRequests.Name()).Return(counterMetric) 400 counterMetric.EXPECT().Record(int64(1)) 401 bulkSizeHistogram := metrics.NewMockHistogramIface(s.controller) 402 s.mockMetricHandler.EXPECT().Histogram( 403 metrics.ElasticsearchBulkProcessorBulkSize.Name(), 404 metrics.ElasticsearchBulkProcessorBulkSize.Unit(), 405 ).Return(bulkSizeHistogram) 406 bulkSizeHistogram.EXPECT().Record(int64(1)) 407 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc) 408 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitStartLatency.Name()).Return(metrics.NoopTimerMetricFunc) 409 mapVal := newAckFuture() 410 mapVal.recordAdd(s.mockMetricHandler) 411 s.esProcessor.mapToAckFuture.Put(testKey, mapVal) 412 s.True(mapVal.startedAt.IsZero()) 413 s.esProcessor.bulkBeforeAction(0, requests) 414 s.False(mapVal.startedAt.IsZero()) 415 } 416 417 func (s *processorSuite) TestAckChan() { 418 key := "test-key" 419 // no msg in map, nothing called 420 s.esProcessor.notifyResult(key, true) 421 422 request := &client.BulkableRequest{} 423 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc) 424 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorRequestLatency.Name()).Return(metrics.NoopTimerMetricFunc) 425 s.mockBulkProcessor.EXPECT().Add(request) 426 future := s.esProcessor.Add(request, key) 427 s.Equal(1, s.esProcessor.mapToAckFuture.Len()) 428 429 s.esProcessor.notifyResult(key, true) 430 result, err := future.Get(context.Background()) 431 s.NoError(err) 432 s.True(result) 433 s.Equal(0, s.esProcessor.mapToAckFuture.Len()) 434 } 435 436 func (s *processorSuite) TestNackChan() { 437 key := "test-key-nack" 438 // no msg in map, nothing called 439 s.esProcessor.notifyResult(key, false) 440 441 request := &client.BulkableRequest{} 442 s.mockBulkProcessor.EXPECT().Add(request) 443 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()).Return(metrics.NoopTimerMetricFunc) 444 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorRequestLatency.Name()).Return(metrics.NoopTimerMetricFunc) 445 future := s.esProcessor.Add(request, key) 446 s.Equal(1, s.esProcessor.mapToAckFuture.Len()) 447 448 s.esProcessor.notifyResult(key, false) 449 result, err := future.Get(context.Background()) 450 s.NoError(err) 451 s.False(result) 452 s.Equal(0, s.esProcessor.mapToAckFuture.Len()) 453 } 454 455 func (s *processorSuite) TestHashFn() { 456 s.Equal(uint32(0), s.esProcessor.hashFn(0)) 457 s.NotEqual(uint32(0), s.esProcessor.hashFn("test")) 458 } 459 460 func (s *processorSuite) TestExtractVisibilityTaskKey() { 461 request := elastic.NewBulkIndexRequest() 462 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorCorruptedData.Name()).Return(metrics.NoopCounterMetricFunc) 463 visibilityTaskKey := s.esProcessor.extractVisibilityTaskKey(request) 464 s.Equal("", visibilityTaskKey) 465 466 m := map[string]interface{}{ 467 searchattribute.VisibilityTaskKey: 1, 468 } 469 request.Doc(m) 470 s.Panics(func() { s.esProcessor.extractVisibilityTaskKey(request) }) 471 472 testKey := "test-key" 473 m[searchattribute.VisibilityTaskKey] = testKey 474 request.Doc(m) 475 s.Equal(testKey, s.esProcessor.extractVisibilityTaskKey(request)) 476 } 477 478 func (s *processorSuite) TestExtractVisibilityTaskKey_Delete() { 479 request := elastic.NewBulkDeleteRequest() 480 481 // ensure compatible with dependency 482 source, err := request.Source() 483 s.NoError(err) 484 s.Equal(1, len(source)) 485 var body map[string]map[string]interface{} 486 err = json.Unmarshal([]byte(source[0]), &body) 487 s.NoError(err) 488 _, ok := body["delete"] 489 s.True(ok) 490 491 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorCorruptedData.Name()).Return(metrics.NoopCounterMetricFunc) 492 key := s.esProcessor.extractVisibilityTaskKey(request) 493 s.Equal("", key) 494 495 id := "id" 496 request.Id(id) 497 key = s.esProcessor.extractVisibilityTaskKey(request) 498 s.Equal(id, key) 499 } 500 501 func (s *processorSuite) TestIsResponseSuccess() { 502 item := &elastic.BulkResponseItem{} 503 504 for status := 200; status < 300; status++ { 505 item.Status = status 506 s.True(isSuccess(item)) 507 } 508 509 item.Status = 409 510 s.True(isSuccess(item)) 511 item.Status = 404 512 s.True(isSuccess(item)) 513 item.Error = &elastic.ErrorDetails{Type: "index_not_found_exception"} 514 s.False(isSuccess(item)) 515 516 for _, status := range []int{100, 199, 300, 400, 500, 408, 429, 503, 507} { 517 item.Status = status 518 s.False(isSuccess(item)) 519 } 520 } 521 522 func (s *processorSuite) TestErrorReasonFromResponse() { 523 reason := "error reason" 524 resp := &elastic.BulkResponseItem{Status: 400} 525 s.Equal("", extractErrorReason(resp)) 526 resp.Error = &elastic.ErrorDetails{Reason: reason} 527 s.Equal(reason, extractErrorReason(resp)) 528 } 529 530 func (s *processorSuite) Test_End2End() { 531 docsCount := 1000 532 parallelFactor := 10 533 version := int64(2208) // random 534 535 request := &client.BulkableRequest{} 536 bulkIndexRequests := make([]elastic.BulkableRequest, docsCount) 537 bulkIndexResponse := &elastic.BulkResponse{ 538 Took: 3, 539 Errors: false, 540 Items: make([]map[string]*elastic.BulkResponseItem, docsCount), 541 } 542 futures := make([]future.Future[bool], docsCount) 543 544 // Add documents in parallel. 545 wg := sync.WaitGroup{} 546 wg.Add(parallelFactor) 547 s.mockBulkProcessor.EXPECT().Add(request).Times(docsCount) 548 s.mockMetricHandler.EXPECT(). 549 Timer(metrics.ElasticsearchBulkProcessorWaitAddLatency.Name()). 550 Return(metrics.NoopTimerMetricFunc).Times(docsCount) 551 for i := 0; i < parallelFactor; i++ { 552 go func(i int) { 553 for j := 0; j < docsCount/parallelFactor; j++ { 554 docIndex := i*docsCount/parallelFactor + j 555 testKey := fmt.Sprintf("test-key-%d-%d", i, j) 556 docId := fmt.Sprintf("docId-%d", docIndex) 557 futures[docIndex] = s.esProcessor.Add(request, testKey) 558 bulkIndexRequests[docIndex] = elastic.NewBulkIndexRequest(). 559 Index(testIndex). 560 Id(docId). 561 Version(version). 562 Doc(map[string]interface{}{searchattribute.VisibilityTaskKey: testKey}) 563 564 mSuccess := map[string]*elastic.BulkResponseItem{ 565 "index": { 566 Index: testIndex, 567 Id: docId, 568 Version: version, 569 Status: 200, 570 }, 571 } 572 bulkIndexResponse.Items[docIndex] = mSuccess 573 } 574 wg.Done() 575 }(i) 576 } 577 wg.Wait() 578 s.Equal(docsCount, s.esProcessor.mapToAckFuture.Len()) 579 580 // Emulate bulk commit. 581 582 counterMetric := metrics.NewMockCounterIface(s.controller) 583 s.mockMetricHandler.EXPECT().Counter(metrics.ElasticsearchBulkProcessorRequests.Name()).Return(counterMetric) 584 counterMetric.EXPECT().Record(int64(docsCount)) 585 queuedRequestsHistogram := metrics.NewMockHistogramIface(s.controller) 586 s.mockMetricHandler.EXPECT().Histogram( 587 metrics.ElasticsearchBulkProcessorQueuedRequests.Name(), 588 metrics.ElasticsearchBulkProcessorQueuedRequests.Unit(), 589 ).Return(queuedRequestsHistogram) 590 queuedRequestsHistogram.EXPECT().Record(int64(0)) 591 bulkSizeHistogram := metrics.NewMockHistogramIface(s.controller) 592 s.mockMetricHandler.EXPECT().Histogram( 593 metrics.ElasticsearchBulkProcessorBulkSize.Name(), 594 metrics.ElasticsearchBulkProcessorBulkSize.Unit(), 595 ).Return(bulkSizeHistogram) 596 bulkSizeHistogram.EXPECT().Record(int64(docsCount)) 597 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorWaitStartLatency.Name()).Return(metrics.NoopTimerMetricFunc).Times(docsCount) 598 s.esProcessor.bulkBeforeAction(0, bulkIndexRequests) 599 600 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorBulkResquestTookLatency.Name()).Return(metrics.NoopTimerMetricFunc) 601 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorRequestLatency.Name()).Return(metrics.NoopTimerMetricFunc).Times(docsCount) 602 s.mockMetricHandler.EXPECT().Timer(metrics.ElasticsearchBulkProcessorCommitLatency.Name()).Return(metrics.NoopTimerMetricFunc).Times(docsCount) 603 s.esProcessor.bulkAfterAction(0, bulkIndexRequests, bulkIndexResponse, nil) 604 605 for i := 0; i < docsCount; i++ { 606 result, err := futures[i].Get(context.Background()) 607 s.NoError(err) 608 s.True(result) 609 } 610 }