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 }