Files
tasks/common/archiver/history_iterator.go
Michael SnowdenandGitHub d5c62e926f Make all Go source file names snake case (#4558)
<!-- Describe what has changed in this PR -->
**What changed?**
I renamed all of our Go source code files so that they're snake case
instead of camel case.

<!-- Tell your future self why have you made these changes -->
**Why?**
This was an annoying inconsistency we carried over from much older code
in the repo.

<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
**How did you test it?**
The renaming process was part of an automated script
(https://gist.github.com/MichaelSnowden/47576fffe27aa1cae6b26eaccec7ab55),
so it should be consistent. Also, I don't see us doing anything in our
codebase that involves parsing our own source files by path:

<img width="687" alt="image"
src="https://github.com/temporalio/temporal/assets/5942963/c6b873b0-d841-40d1-8b0f-2fe9d8079a53">

*PS: here's a link to the GPT convo I had to generate this renaming
script:
https://chat.openai.com/share/7f2d011a-a8da-4fa4-9c6d-e09102fdd49f*

<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->
**Potential risks**
Mainly merge conflicts, but I used `git mv` so that these are renames
instead of deletes/adds, so that shouldn't be a big issue.

<!-- Is this PR a hotfix candidate or require that a notification be
sent to the broader community? (Yes/No) -->
**Is hotfix candidate?**
No.
2023-07-03 14:23:56 -04:00

274 lines
8.3 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination history_iterator_mock.go
package archiver
import (
"bytes"
"context"
"encoding/json"
"errors"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
archiverspb "go.temporal.io/server/api/archiver/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/persistence"
)
const (
historyPageSize = 250
)
type (
// HistoryIterator is used to get history batches
HistoryIterator interface {
Next(context.Context) (*archiverspb.HistoryBlob, error)
HasNext() bool
GetState() ([]byte, error)
}
historyIteratorState struct {
NextEventID int64
FinishedIteration bool
}
historyIterator struct {
historyIteratorState
request *ArchiveHistoryRequest
executionManager persistence.ExecutionManager
sizeEstimator SizeEstimator
historyPageSize int
targetHistoryBlobSize int
}
)
var (
errIteratorDepleted = errors.New("iterator is depleted")
)
// NewHistoryIterator returns a new HistoryIterator
func NewHistoryIterator(
request *ArchiveHistoryRequest,
executionManager persistence.ExecutionManager,
targetHistoryBlobSize int,
) HistoryIterator {
return newHistoryIterator(request, executionManager, targetHistoryBlobSize)
}
// NewHistoryIteratorFromState returns a new HistoryIterator with specified state
func NewHistoryIteratorFromState(
request *ArchiveHistoryRequest,
executionManager persistence.ExecutionManager,
targetHistoryBlobSize int,
initialState []byte,
) (HistoryIterator, error) {
it := newHistoryIterator(request, executionManager, targetHistoryBlobSize)
if initialState == nil {
return it, nil
}
if err := it.reset(initialState); err != nil {
return nil, err
}
return it, nil
}
func newHistoryIterator(
request *ArchiveHistoryRequest,
executionManager persistence.ExecutionManager,
targetHistoryBlobSize int,
) *historyIterator {
return &historyIterator{
historyIteratorState: historyIteratorState{
NextEventID: common.FirstEventID,
FinishedIteration: false,
},
request: request,
executionManager: executionManager,
historyPageSize: historyPageSize,
targetHistoryBlobSize: targetHistoryBlobSize,
sizeEstimator: NewJSONSizeEstimator(),
}
}
func (i *historyIterator) Next(
ctx context.Context,
) (*archiverspb.HistoryBlob, error) {
if !i.HasNext() {
return nil, errIteratorDepleted
}
historyBatches, newIterState, err := i.readHistoryBatches(ctx, i.NextEventID)
if err != nil {
return nil, err
}
i.historyIteratorState = newIterState
firstEvent := historyBatches[0].Events[0]
lastBatch := historyBatches[len(historyBatches)-1]
lastEvent := lastBatch.Events[len(lastBatch.Events)-1]
eventCount := int64(0)
for _, batch := range historyBatches {
eventCount += int64(len(batch.Events))
}
header := &archiverspb.HistoryBlobHeader{
Namespace: i.request.Namespace,
NamespaceId: i.request.NamespaceID,
WorkflowId: i.request.WorkflowID,
RunId: i.request.RunID,
IsLast: i.FinishedIteration,
FirstFailoverVersion: firstEvent.Version,
LastFailoverVersion: lastEvent.Version,
FirstEventId: firstEvent.EventId,
LastEventId: lastEvent.EventId,
EventCount: eventCount,
}
return &archiverspb.HistoryBlob{
Header: header,
Body: historyBatches,
}, nil
}
// HasNext returns true if there are more items to iterate over.
func (i *historyIterator) HasNext() bool {
return !i.FinishedIteration
}
// GetState returns the encoded iterator state
func (i *historyIterator) GetState() ([]byte, error) {
return json.Marshal(i.historyIteratorState)
}
func (i *historyIterator) readHistoryBatches(
ctx context.Context,
firstEventID int64,
) ([]*historypb.History, historyIteratorState, error) {
size := 0
targetSize := i.targetHistoryBlobSize
var historyBatches []*historypb.History
newIterState := historyIteratorState{}
for size < targetSize {
currHistoryBatches, err := i.readHistory(ctx, firstEventID)
if _, isNotFound := err.(*serviceerror.NotFound); isNotFound && firstEventID != common.FirstEventID {
newIterState.FinishedIteration = true
return historyBatches, newIterState, nil
}
if err != nil {
return nil, newIterState, err
}
for idx, batch := range currHistoryBatches {
historyBatchSize, err := i.sizeEstimator.EstimateSize(batch)
if err != nil {
return nil, newIterState, err
}
size += historyBatchSize
historyBatches = append(historyBatches, batch)
firstEventID = batch.Events[len(batch.Events)-1].EventId + 1
// In case targetSize is satisfied before reaching the end of current set of batches, return immediately.
// Otherwise, we need to look ahead to see if there's more history batches.
if size >= targetSize && idx != len(currHistoryBatches)-1 {
newIterState.FinishedIteration = false
newIterState.NextEventID = firstEventID
return historyBatches, newIterState, nil
}
}
}
// If you are here, it means the target size is met after adding the last batch of read history.
// We need to check if there's more history batches.
_, err := i.readHistory(ctx, firstEventID)
if _, isNotFound := err.(*serviceerror.NotFound); isNotFound && firstEventID != common.FirstEventID {
newIterState.FinishedIteration = true
return historyBatches, newIterState, nil
}
if err != nil {
return nil, newIterState, err
}
newIterState.FinishedIteration = false
newIterState.NextEventID = firstEventID
return historyBatches, newIterState, nil
}
func (i *historyIterator) readHistory(ctx context.Context, firstEventID int64) ([]*historypb.History, error) {
req := &persistence.ReadHistoryBranchRequest{
BranchToken: i.request.BranchToken,
MinEventID: firstEventID,
MaxEventID: common.EndEventID,
PageSize: i.historyPageSize,
ShardID: i.request.ShardID,
}
historyBatches, _, _, err := persistence.ReadFullPageEventsByBatch(ctx, i.executionManager, req)
return historyBatches, err
}
// reset resets iterator to a certain state given its encoded representation
// if it returns an error, the operation will have no effect on the iterator
func (i *historyIterator) reset(stateToken []byte) error {
var iteratorState historyIteratorState
if err := json.Unmarshal(stateToken, &iteratorState); err != nil {
return err
}
i.historyIteratorState = iteratorState
return nil
}
type (
// SizeEstimator is used to estimate the size of any object
SizeEstimator interface {
EstimateSize(v interface{}) (int, error)
}
jsonSizeEstimator struct {
marshaler jsonpb.Marshaler
}
)
func (e *jsonSizeEstimator) EstimateSize(v interface{}) (int, error) {
// jsonpb must be used for proto structs.
if protoMessage, ok := v.(proto.Message); ok {
var buf bytes.Buffer
err := e.marshaler.Marshal(&buf, protoMessage)
return buf.Len(), err
}
data, err := json.Marshal(v)
if err != nil {
return 0, err
}
return len(data), nil
}
// NewJSONSizeEstimator returns a new SizeEstimator which uses json encoding to estimate size
func NewJSONSizeEstimator() SizeEstimator {
return &jsonSizeEstimator{}
}