github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/topic/topicreaderinternal/batch.go (about) 1 package topicreaderinternal 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 offset := commitRange{ 48 partitionSession: session, 49 } 50 if len(messages) > 0 { 51 offset.commitOffsetStart = messages[0].commitRange.commitOffsetStart 52 offset.commitOffsetEnd = messages[len(messages)-1].commitRange.commitOffsetEnd 53 } 54 55 return &PublicBatch{ 56 Messages: messages, 57 commitRange: offset, 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 (m *PublicBatch) append(b *PublicBatch) (*PublicBatch, error) { 129 var res *PublicBatch 130 if m == nil { 131 res = &PublicBatch{} 132 } else { 133 res = m 134 } 135 136 if res.commitRange.partitionSession != b.commitRange.partitionSession { 137 return nil, xerrors.WithStackTrace(errors.New("ydb: bad partition session for merge")) 138 } 139 140 if res.commitRange.commitOffsetEnd != b.commitRange.commitOffsetStart { 141 return nil, xerrors.WithStackTrace(errors.New("ydb: bad offset interval for merge")) 142 } 143 144 res.Messages = append(res.Messages, b.Messages...) 145 res.commitRange.commitOffsetEnd = b.commitRange.commitOffsetEnd 146 147 return res, nil 148 } 149 150 func (m *PublicBatch) cutMessages(count int) (head, rest *PublicBatch) { 151 switch { 152 case count == 0: 153 return nil, m 154 case count >= len(m.Messages): 155 return m, nil 156 default: 157 // slice[0:count:count] - limit slice capacity and prevent overwrite rest by append messages to head 158 // explicit 0 need for prevent typos, when type slice[count:count] instead of slice[:count:count] 159 head, _ = newBatch(m.commitRange.partitionSession, m.Messages[0:count:count]) 160 rest, _ = newBatch(m.commitRange.partitionSession, m.Messages[count:]) 161 162 return head, rest 163 } 164 } 165 166 func (m *PublicBatch) isEmpty() bool { 167 return m == nil || len(m.Messages) == 0 168 } 169 170 func splitBytesByMessagesInBatches(batches []*PublicBatch, totalBytesCount int) error { 171 restBytes := totalBytesCount 172 173 cutBytes := func(want int) int { 174 switch { 175 case restBytes == 0: 176 return 0 177 case want >= restBytes: 178 res := restBytes 179 restBytes = 0 180 181 return res 182 default: 183 restBytes -= want 184 185 return want 186 } 187 } 188 189 messagesCount := 0 190 for batchIndex := range batches { 191 messagesCount += len(batches[batchIndex].Messages) 192 for messageIndex := range batches[batchIndex].Messages { 193 message := batches[batchIndex].Messages[messageIndex] 194 message.bufferBytesAccount = cutBytes(batches[batchIndex].Messages[messageIndex].rawDataLen) 195 } 196 } 197 198 if messagesCount == 0 { 199 if totalBytesCount == 0 { 200 return nil 201 } 202 203 return xerrors.NewWithIssues("ydb: can't split bytes to zero length messages count") 204 } 205 206 overheadPerMessage := restBytes / messagesCount 207 overheadRemind := restBytes % messagesCount 208 209 for batchIndex := range batches { 210 for messageIndex := range batches[batchIndex].Messages { 211 msg := batches[batchIndex].Messages[messageIndex] 212 213 msg.bufferBytesAccount += cutBytes(overheadPerMessage) 214 if overheadRemind > 0 { 215 msg.bufferBytesAccount += cutBytes(1) 216 } 217 } 218 } 219 220 return nil 221 }