github.com/opensearch-project/opensearch-go/v2@v2.3.0/opensearchutil/bulk_indexer_integration_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  //
     3  // The OpenSearch Contributors require contributions made to
     4  // this file be licensed under the Apache-2.0 license or a
     5  // compatible open source license.
     6  //
     7  // Modifications Copyright OpenSearch Contributors. See
     8  // GitHub history for details.
     9  
    10  // Licensed to Elasticsearch B.V. under one or more contributor
    11  // license agreements. See the NOTICE file distributed with
    12  // this work for additional information regarding copyright
    13  // ownership. Elasticsearch B.V. licenses this file to you under
    14  // the Apache License, Version 2.0 (the "License"); you may
    15  // not use this file except in compliance with the License.
    16  // You may obtain a copy of the License at
    17  //
    18  //    http://www.apache.org/licenses/LICENSE-2.0
    19  //
    20  // Unless required by applicable law or agreed to in writing,
    21  // software distributed under the License is distributed on an
    22  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    23  // KIND, either express or implied.  See the License for the
    24  // specific language governing permissions and limitations
    25  // under the License.
    26  
    27  //go:build integration
    28  // +build integration
    29  
    30  package opensearchutil_test
    31  
    32  import (
    33  	"context"
    34  	"fmt"
    35  	"os"
    36  	"strconv"
    37  	"strings"
    38  	"testing"
    39  	"time"
    40  
    41  	"github.com/opensearch-project/opensearch-go/v2"
    42  	"github.com/opensearch-project/opensearch-go/v2/opensearchtransport"
    43  	"github.com/opensearch-project/opensearch-go/v2/opensearchutil"
    44  )
    45  
    46  func TestBulkIndexerIntegration(t *testing.T) {
    47  	testRecordCount := uint64(10000)
    48  
    49  	testCases := []struct {
    50  		name                       string
    51  		compressRequestBodyEnabled bool
    52  		tests                      []struct {
    53  			name       string
    54  			action     string
    55  			body       string
    56  			numItems   uint64
    57  			numIndexed uint64
    58  			numCreated uint64
    59  			numUpdated uint64
    60  			numFailed  uint64
    61  		}
    62  	}{
    63  		{
    64  			name:                       "With body compression",
    65  			compressRequestBodyEnabled: true,
    66  			tests: []struct {
    67  				name       string
    68  				action     string
    69  				body       string
    70  				numItems   uint64
    71  				numIndexed uint64
    72  				numCreated uint64
    73  				numUpdated uint64
    74  				numFailed  uint64
    75  			}{
    76  				{
    77  					name:       "Index",
    78  					action:     "index",
    79  					body:       `{"title":"bar"}`,
    80  					numItems:   testRecordCount,
    81  					numIndexed: testRecordCount,
    82  					numCreated: 0,
    83  					numUpdated: 0,
    84  					numFailed:  0,
    85  				},
    86  				{
    87  					name:       "Upsert",
    88  					action:     "update",
    89  					body:       `{"doc":{"title":"qwe"}, "doc_as_upsert": true}`,
    90  					numItems:   testRecordCount,
    91  					numIndexed: 0,
    92  					numCreated: 0,
    93  					numUpdated: testRecordCount,
    94  					numFailed:  0,
    95  				},
    96  				{
    97  					name:       "Create",
    98  					action:     "create",
    99  					body:       `{"title":"bar"}`,
   100  					numItems:   testRecordCount,
   101  					numIndexed: 0,
   102  					numCreated: 0,
   103  					numUpdated: 0,
   104  					numFailed:  testRecordCount,
   105  				},
   106  			},
   107  		},
   108  		{
   109  			name:                       "Without body compression",
   110  			compressRequestBodyEnabled: false,
   111  			tests: []struct {
   112  				name       string
   113  				action     string
   114  				body       string
   115  				numItems   uint64
   116  				numIndexed uint64
   117  				numCreated uint64
   118  				numUpdated uint64
   119  				numFailed  uint64
   120  			}{
   121  				{
   122  					name:       "Index",
   123  					action:     "index",
   124  					body:       `{"title":"bar"}`,
   125  					numItems:   testRecordCount,
   126  					numIndexed: testRecordCount,
   127  					numCreated: 0,
   128  					numUpdated: 0,
   129  					numFailed:  0,
   130  				},
   131  				{
   132  					name:       "Upsert",
   133  					action:     "update",
   134  					body:       `{"doc":{"title":"qwe"}, "doc_as_upsert": true}`,
   135  					numItems:   testRecordCount,
   136  					numIndexed: 0,
   137  					numCreated: 0,
   138  					numUpdated: testRecordCount,
   139  					numFailed:  0,
   140  				},
   141  				{
   142  					name:       "Create",
   143  					action:     "create",
   144  					body:       `{"title":"bar"}`,
   145  					numItems:   testRecordCount,
   146  					numIndexed: 0,
   147  					numCreated: 0,
   148  					numUpdated: 0,
   149  					numFailed:  testRecordCount,
   150  				},
   151  			},
   152  		},
   153  	}
   154  
   155  	for _, c := range testCases {
   156  		indexName := "test-bulk-integration"
   157  
   158  		client, _ := opensearch.NewClient(opensearch.Config{
   159  			CompressRequestBody: c.compressRequestBodyEnabled,
   160  			Logger:              &opensearchtransport.ColorLogger{Output: os.Stdout},
   161  		})
   162  
   163  		client.Indices.Delete([]string{indexName}, client.Indices.Delete.WithIgnoreUnavailable(true))
   164  		client.Indices.Create(
   165  			indexName,
   166  			client.Indices.Create.WithBody(strings.NewReader(`{"settings": {"number_of_shards": 1, "number_of_replicas": 0, "refresh_interval":"5s"}}`)),
   167  			client.Indices.Create.WithWaitForActiveShards("1"))
   168  
   169  		for _, tt := range c.tests {
   170  			t.Run(tt.name, func(t *testing.T) {
   171  				t.Run(c.name, func(t *testing.T) {
   172  					bi, _ := opensearchutil.NewBulkIndexer(opensearchutil.BulkIndexerConfig{
   173  						Index:      indexName,
   174  						Client:     client,
   175  						ErrorTrace: true,
   176  						Human:      true,
   177  						Pretty:     true,
   178  						// FlushBytes: 3e+6,
   179  					})
   180  
   181  					start := time.Now().UTC()
   182  
   183  					for i := 1; i <= int(tt.numItems); i++ {
   184  						err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{
   185  							Index:      indexName,
   186  							Action:     tt.action,
   187  							DocumentID: strconv.Itoa(i),
   188  							Body:       strings.NewReader(tt.body),
   189  						})
   190  						if err != nil {
   191  							t.Fatalf("Unexpected error: %s", err)
   192  						}
   193  					}
   194  
   195  					if err := bi.Close(context.Background()); err != nil {
   196  						t.Errorf("Unexpected error: %s", err)
   197  					}
   198  
   199  					stats := bi.Stats()
   200  
   201  					if stats.NumAdded != tt.numItems {
   202  						t.Errorf("Unexpected NumAdded: want=%d, got=%d", tt.numItems, stats.NumAdded)
   203  					}
   204  
   205  					if stats.NumIndexed != tt.numIndexed {
   206  						t.Errorf("Unexpected NumIndexed: want=%d, got=%d", tt.numItems, stats.NumIndexed)
   207  					}
   208  
   209  					if stats.NumUpdated != tt.numUpdated {
   210  						t.Errorf("Unexpected NumUpdated: want=%d, got=%d", tt.numUpdated, stats.NumUpdated)
   211  					}
   212  
   213  					if stats.NumCreated != tt.numCreated {
   214  						t.Errorf("Unexpected NumCreated: want=%d, got=%d", tt.numCreated, stats.NumCreated)
   215  					}
   216  
   217  					if stats.NumFailed != tt.numFailed {
   218  						t.Errorf("Unexpected NumFailed: want=0, got=%d", stats.NumFailed)
   219  					}
   220  
   221  					fmt.Printf("  Added %d documents to indexer. Succeeded: %d. Failed: %d. Requests: %d. Duration: %s (%.0f docs/sec)\n",
   222  						stats.NumAdded,
   223  						stats.NumFlushed,
   224  						stats.NumFailed,
   225  						stats.NumRequests,
   226  						time.Since(start).Truncate(time.Millisecond),
   227  						1000.0/float64(time.Since(start)/time.Millisecond)*float64(stats.NumFlushed))
   228  				})
   229  
   230  				t.Run("Multiple indices", func(t *testing.T) {
   231  					bi, _ := opensearchutil.NewBulkIndexer(opensearchutil.BulkIndexerConfig{
   232  						Index:  "test-index-a",
   233  						Client: client,
   234  					})
   235  
   236  					// Default index
   237  					for i := 1; i <= 10; i++ {
   238  						err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{
   239  							Action:     "index",
   240  							DocumentID: strconv.Itoa(i),
   241  							Body:       strings.NewReader(tt.body),
   242  						})
   243  						if err != nil {
   244  							t.Fatalf("Unexpected error: %s", err)
   245  						}
   246  					}
   247  
   248  					// Index 1
   249  					for i := 1; i <= 10; i++ {
   250  						err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{
   251  							Action: "index",
   252  							Index:  "test-index-b",
   253  							Body:   strings.NewReader(tt.body),
   254  						})
   255  						if err != nil {
   256  							t.Fatalf("Unexpected error: %s", err)
   257  						}
   258  					}
   259  
   260  					// Index 2
   261  					for i := 1; i <= 10; i++ {
   262  						err := bi.Add(context.Background(), opensearchutil.BulkIndexerItem{
   263  							Action: "index",
   264  							Index:  "test-index-c",
   265  							Body:   strings.NewReader(tt.body),
   266  						})
   267  						if err != nil {
   268  							t.Fatalf("Unexpected error: %s", err)
   269  						}
   270  					}
   271  
   272  					if err := bi.Close(context.Background()); err != nil {
   273  						t.Errorf("Unexpected error: %s", err)
   274  					}
   275  					stats := bi.Stats()
   276  
   277  					expectedIndexed := 10 + 10 + 10
   278  					if stats.NumIndexed != uint64(expectedIndexed) {
   279  						t.Errorf("Unexpected NumIndexed: want=%d, got=%d", expectedIndexed, stats.NumIndexed)
   280  					}
   281  
   282  					res, err := client.Indices.Exists([]string{"test-index-a", "test-index-b", "test-index-c"})
   283  					if err != nil {
   284  						t.Fatalf("Unexpected error: %s", err)
   285  					}
   286  					if res.StatusCode != 200 {
   287  						t.Errorf("Expected indices to exist, but got a [%s] response", res.Status())
   288  					}
   289  				})
   290  			})
   291  		}
   292  	}
   293  }