Skip to content

Commit cea5d97

Browse files
authored
Improve batch send performances (#366)
* Change lock type for unconfirmed messages. Use RWlock instead of Lock. Use the RLock when the map is read. Aggregate the messages and add unconfirmed the messages in a batch way. Aggregate the messages and removeunconfirmed messages in a bach way. Pass point array of *messageSequence * add random numer to super stream test to avoid duplication Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com> --------- Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
1 parent 9c1890a commit cea5d97

12 files changed

+170
-76
lines changed

examples/reliable/reliable_client.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111

1212
"github.com/rabbitmq/rabbitmq-stream-go-client/pkg/amqp"
1313
"github.com/rabbitmq/rabbitmq-stream-go-client/pkg/ha"
14-
"github.com/rabbitmq/rabbitmq-stream-go-client/pkg/logs"
1514
"github.com/rabbitmq/rabbitmq-stream-go-client/pkg/message"
1615
"github.com/rabbitmq/rabbitmq-stream-go-client/pkg/stream"
1716
)
@@ -33,18 +32,18 @@ var reSent int32
3332

3433
func main() {
3534
// Tune the parameters to test the reliability
36-
const messagesToSend = 5_000_000
37-
const numberOfProducers = 2
35+
const messagesToSend = 10_000_000
36+
const numberOfProducers = 4
3837
const concurrentProducers = 2
39-
const numberOfConsumers = 2
38+
const numberOfConsumers = 4
4039
const sendDelay = 1 * time.Millisecond
4140
const delayEachMessages = 200
4241
const maxProducersPerClient = 4
4342
const maxConsumersPerClient = 2
4443
//
4544

4645
reader := bufio.NewReader(os.Stdin)
47-
stream.SetLevelInfo(logs.DEBUG)
46+
//stream.SetLevelInfo(logs.DEBUG)
4847
fmt.Println("Reliable Producer/Consumer example")
4948
fmt.Println("Connecting to RabbitMQ streaming ...")
5049

perfTest/cmd/commands.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func init() {
5757
func setupCli(baseCmd *cobra.Command) {
5858
baseCmd.PersistentFlags().StringSliceVarP(&rabbitmqBrokerUrl, "uris", "", []string{stream.LocalhostUriConnection}, "Broker URLs")
5959
baseCmd.PersistentFlags().IntVarP(&publishers, "publishers", "", 1, "Number of Publishers")
60-
baseCmd.PersistentFlags().IntVarP(&batchSize, "batch-size", "", 100, "Batch Size, from 1 to 200")
60+
baseCmd.PersistentFlags().IntVarP(&batchSize, "batch-size", "", 200, "Batch Size, from 1 to 300")
6161
baseCmd.PersistentFlags().IntVarP(&subEntrySize, "sub-entry-size", "", 1, "SubEntry size, default 1. > 1 Enable the subEntryBatch")
6262
baseCmd.PersistentFlags().StringVarP(&compression, "compression", "", "", "Compression for sub batching, none,gzip,lz4,snappy,zstd")
6363
baseCmd.PersistentFlags().IntVarP(&consumers, "consumers", "", 1, "Number of Consumers")

perfTest/cmd/silent.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ func startSimulation() error {
150150
stream.SetLevelInfo(logs.DEBUG)
151151
}
152152

153-
if batchSize < 1 || batchSize > 200 {
154-
logError("Invalid batchSize, must be from 1 to 200, value:%d", batchSize)
153+
if batchSize < 1 || batchSize > 300 {
154+
logError("Invalid batchSize, must be from 1 to 300, value:%d", batchSize)
155155
os.Exit(1)
156156
}
157157

pkg/stream/aggregation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (compression Compression) Lz4() Compression {
5252
}
5353

5454
type subEntry struct {
55-
messages []messageSequence
55+
messages []*messageSequence
5656
publishingId int64 // need to store the publishingId useful in case of aggregation
5757

5858
unCompressedSize int

pkg/stream/aggregation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ var _ = Describe("Compression algorithms", func() {
1717
messagePayload[i] = 99
1818
}
1919

20-
message := messageSequence{
20+
message := &messageSequence{
2121
messageBytes: messagePayload,
2222
unCompressedSize: len(messagePayload),
2323
publishingId: 0,
2424
}
2525

2626
entries = &subEntries{
2727
items: []*subEntry{{
28-
messages: []messageSequence{message},
28+
messages: []*messageSequence{message},
2929
publishingId: 0,
3030
unCompressedSize: len(messagePayload) + 4,
3131
sizeInBytes: 0,

pkg/stream/coordinator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ func (coordinator *Coordinator) NewProducer(
6565
}
6666
var producer = &Producer{id: lastId,
6767
options: parameters,
68-
mutex: &sync.Mutex{},
68+
mutex: &sync.RWMutex{},
6969
mutexPending: &sync.Mutex{},
7070
unConfirmedMessages: map[int64]*ConfirmationStatus{},
7171
status: open,
7272
messageSequenceCh: make(chan messageSequence, size),
7373
pendingMessages: pendingMessagesSequence{
74-
messages: make([]messageSequence, 0),
74+
messages: make([]*messageSequence, 0),
7575
size: initBufferPublishSize,
7676
}}
7777
coordinator.producers[lastId] = producer

pkg/stream/producer.go

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (cs *ConfirmationStatus) GetErrorCode() uint16 {
5151
}
5252

5353
type pendingMessagesSequence struct {
54-
messages []messageSequence
54+
messages []*messageSequence
5555
size int
5656
}
5757

@@ -60,6 +60,7 @@ type messageSequence struct {
6060
unCompressedSize int
6161
publishingId int64
6262
filterValue string
63+
refMessage *message.StreamMessage
6364
}
6465

6566
type Producer struct {
@@ -68,7 +69,7 @@ type Producer struct {
6869
onClose onInternalClose
6970
unConfirmedMessages map[int64]*ConfirmationStatus
7071
sequence int64
71-
mutex *sync.Mutex
72+
mutex *sync.RWMutex
7273
mutexPending *sync.Mutex
7374
publishConfirm chan []*ConfirmationStatus
7475
closeHandler chan Event
@@ -170,11 +171,27 @@ func NewProducerOptions() *ProducerOptions {
170171
}
171172

172173
func (producer *Producer) GetUnConfirmed() map[int64]*ConfirmationStatus {
173-
producer.mutex.Lock()
174-
defer producer.mutex.Unlock()
174+
producer.mutex.RLock()
175+
defer producer.mutex.RUnlock()
175176
return producer.unConfirmedMessages
176177
}
177178

179+
func (producer *Producer) addUnConfirmedSequences(message []*messageSequence, producerID uint8) {
180+
producer.mutex.Lock()
181+
defer producer.mutex.Unlock()
182+
183+
for _, msg := range message {
184+
producer.unConfirmedMessages[msg.publishingId] =
185+
&ConfirmationStatus{
186+
inserted: time.Now(),
187+
message: *msg.refMessage,
188+
producerID: producerID,
189+
publishingId: msg.publishingId,
190+
confirmed: false,
191+
}
192+
}
193+
194+
}
178195
func (producer *Producer) addUnConfirmed(sequence int64, message message.StreamMessage, producerID uint8) {
179196
producer.mutex.Lock()
180197
defer producer.mutex.Unlock()
@@ -191,6 +208,18 @@ func (po *ProducerOptions) isSubEntriesBatching() bool {
191208
return po.SubEntrySize > 1
192209
}
193210

211+
func (producer *Producer) removeFromConfirmationStatus(status []*ConfirmationStatus) {
212+
producer.mutex.Lock()
213+
defer producer.mutex.Unlock()
214+
215+
for _, msg := range status {
216+
delete(producer.unConfirmedMessages, msg.publishingId)
217+
for _, linked := range msg.linkedTo {
218+
delete(producer.unConfirmedMessages, linked.publishingId)
219+
}
220+
}
221+
}
222+
194223
func (producer *Producer) removeUnConfirmed(sequence int64) {
195224
producer.mutex.Lock()
196225
defer producer.mutex.Unlock()
@@ -210,13 +239,13 @@ func (producer *Producer) lenPendingMessages() int {
210239
}
211240

212241
func (producer *Producer) getUnConfirmed(sequence int64) *ConfirmationStatus {
213-
producer.mutex.Lock()
214-
defer producer.mutex.Unlock()
242+
producer.mutex.RLock()
243+
defer producer.mutex.RUnlock()
215244
return producer.unConfirmedMessages[sequence]
216245
}
217246

218247
func (producer *Producer) NotifyPublishConfirmation() ChannelPublishConfirm {
219-
ch := make(chan []*ConfirmationStatus)
248+
ch := make(chan []*ConfirmationStatus, 1)
220249
producer.publishConfirm = ch
221250
return ch
222251
}
@@ -263,19 +292,26 @@ func (producer *Producer) startUnconfirmedMessagesTimeOutTask() {
263292
go func() {
264293
for producer.getStatus() == open {
265294
time.Sleep(2 * time.Second)
266-
producer.mutex.Lock()
295+
toRemove := make([]*ConfirmationStatus, 0)
296+
// check the unconfirmed messages and remove the one that are expired
297+
// use the RLock to avoid blocking the producer
298+
producer.mutex.RLock()
267299
for _, msg := range producer.unConfirmedMessages {
268300
if time.Since(msg.inserted) > producer.options.ConfirmationTimeOut {
269301
msg.err = ConfirmationTimoutError
270302
msg.errorCode = timeoutError
271303
msg.confirmed = false
272-
if producer.publishConfirm != nil {
273-
producer.publishConfirm <- []*ConfirmationStatus{msg}
274-
}
275-
delete(producer.unConfirmedMessages, msg.publishingId)
304+
toRemove = append(toRemove, msg)
305+
}
306+
}
307+
producer.mutex.RUnlock()
308+
309+
if len(toRemove) > 0 {
310+
producer.removeFromConfirmationStatus(toRemove)
311+
if producer.publishConfirm != nil {
312+
producer.publishConfirm <- toRemove
276313
}
277314
}
278-
producer.mutex.Unlock()
279315
}
280316
time.Sleep(5 * time.Second)
281317
producer.flushUnConfirmedMessages(timeoutError, ConfirmationTimoutError)
@@ -312,7 +348,7 @@ func (producer *Producer) startPublishTask() {
312348
}
313349

314350
producer.pendingMessages.size += msg.unCompressedSize
315-
producer.pendingMessages.messages = append(producer.pendingMessages.messages, msg)
351+
producer.pendingMessages.messages = append(producer.pendingMessages.messages, &msg)
316352
if len(producer.pendingMessages.messages) >= (producer.options.BatchSize) {
317353
producer.sendBufferedMessages()
318354
}
@@ -384,7 +420,7 @@ func (producer *Producer) assignPublishingID(message message.StreamMessage) int6
384420
// BatchSend is the primitive method to send messages to the stream, the method Send prepares the messages and
385421
// calls BatchSend internally.
386422
func (producer *Producer) BatchSend(batchMessages []message.StreamMessage) error {
387-
var messagesSequence = make([]messageSequence, len(batchMessages))
423+
var messagesSequence = make([]*messageSequence, len(batchMessages))
388424
totalBufferToSend := 0
389425
for i, batchMessage := range batchMessages {
390426
messageBytes, err := batchMessage.MarshalBinary()
@@ -398,16 +434,17 @@ func (producer *Producer) BatchSend(batchMessages []message.StreamMessage) error
398434

399435
sequence := producer.assignPublishingID(batchMessage)
400436
totalBufferToSend += len(messageBytes)
401-
messagesSequence[i] = messageSequence{
437+
messagesSequence[i] = &messageSequence{
402438
messageBytes: messageBytes,
403439
unCompressedSize: len(messageBytes),
404440
publishingId: sequence,
405441
filterValue: filterValue,
442+
refMessage: &batchMessage,
406443
}
407-
408-
producer.addUnConfirmed(sequence, batchMessage, producer.id)
409444
}
410445

446+
producer.addUnConfirmedSequences(messagesSequence, producer.GetID())
447+
411448
if totalBufferToSend+initBufferPublishSize > producer.options.client.tuneState.requestedMaxFrameSize {
412449
for _, msg := range messagesSequence {
413450

@@ -432,11 +469,11 @@ func (producer *Producer) BatchSend(batchMessages []message.StreamMessage) error
432469
func (producer *Producer) GetID() uint8 {
433470
return producer.id
434471
}
435-
func (producer *Producer) internalBatchSend(messagesSequence []messageSequence) error {
472+
func (producer *Producer) internalBatchSend(messagesSequence []*messageSequence) error {
436473
return producer.internalBatchSendProdId(messagesSequence, producer.GetID())
437474
}
438475

439-
func (producer *Producer) simpleAggregation(messagesSequence []messageSequence, b *bufio.Writer) {
476+
func (producer *Producer) simpleAggregation(messagesSequence []*messageSequence, b *bufio.Writer) {
440477
for _, msg := range messagesSequence {
441478
r := msg.messageBytes
442479
writeBLong(b, msg.publishingId) // publishingId
@@ -459,13 +496,15 @@ func (producer *Producer) subEntryAggregation(aggregation subEntries, b *bufio.W
459496
}
460497
}
461498

462-
func (producer *Producer) aggregateEntities(msgs []messageSequence, size int, compression Compression) (subEntries, error) {
499+
func (producer *Producer) aggregateEntities(msgs []*messageSequence, size int, compression Compression) (subEntries, error) {
463500
subEntries := subEntries{}
464501

465502
var entry *subEntry
466503
for _, msg := range msgs {
467504
if len(subEntries.items) == 0 || len(entry.messages) >= size {
468-
entry = &subEntry{}
505+
entry = &subEntry{
506+
messages: make([]*messageSequence, 0),
507+
}
469508
entry.publishingId = -1
470509
subEntries.items = append(subEntries.items, entry)
471510
}
@@ -506,7 +545,7 @@ func (producer *Producer) aggregateEntities(msgs []messageSequence, size int, co
506545
/// the producer id is always the producer.GetID(). This function is needed only for testing
507546
// some condition, like simulate publish error, see
508547

509-
func (producer *Producer) internalBatchSendProdId(messagesSequence []messageSequence, producerID uint8) error {
548+
func (producer *Producer) internalBatchSendProdId(messagesSequence []*messageSequence, producerID uint8) error {
510549
producer.options.client.socket.mutex.Lock()
511550
defer producer.options.client.socket.mutex.Unlock()
512551
if producer.getStatus() == closed {
@@ -656,7 +695,7 @@ func (producer *Producer) GetName() string {
656695
return producer.options.Name
657696
}
658697

659-
func (producer *Producer) sendWithFilter(messagesSequence []messageSequence, producerID uint8) error {
698+
func (producer *Producer) sendWithFilter(messagesSequence []*messageSequence, producerID uint8) error {
660699
frameHeaderLength := initBufferPublishSize
661700
var msgLen int
662701
for _, msg := range messagesSequence {

0 commit comments

Comments
 (0)