go.temporal.io/server@v1.23.0/common/archiver/history_iterator.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  //go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination history_iterator_mock.go
    26  
    27  package archiver
    28  
    29  import (
    30  	"context"
    31  	"encoding/json"
    32  	"errors"
    33  
    34  	historypb "go.temporal.io/api/history/v1"
    35  	"go.temporal.io/api/serviceerror"
    36  	"google.golang.org/protobuf/encoding/protojson"
    37  	"google.golang.org/protobuf/proto"
    38  
    39  	archiverspb "go.temporal.io/server/api/archiver/v1"
    40  	"go.temporal.io/server/common"
    41  	"go.temporal.io/server/common/persistence"
    42  )
    43  
    44  const (
    45  	historyPageSize = 250
    46  )
    47  
    48  type (
    49  	// HistoryIterator is used to get history batches
    50  	HistoryIterator interface {
    51  		Next(context.Context) (*archiverspb.HistoryBlob, error)
    52  		HasNext() bool
    53  		GetState() ([]byte, error)
    54  	}
    55  
    56  	historyIteratorState struct {
    57  		NextEventID       int64
    58  		FinishedIteration bool
    59  	}
    60  
    61  	historyIterator struct {
    62  		historyIteratorState
    63  
    64  		request               *ArchiveHistoryRequest
    65  		executionManager      persistence.ExecutionManager
    66  		sizeEstimator         SizeEstimator
    67  		historyPageSize       int
    68  		targetHistoryBlobSize int
    69  	}
    70  )
    71  
    72  var (
    73  	errIteratorDepleted = errors.New("iterator is depleted")
    74  )
    75  
    76  // NewHistoryIterator returns a new HistoryIterator
    77  func NewHistoryIterator(
    78  	request *ArchiveHistoryRequest,
    79  	executionManager persistence.ExecutionManager,
    80  	targetHistoryBlobSize int,
    81  ) HistoryIterator {
    82  	return newHistoryIterator(request, executionManager, targetHistoryBlobSize)
    83  }
    84  
    85  // NewHistoryIteratorFromState returns a new HistoryIterator with specified state
    86  func NewHistoryIteratorFromState(
    87  	request *ArchiveHistoryRequest,
    88  	executionManager persistence.ExecutionManager,
    89  	targetHistoryBlobSize int,
    90  	initialState []byte,
    91  ) (HistoryIterator, error) {
    92  	it := newHistoryIterator(request, executionManager, targetHistoryBlobSize)
    93  	if initialState == nil {
    94  		return it, nil
    95  	}
    96  	if err := it.reset(initialState); err != nil {
    97  		return nil, err
    98  	}
    99  	return it, nil
   100  }
   101  
   102  func newHistoryIterator(
   103  	request *ArchiveHistoryRequest,
   104  	executionManager persistence.ExecutionManager,
   105  	targetHistoryBlobSize int,
   106  ) *historyIterator {
   107  	return &historyIterator{
   108  		historyIteratorState: historyIteratorState{
   109  			NextEventID:       common.FirstEventID,
   110  			FinishedIteration: false,
   111  		},
   112  		request:               request,
   113  		executionManager:      executionManager,
   114  		historyPageSize:       historyPageSize,
   115  		targetHistoryBlobSize: targetHistoryBlobSize,
   116  		sizeEstimator:         NewJSONSizeEstimator(),
   117  	}
   118  }
   119  
   120  func (i *historyIterator) Next(
   121  	ctx context.Context,
   122  ) (*archiverspb.HistoryBlob, error) {
   123  	if !i.HasNext() {
   124  		return nil, errIteratorDepleted
   125  	}
   126  
   127  	historyBatches, newIterState, err := i.readHistoryBatches(ctx, i.NextEventID)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	i.historyIteratorState = newIterState
   133  	firstEvent := historyBatches[0].Events[0]
   134  	lastBatch := historyBatches[len(historyBatches)-1]
   135  	lastEvent := lastBatch.Events[len(lastBatch.Events)-1]
   136  	eventCount := int64(0)
   137  	for _, batch := range historyBatches {
   138  		eventCount += int64(len(batch.Events))
   139  	}
   140  	header := &archiverspb.HistoryBlobHeader{
   141  		Namespace:            i.request.Namespace,
   142  		NamespaceId:          i.request.NamespaceID,
   143  		WorkflowId:           i.request.WorkflowID,
   144  		RunId:                i.request.RunID,
   145  		IsLast:               i.FinishedIteration,
   146  		FirstFailoverVersion: firstEvent.Version,
   147  		LastFailoverVersion:  lastEvent.Version,
   148  		FirstEventId:         firstEvent.EventId,
   149  		LastEventId:          lastEvent.EventId,
   150  		EventCount:           eventCount,
   151  	}
   152  
   153  	return &archiverspb.HistoryBlob{
   154  		Header: header,
   155  		Body:   historyBatches,
   156  	}, nil
   157  }
   158  
   159  // HasNext returns true if there are more items to iterate over.
   160  func (i *historyIterator) HasNext() bool {
   161  	return !i.FinishedIteration
   162  }
   163  
   164  // GetState returns the encoded iterator state
   165  func (i *historyIterator) GetState() ([]byte, error) {
   166  	return json.Marshal(i.historyIteratorState)
   167  }
   168  
   169  func (i *historyIterator) readHistoryBatches(
   170  	ctx context.Context,
   171  	firstEventID int64,
   172  ) ([]*historypb.History, historyIteratorState, error) {
   173  	size := 0
   174  	targetSize := i.targetHistoryBlobSize
   175  	var historyBatches []*historypb.History
   176  	newIterState := historyIteratorState{}
   177  	for size < targetSize {
   178  		currHistoryBatches, err := i.readHistory(ctx, firstEventID)
   179  		if _, isNotFound := err.(*serviceerror.NotFound); isNotFound && firstEventID != common.FirstEventID {
   180  			newIterState.FinishedIteration = true
   181  			return historyBatches, newIterState, nil
   182  		}
   183  		if err != nil {
   184  			return nil, newIterState, err
   185  		}
   186  		for idx, batch := range currHistoryBatches {
   187  			historyBatchSize, err := i.sizeEstimator.EstimateSize(batch)
   188  			if err != nil {
   189  				return nil, newIterState, err
   190  			}
   191  			size += historyBatchSize
   192  			historyBatches = append(historyBatches, batch)
   193  			firstEventID = batch.Events[len(batch.Events)-1].EventId + 1
   194  
   195  			// In case targetSize is satisfied before reaching the end of current set of batches, return immediately.
   196  			// Otherwise, we need to look ahead to see if there's more history batches.
   197  			if size >= targetSize && idx != len(currHistoryBatches)-1 {
   198  				newIterState.FinishedIteration = false
   199  				newIterState.NextEventID = firstEventID
   200  				return historyBatches, newIterState, nil
   201  			}
   202  		}
   203  	}
   204  
   205  	// If you are here, it means the target size is met after adding the last batch of read history.
   206  	// We need to check if there's more history batches.
   207  	_, err := i.readHistory(ctx, firstEventID)
   208  	if _, isNotFound := err.(*serviceerror.NotFound); isNotFound && firstEventID != common.FirstEventID {
   209  		newIterState.FinishedIteration = true
   210  		return historyBatches, newIterState, nil
   211  	}
   212  	if err != nil {
   213  		return nil, newIterState, err
   214  	}
   215  	newIterState.FinishedIteration = false
   216  	newIterState.NextEventID = firstEventID
   217  	return historyBatches, newIterState, nil
   218  }
   219  
   220  func (i *historyIterator) readHistory(ctx context.Context, firstEventID int64) ([]*historypb.History, error) {
   221  	req := &persistence.ReadHistoryBranchRequest{
   222  		BranchToken: i.request.BranchToken,
   223  		MinEventID:  firstEventID,
   224  		MaxEventID:  common.EndEventID,
   225  		PageSize:    i.historyPageSize,
   226  		ShardID:     i.request.ShardID,
   227  	}
   228  	historyBatches, _, _, err := persistence.ReadFullPageEventsByBatch(ctx, i.executionManager, req)
   229  	return historyBatches, err
   230  }
   231  
   232  // reset resets iterator to a certain state given its encoded representation
   233  // if it returns an error, the operation will have no effect on the iterator
   234  func (i *historyIterator) reset(stateToken []byte) error {
   235  	var iteratorState historyIteratorState
   236  	if err := json.Unmarshal(stateToken, &iteratorState); err != nil {
   237  		return err
   238  	}
   239  	i.historyIteratorState = iteratorState
   240  	return nil
   241  }
   242  
   243  type (
   244  	// SizeEstimator is used to estimate the size of any object
   245  	SizeEstimator interface {
   246  		EstimateSize(v interface{}) (int, error)
   247  	}
   248  
   249  	jsonSizeEstimator struct {
   250  	}
   251  )
   252  
   253  func (e *jsonSizeEstimator) EstimateSize(v interface{}) (int, error) {
   254  	// protojson must be used for proto structs.
   255  	if protoMessage, ok := v.(proto.Message); ok {
   256  		bs, err := protojson.Marshal(protoMessage)
   257  		return len(bs), err
   258  	}
   259  
   260  	data, err := json.Marshal(v)
   261  	if err != nil {
   262  		return 0, err
   263  	}
   264  	return len(data), nil
   265  }
   266  
   267  // NewJSONSizeEstimator returns a new SizeEstimator which uses json encoding to estimate size
   268  func NewJSONSizeEstimator() SizeEstimator {
   269  	return &jsonSizeEstimator{}
   270  }