github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicreadercommon/batch.go (about)

     1  package topicreadercommon
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/empty"
     8  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicreader"
     9  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    10  )
    11  
    12  var (
    13  	errBadSessionWhileMessageBatchCreate       = xerrors.Wrap(errors.New("ydb: bad session while messages batch create"))        //nolint:lll
    14  	errBadMessageOffsetWhileMessageBatchCreate = xerrors.Wrap(errors.New("ydb: bad message offset while messages batch create")) //nolint:lll
    15  )
    16  
    17  // PublicBatch is ordered group of message from one partition
    18  type PublicBatch struct {
    19  	empty.DoNotCopy
    20  
    21  	Messages []*PublicMessage
    22  
    23  	commitRange CommitRange // от всех сообщений батча
    24  }
    25  
    26  func NewBatch(session *PartitionSession, messages []*PublicMessage) (*PublicBatch, error) {
    27  	for i := 0; i < len(messages); i++ {
    28  		msg := messages[i]
    29  
    30  		if msg.commitRange.PartitionSession == nil {
    31  			msg.commitRange.PartitionSession = session
    32  		}
    33  		if session != msg.commitRange.PartitionSession {
    34  			return nil, xerrors.WithStackTrace(errBadSessionWhileMessageBatchCreate)
    35  		}
    36  
    37  		if i == 0 {
    38  			continue
    39  		}
    40  
    41  		prev := messages[i-1]
    42  		if prev.commitRange.CommitOffsetEnd != msg.commitRange.CommitOffsetStart {
    43  			return nil, xerrors.WithStackTrace(errBadMessageOffsetWhileMessageBatchCreate)
    44  		}
    45  	}
    46  
    47  	commitRange := CommitRange{
    48  		PartitionSession: session,
    49  	}
    50  	if len(messages) > 0 {
    51  		commitRange.CommitOffsetStart = messages[0].commitRange.CommitOffsetStart
    52  		commitRange.CommitOffsetEnd = messages[len(messages)-1].commitRange.CommitOffsetEnd
    53  	}
    54  
    55  	return &PublicBatch{
    56  		Messages:    messages,
    57  		commitRange: commitRange,
    58  	}, nil
    59  }
    60  
    61  func NewBatchFromStream(
    62  	decoders DecoderMap,
    63  	session *PartitionSession,
    64  	sb rawtopicreader.Batch, //nolint:gocritic
    65  ) (*PublicBatch, error) {
    66  	messages := make([]*PublicMessage, len(sb.MessageData))
    67  	prevOffset := session.LastReceivedMessageOffset()
    68  	for i := range sb.MessageData {
    69  		sMess := &sb.MessageData[i]
    70  
    71  		dstMess := &PublicMessage{}
    72  		messages[i] = dstMess
    73  		dstMess.CreatedAt = sMess.CreatedAt
    74  		dstMess.MessageGroupID = sMess.MessageGroupID
    75  		dstMess.Offset = sMess.Offset.ToInt64()
    76  		dstMess.SeqNo = sMess.SeqNo
    77  		dstMess.WrittenAt = sb.WrittenAt
    78  		dstMess.ProducerID = sb.ProducerID
    79  		dstMess.WriteSessionMetadata = sb.WriteSessionMeta
    80  
    81  		dstMess.rawDataLen = len(sMess.Data)
    82  		dstMess.data = createReader(decoders, sb.Codec, sMess.Data)
    83  		dstMess.UncompressedSize = int(sMess.UncompressedSize)
    84  
    85  		dstMess.commitRange.PartitionSession = session
    86  		dstMess.commitRange.CommitOffsetStart = prevOffset + 1
    87  		dstMess.commitRange.CommitOffsetEnd = sMess.Offset + 1
    88  
    89  		if len(sMess.MetadataItems) > 0 {
    90  			dstMess.Metadata = make(map[string][]byte, len(sMess.MetadataItems))
    91  			for metadataIndex := range sMess.MetadataItems {
    92  				dstMess.Metadata[sMess.MetadataItems[metadataIndex].Key] = sMess.MetadataItems[metadataIndex].Value
    93  			}
    94  		}
    95  
    96  		prevOffset = sMess.Offset
    97  	}
    98  
    99  	session.SetLastReceivedMessageOffset(prevOffset)
   100  
   101  	return NewBatch(session, messages)
   102  }
   103  
   104  // Context is cancelled when code should stop to process messages batch
   105  // for example - lost connection to server or receive stop partition signal without graceful flag
   106  func (m *PublicBatch) Context() context.Context {
   107  	return m.commitRange.PartitionSession.Context()
   108  }
   109  
   110  // Topic is path of source topic of the messages in the batch
   111  func (m *PublicBatch) Topic() string {
   112  	return m.partitionSession().Topic
   113  }
   114  
   115  // PartitionID of messages in the batch
   116  func (m *PublicBatch) PartitionID() int64 {
   117  	return m.partitionSession().PartitionID
   118  }
   119  
   120  func (m *PublicBatch) partitionSession() *PartitionSession {
   121  	return m.commitRange.PartitionSession
   122  }
   123  
   124  func (m *PublicBatch) getCommitRange() PublicCommitRange {
   125  	return m.commitRange.getCommitRange()
   126  }
   127  
   128  func splitBytesByMessagesInBatches(batches []*PublicBatch, totalBytesCount int) error {
   129  	restBytes := totalBytesCount
   130  
   131  	cutBytes := func(want int) int {
   132  		switch {
   133  		case restBytes == 0:
   134  			return 0
   135  		case want >= restBytes:
   136  			res := restBytes
   137  			restBytes = 0
   138  
   139  			return res
   140  		default:
   141  			restBytes -= want
   142  
   143  			return want
   144  		}
   145  	}
   146  
   147  	messagesCount := 0
   148  	for batchIndex := range batches {
   149  		messagesCount += len(batches[batchIndex].Messages)
   150  		for messageIndex := range batches[batchIndex].Messages {
   151  			message := batches[batchIndex].Messages[messageIndex]
   152  			message.bufferBytesAccount = cutBytes(batches[batchIndex].Messages[messageIndex].rawDataLen)
   153  		}
   154  	}
   155  
   156  	if messagesCount == 0 {
   157  		if totalBytesCount == 0 {
   158  			return nil
   159  		}
   160  
   161  		return xerrors.NewWithIssues("ydb: can't split bytes to zero length messages count")
   162  	}
   163  
   164  	overheadPerMessage := restBytes / messagesCount
   165  	overheadRemind := restBytes % messagesCount
   166  
   167  	for batchIndex := range batches {
   168  		for messageIndex := range batches[batchIndex].Messages {
   169  			msg := batches[batchIndex].Messages[messageIndex]
   170  
   171  			msg.bufferBytesAccount += cutBytes(overheadPerMessage)
   172  			if overheadRemind > 0 {
   173  				msg.bufferBytesAccount += cutBytes(1)
   174  			}
   175  		}
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func BatchAppend(original, appended *PublicBatch) (*PublicBatch, error) {
   182  	var res *PublicBatch
   183  	if original == nil {
   184  		res = &PublicBatch{}
   185  	} else {
   186  		res = original
   187  	}
   188  
   189  	if res.commitRange.PartitionSession != appended.commitRange.PartitionSession {
   190  		return nil, xerrors.WithStackTrace(errors.New("ydb: bad partition session for merge"))
   191  	}
   192  
   193  	if res.commitRange.CommitOffsetEnd != appended.commitRange.CommitOffsetStart {
   194  		return nil, xerrors.WithStackTrace(errors.New("ydb: bad offset interval for merge"))
   195  	}
   196  
   197  	res.Messages = append(res.Messages, appended.Messages...)
   198  	res.commitRange.CommitOffsetEnd = appended.commitRange.CommitOffsetEnd
   199  
   200  	return res, nil
   201  }
   202  
   203  func BatchCutMessages(b *PublicBatch, count int) (head, rest *PublicBatch) {
   204  	switch {
   205  	case count == 0:
   206  		return nil, b
   207  	case count >= len(b.Messages):
   208  		return b, nil
   209  	default:
   210  		// slice[0:count:count] - limit slice capacity and prevent overwrite rest by append messages to head
   211  		// explicit 0 need for prevent typos, when type slice[count:count] instead of slice[:count:count]
   212  		head, _ = NewBatch(b.commitRange.PartitionSession, b.Messages[0:count:count])
   213  		rest, _ = NewBatch(b.commitRange.PartitionSession, b.Messages[count:])
   214  
   215  		return head, rest
   216  	}
   217  }
   218  
   219  func BatchIsEmpty(b *PublicBatch) bool {
   220  	return b == nil || len(b.Messages) == 0
   221  }
   222  
   223  func BatchGetPartitionSession(item *PublicBatch) *PartitionSession {
   224  	return item.partitionSession()
   225  }
   226  
   227  func BatchSetCommitRangeForTest(b *PublicBatch, commitRange CommitRange) *PublicBatch {
   228  	b.commitRange = commitRange
   229  
   230  	return b
   231  }