github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/kinesis_balanced.go (about)

     1  //go:build !wasm
     2  // +build !wasm
     3  
     4  package reader
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/message"
    12  	"github.com/Jeffail/benthos/v3/lib/message/batch"
    13  	"github.com/Jeffail/benthos/v3/lib/metrics"
    14  	"github.com/Jeffail/benthos/v3/lib/types"
    15  	sess "github.com/Jeffail/benthos/v3/lib/util/aws/session"
    16  	"github.com/patrobinson/gokini"
    17  )
    18  
    19  //------------------------------------------------------------------------------
    20  
    21  // KinesisBalancedConfig is configuration values for the input type.
    22  type KinesisBalancedConfig struct {
    23  	sess.Config           `json:",inline" yaml:",inline"`
    24  	Stream                string `json:"stream" yaml:"stream"`
    25  	DynamoDBTable         string `json:"dynamodb_table" yaml:"dynamodb_table"`
    26  	DynamoDBBillingMode   string `json:"dynamodb_billing_mode" yaml:"dynamodb_billing_mode"`
    27  	DynamoDBReadCapacity  int64  `json:"dynamodb_read_provision" yaml:"dynamodb_read_provision"`
    28  	DynamoDBWriteCapacity int64  `json:"dynamodb_write_provision" yaml:"dynamodb_write_provision"`
    29  	// TODO: V4 Remove this.
    30  	MaxBatchCount   int                `json:"max_batch_count" yaml:"max_batch_count"`
    31  	Batching        batch.PolicyConfig `json:"batching" yaml:"batching"`
    32  	StartFromOldest bool               `json:"start_from_oldest" yaml:"start_from_oldest"`
    33  }
    34  
    35  // NewKinesisBalancedConfig creates a new Config with default values.
    36  func NewKinesisBalancedConfig() KinesisBalancedConfig {
    37  	s := sess.NewConfig()
    38  	return KinesisBalancedConfig{
    39  		Config:                s,
    40  		Stream:                "",
    41  		DynamoDBTable:         "",
    42  		DynamoDBBillingMode:   "",
    43  		DynamoDBReadCapacity:  0,
    44  		DynamoDBWriteCapacity: 0,
    45  		MaxBatchCount:         1,
    46  		Batching:              batch.NewPolicyConfig(),
    47  		StartFromOldest:       true,
    48  	}
    49  }
    50  
    51  //------------------------------------------------------------------------------
    52  
    53  // KinesisBalanced is a benthos reader.Type implementation that reads messages
    54  // from an Amazon Kinesis stream.
    55  type KinesisBalanced struct {
    56  	conf KinesisBalancedConfig
    57  
    58  	lastSequences map[string]*string
    59  
    60  	log     log.Modular
    61  	stats   metrics.Type
    62  	kc      *gokini.KinesisConsumer
    63  	records chan *gokini.Records
    64  	shardID string
    65  }
    66  
    67  // NewKinesisBalanced creates a new Amazon Kinesis stream reader.Type.
    68  func NewKinesisBalanced(
    69  	conf KinesisBalancedConfig,
    70  	log log.Modular,
    71  	stats metrics.Type,
    72  ) (*KinesisBalanced, error) {
    73  	records := make(chan *gokini.Records)
    74  	consumer := &KinesisBalanced{
    75  		conf:    conf,
    76  		log:     log,
    77  		stats:   stats,
    78  		records: records,
    79  	}
    80  	sess, err := conf.GetSession()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	kc := &gokini.KinesisConsumer{
    85  		StreamName:                  conf.Stream,
    86  		ShardIteratorType:           "TRIM_HORIZON",
    87  		RecordConsumer:              consumer,
    88  		TableName:                   conf.DynamoDBTable,
    89  		EmptyRecordBackoffMs:        1000,
    90  		DisableAutomaticCheckpoints: true,
    91  		Session:                     sess,
    92  	}
    93  	if !consumer.conf.StartFromOldest {
    94  		kc.ShardIteratorType = "LATEST"
    95  	}
    96  	if consumer.conf.DynamoDBBillingMode != "" {
    97  		kc.DynamoBillingMode = &consumer.conf.DynamoDBBillingMode
    98  	}
    99  	if consumer.conf.DynamoDBReadCapacity != 0 && consumer.conf.DynamoDBWriteCapacity != 0 {
   100  		kc.DynamoReadCapacityUnits = &consumer.conf.DynamoDBReadCapacity
   101  		kc.DynamoWriteCapacityUnits = &consumer.conf.DynamoDBWriteCapacity
   102  	}
   103  
   104  	consumer.kc = kc
   105  	return consumer, nil
   106  }
   107  
   108  // Connect attempts to establish a connection to the target Kinesis stream.
   109  func (k *KinesisBalanced) Connect() error {
   110  	return k.ConnectWithContext(context.Background())
   111  }
   112  
   113  // ConnectWithContext attempts to establish a connection to the target Kinesis
   114  // stream.
   115  func (k *KinesisBalanced) ConnectWithContext(ctx context.Context) error {
   116  	err := k.kc.StartConsumer()
   117  
   118  	k.log.Infof("Receiving Amazon Kinesis messages from stream: %v\n", k.conf.Stream)
   119  	return err
   120  }
   121  
   122  func (k *KinesisBalanced) setMetadata(record *gokini.Records, p types.Part) {
   123  	met := p.Metadata()
   124  	met.Set("kinesis_shard", k.shardID)
   125  	met.Set("kinesis_partition_key", record.PartitionKey)
   126  	met.Set("kinesis_sequence_number", record.SequenceNumber)
   127  }
   128  
   129  // ReadWithContext attempts to read a new message from the target Kinesis
   130  // stream.
   131  func (k *KinesisBalanced) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) {
   132  	var record *gokini.Records
   133  	select {
   134  	case record = <-k.records:
   135  	case <-ctx.Done():
   136  		return nil, nil, types.ErrTimeout
   137  	}
   138  	if record == nil {
   139  		return nil, nil, types.ErrTimeout
   140  	}
   141  
   142  	part := message.NewPart(record.Data)
   143  	k.setMetadata(record, part)
   144  
   145  	msg := message.New(nil)
   146  	msg.Append(part)
   147  
   148  	return msg, func(rctx context.Context, res types.Response) error {
   149  		return k.kc.Checkpoint(record.ShardID, record.SequenceNumber)
   150  	}, nil
   151  }
   152  
   153  // Read attempts to read a new message from the target Kinesis stream.
   154  func (k *KinesisBalanced) Read() (types.Message, error) {
   155  	msg := message.New(nil)
   156  
   157  	record := <-k.records
   158  	if record == nil {
   159  		return nil, types.ErrTimeout
   160  	}
   161  	k.lastSequences[record.ShardID] = &record.SequenceNumber
   162  	{
   163  		part := message.NewPart(record.Data)
   164  		k.setMetadata(record, part)
   165  		msg.Append(part)
   166  	}
   167  
   168  batchLoop:
   169  	for i := 1; i < k.conf.MaxBatchCount; i++ {
   170  		select {
   171  		case record := <-k.records:
   172  			if record != nil {
   173  				k.lastSequences[record.ShardID] = &record.SequenceNumber
   174  				part := message.NewPart(record.Data)
   175  				k.setMetadata(record, part)
   176  				msg.Append(part)
   177  			} else {
   178  				break batchLoop
   179  			}
   180  		default:
   181  			// Drained the buffer
   182  			break batchLoop
   183  		}
   184  	}
   185  
   186  	return msg, nil
   187  }
   188  
   189  // Acknowledge confirms whether or not our unacknowledged messages have been
   190  // successfully propagated or not.
   191  func (k *KinesisBalanced) Acknowledge(err error) error {
   192  	if err == nil && k.lastSequences != nil {
   193  		for shard, sequence := range k.lastSequences {
   194  			err := k.kc.Checkpoint(shard, *sequence)
   195  			if err != nil {
   196  				return err
   197  			}
   198  			delete(k.lastSequences, shard)
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  // CloseAsync begins cleaning up resources used by this reader asynchronously.
   205  func (k *KinesisBalanced) CloseAsync() {
   206  	go k.kc.Shutdown()
   207  }
   208  
   209  // WaitForClose will block until either the reader is closed or a specified
   210  // timeout occurs.
   211  func (k *KinesisBalanced) WaitForClose(time.Duration) error {
   212  	return nil
   213  }
   214  
   215  // Init is required by the KinesisConsumer interface
   216  func (k *KinesisBalanced) Init(shardID string) error {
   217  	return nil
   218  }
   219  
   220  // ProcessRecords implements the KinesisConsumer interface
   221  func (k *KinesisBalanced) ProcessRecords(records []*gokini.Records, _ *gokini.KinesisConsumer) {
   222  	for _, record := range records {
   223  		k.records <- record
   224  	}
   225  }
   226  
   227  // Shutdown implements the KinesisConsumer interface
   228  func (k *KinesisBalanced) Shutdown() {
   229  	k.log.Infof("Stopping processing of Stream %s Shard %s", k.conf.Stream, k.shardID)
   230  	close(k.records)
   231  }
   232  
   233  //------------------------------------------------------------------------------