github.com/erda-project/erda-infra@v1.0.9/providers/elasticsearch/writer.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package elasticsearch
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/erda-project/erda-infra/base/logs"
    27  	"github.com/olivere/elastic"
    28  	"github.com/recallsong/go-utils/reflectx"
    29  )
    30  
    31  // Document .
    32  type Document struct {
    33  	ID    string      `json:"id"`
    34  	Index string      `json:"index"`
    35  	Data  interface{} `json:"data"`
    36  }
    37  
    38  type batchWriter struct {
    39  	client        *elastic.Client
    40  	typ           string
    41  	timeout       string
    42  	retry         int
    43  	retryDuration time.Duration
    44  	log           logs.Logger
    45  }
    46  
    47  func (w *batchWriter) Write(data interface{}) error {
    48  	_, err := w.WriteN(data)
    49  	return err
    50  }
    51  
    52  func (w *batchWriter) WriteN(data ...interface{}) (int, error) {
    53  	if len(data) <= 0 {
    54  		return 0, nil
    55  	}
    56  
    57  	requests := make([]elastic.BulkableRequest, len(data), len(data))
    58  	for i, item := range data {
    59  		if doc, ok := item.(*Document); ok {
    60  			req := elastic.NewBulkIndexRequest().Index(doc.Index).Type(w.typ).Doc(doc.Data)
    61  			if len(doc.ID) > 0 {
    62  				req.Id(doc.ID)
    63  			}
    64  			requests[i] = req
    65  		} else {
    66  			return 0, fmt.Errorf("%s is not *elasticsearch.Document", reflect.TypeOf(item))
    67  		}
    68  	}
    69  	for i := 0; ; i++ {
    70  		res, err := w.client.Bulk().Add(requests...).Timeout(w.timeout).Do(context.Background())
    71  		if err != nil {
    72  			if i < w.retry {
    73  				w.log.Warnf("failed to write batch(%d) to elasticsearch and retry after %s: %s", len(requests), w.retryDuration.String(), err)
    74  				time.Sleep(w.retryDuration)
    75  				continue
    76  			}
    77  			w.log.Errorf("failed to write batch(%d) to elasticsearch: %s", len(requests), err)
    78  			break
    79  		}
    80  		if res.Errors {
    81  			for _, item := range res.Failed() {
    82  				if item == nil || item.Error == nil {
    83  					continue
    84  				}
    85  				byts, _ := json.Marshal(item.Error)
    86  				// TODO: notify error
    87  				w.log.Errorf("failed to index data, [%s][%s]: %s", item.Index, item.Type, reflectx.BytesToString(byts))
    88  			}
    89  		}
    90  		break
    91  	}
    92  	return len(requests), nil
    93  }
    94  
    95  func (w *batchWriter) Close() error { return nil }
    96  
    97  // NewWriter .
    98  func NewWriter(client *elastic.Client, timeout time.Duration, enc EncodeFunc) *Writer {
    99  	w := &Writer{
   100  		client: client,
   101  		enc:    enc,
   102  	}
   103  	if timeout > 0 {
   104  		w.timeout = fmt.Sprintf("%dms", timeout.Milliseconds())
   105  	}
   106  	return w
   107  }
   108  
   109  // Writer .
   110  type Writer struct {
   111  	client  *elastic.Client
   112  	enc     EncodeFunc
   113  	timeout string
   114  }
   115  
   116  // Close .
   117  func (w *Writer) Close() error { return nil }
   118  
   119  // Write .
   120  func (w *Writer) Write(data interface{}) error {
   121  	index, id, typ, body, err := w.enc(data)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	_, err = w.client.Index().
   126  		Index(index).Id(id).Type(typ).
   127  		BodyJson(body).Timeout(w.timeout).Do(context.Background())
   128  	return err
   129  }
   130  
   131  // WriteN .
   132  func (w *Writer) WriteN(list ...interface{}) (int, error) {
   133  	if len(list) <= 0 {
   134  		return 0, nil
   135  	}
   136  	bulk := w.client.Bulk()
   137  	for _, data := range list {
   138  		index, id, typ, body, err := w.enc(data)
   139  		if err != nil {
   140  			return 0, err
   141  		}
   142  		req := elastic.NewBulkIndexRequest().Index(index).Id(id).Type(typ).Doc(body)
   143  		bulk.Add(req)
   144  	}
   145  	res, err := bulk.Timeout(w.timeout).Do(context.Background())
   146  	if err != nil {
   147  		berr := &BatchWriteError{
   148  			List:   list,
   149  			Errors: make([]error, len(list), len(list)),
   150  		}
   151  		for i, n := 0, len(list); i < n; i++ {
   152  			berr.Errors[i] = err
   153  		}
   154  		return 0, berr
   155  	}
   156  	if res.Errors {
   157  		if len(res.Items) != len(list) {
   158  			return 0, fmt.Errorf("request items(%d), but response items(%d)", len(list), len(res.Items))
   159  		}
   160  		berr := &BatchWriteError{
   161  			List:   make([]interface{}, 0, len(list)),
   162  			Errors: make([]error, 0, len(list)),
   163  		}
   164  		for i, item := range res.Items {
   165  			for _, result := range item { // len(item) is 1, contains index request only
   166  				if !(result.Status >= 200 && result.Status <= 299) {
   167  					var sb strings.Builder
   168  					json.NewEncoder(&sb).Encode(result)
   169  					berr.List = append(berr.List, list[i])
   170  					berr.Errors = append(berr.Errors, errors.New(sb.String()))
   171  					break
   172  				}
   173  			}
   174  		}
   175  		return len(list) - len(berr.Errors), berr
   176  	}
   177  	return len(list), nil
   178  }
   179  
   180  // BatchWriteError .
   181  type BatchWriteError struct {
   182  	List   []interface{}
   183  	Errors []error
   184  }
   185  
   186  // Error .
   187  func (e *BatchWriteError) Error() string {
   188  	if len(e.Errors) <= 0 {
   189  		return ""
   190  	}
   191  	return fmt.Sprintf("bulk writes occur errors(%d): %v ...", len(e.Errors), e.Errors[0])
   192  }