-
Notifications
You must be signed in to change notification settings - Fork 209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable batch delivery over WebSockets #1447
Changes from 8 commits
c7de181
d1598d8
1c5f7e6
011dda3
93894ff
34c4377
88c820e
7a26e8b
fee90bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright © 2023 Kaleido, Inc. | ||
// Copyright © 2024 Kaleido, Inc. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
@@ -65,25 +65,21 @@ type eventDispatcher struct { | |
elected bool | ||
eventPoller *eventPoller | ||
inflight map[fftypes.UUID]*core.Event | ||
eventDelivery chan *core.EventDelivery | ||
eventDelivery chan []*core.EventDelivery | ||
mux sync.Mutex | ||
namespace string | ||
readAhead int | ||
batch bool | ||
batchTimeout time.Duration | ||
subscription *subscription | ||
txHelper txcommon.Helper | ||
} | ||
|
||
func newEventDispatcher(ctx context.Context, enricher *eventEnricher, ei events.Plugin, di database.Plugin, dm data.Manager, bm broadcast.Manager, pm privatemessaging.Manager, connID string, sub *subscription, en *eventNotifier, txHelper txcommon.Helper) *eventDispatcher { | ||
ctx, cancelCtx := context.WithCancel(ctx) | ||
readAhead := config.GetUint(coreconfig.SubscriptionDefaultsReadAhead) | ||
readAhead := uint(0) | ||
if sub.definition.Options.ReadAhead != nil { | ||
readAhead = uint(*sub.definition.Options.ReadAhead) | ||
} | ||
if readAhead > maxReadAhead { | ||
readAhead = maxReadAhead | ||
} | ||
|
||
batchTimeout := defaultBatchTimeout | ||
if sub.definition.Options.BatchTimeout != nil && *sub.definition.Options.BatchTimeout != "" { | ||
|
@@ -108,13 +104,12 @@ func newEventDispatcher(ctx context.Context, enricher *eventEnricher, ei events. | |
subscription: sub, | ||
namespace: sub.definition.Namespace, | ||
inflight: make(map[fftypes.UUID]*core.Event), | ||
eventDelivery: make(chan *core.EventDelivery, readAhead+1), | ||
eventDelivery: make(chan []*core.EventDelivery, readAhead+1), | ||
readAhead: int(readAhead), | ||
acksNacks: make(chan ackNack), | ||
closed: make(chan struct{}), | ||
txHelper: txHelper, | ||
batch: batch, | ||
batchTimeout: batchTimeout, | ||
} | ||
|
||
pollerConf := &eventPollerConf{ | ||
|
@@ -138,6 +133,20 @@ func newEventDispatcher(ctx context.Context, enricher *eventEnricher, ei events. | |
firstEvent: sub.definition.Options.FirstEvent, | ||
} | ||
|
||
// Users can tune the batch related settings. | ||
// This is always true in batch:true cases, and optionally you can use the batchTimeout setting | ||
// to tweak how we optimize ourselves for readahead / latency detection without batching | ||
// (most likely users with this requirement would be best to just move to batch:true). | ||
if batchTimeout > 0 { | ||
pollerConf.eventBatchTimeout = batchTimeout | ||
if batchTimeout > pollerConf.eventPollTimeout { | ||
pollerConf.eventPollTimeout = batchTimeout | ||
} | ||
} | ||
if batch || pollerConf.eventBatchSize < int(readAhead) { | ||
pollerConf.eventBatchSize = ed.readAhead | ||
} | ||
|
||
ed.eventPoller = newEventPoller(ctx, di, en, pollerConf) | ||
return ed | ||
} | ||
|
@@ -165,11 +174,8 @@ func (ed *eventDispatcher) electAndStart() { | |
ed.elected = true | ||
ed.eventPoller.start() | ||
|
||
if ed.batch { | ||
go ed.deliverBatchedEvents() | ||
} else { | ||
go ed.deliverEvents() | ||
} | ||
go ed.deliverEvents() | ||
|
||
// Wait until the event poller closes | ||
<-ed.eventPoller.closed | ||
} | ||
|
@@ -326,7 +332,15 @@ func (ed *eventDispatcher) bufferedDelivery(events []core.LocallySequenced) (boo | |
ed.mux.Unlock() | ||
|
||
dispatched++ | ||
ed.eventDelivery <- event | ||
if !ed.batch { | ||
// dispatch individually | ||
ed.eventDelivery <- []*core.EventDelivery{event} | ||
} | ||
} | ||
|
||
if ed.batch && len(dispatchable) > 0 { | ||
// Dispatch the whole batch now marked in-flight | ||
ed.eventDelivery <- dispatchable | ||
} | ||
|
||
if inflightCount == 0 { | ||
|
@@ -384,88 +398,48 @@ func (ed *eventDispatcher) handleAckOffsetUpdate(ack ackNack) { | |
} | ||
} | ||
|
||
func (ed *eventDispatcher) deliverBatchedEvents() { | ||
withData := ed.subscription.definition.Options.WithData != nil && *ed.subscription.definition.Options.WithData | ||
|
||
var events []*core.CombinedEventDataDelivery | ||
var batchTimeoutContext context.Context | ||
var batchTimeoutCancel func() | ||
for { | ||
var timeoutContext context.Context | ||
var timedOut bool | ||
if batchTimeoutContext != nil { | ||
timeoutContext = batchTimeoutContext | ||
} else { | ||
timeoutContext = ed.ctx | ||
} | ||
select { | ||
case event, ok := <-ed.eventDelivery: | ||
if !ok { | ||
if batchTimeoutCancel != nil { | ||
batchTimeoutCancel() | ||
} | ||
return | ||
} | ||
|
||
if events == nil { | ||
events = []*core.CombinedEventDataDelivery{} | ||
batchTimeoutContext, batchTimeoutCancel = context.WithTimeout(ed.ctx, ed.batchTimeout) | ||
} | ||
|
||
log.L(ed.ctx).Debugf("Dispatching %s event in a batch: %.10d/%s [%s]: ref=%s/%s", ed.transport.Name(), event.Sequence, event.ID, event.Type, event.Namespace, event.Reference) | ||
|
||
var data []*core.Data | ||
var err error | ||
if withData && event.Message != nil { | ||
data, _, err = ed.data.GetMessageDataCached(ed.ctx, event.Message) | ||
} | ||
|
||
events = append(events, &core.CombinedEventDataDelivery{Event: event, Data: data}) | ||
|
||
if err != nil { | ||
ed.deliveryResponse(&core.EventDeliveryResponse{ID: event.ID, Rejected: true}) | ||
} | ||
|
||
case <-timeoutContext.Done(): | ||
timedOut = true | ||
case <-ed.ctx.Done(): | ||
if batchTimeoutCancel != nil { | ||
batchTimeoutCancel() | ||
} | ||
return | ||
} | ||
|
||
if len(events) == ed.readAhead || (timedOut && len(events) > 0) { | ||
_ = ed.transport.BatchDeliveryRequest(ed.ctx, ed.connID, ed.subscription.definition, events) | ||
// If err handle all the delivery responses for all the events?? | ||
events = nil | ||
} | ||
} | ||
} | ||
|
||
// TODO issue here, we can't just call DeliveryRequest with one thing. | ||
func (ed *eventDispatcher) deliverEvents() { | ||
peterbroadhurst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
withData := ed.subscription.definition.Options.WithData != nil && *ed.subscription.definition.Options.WithData | ||
for { | ||
select { | ||
case event, ok := <-ed.eventDelivery: | ||
case events, ok := <-ed.eventDelivery: | ||
if !ok { | ||
return | ||
} | ||
|
||
log.L(ed.ctx).Debugf("Dispatching %s event: %.10d/%s [%s]: ref=%s/%s", ed.transport.Name(), event.Sequence, event.ID, event.Type, event.Namespace, event.Reference) | ||
var data []*core.Data | ||
// As soon as we hit an error, we need to trigger into nack mode | ||
var err error | ||
if withData && event.Message != nil { | ||
data, _, err = ed.data.GetMessageDataCached(ed.ctx, event.Message) | ||
eventsWithData := make([]*core.CombinedEventDataDelivery, len(events)) | ||
for i := 0; i < len(events) && err == nil; i++ { | ||
e := &core.CombinedEventDataDelivery{ | ||
Event: events[i], | ||
} | ||
eventsWithData[i] = e | ||
log.L(ed.ctx).Debugf("Dispatching %s event: %.10d/%s [%s]: ref=%s/%s", ed.transport.Name(), e.Event.Sequence, e.Event.ID, e.Event.Type, e.Event.Namespace, e.Event.Reference) | ||
if withData && e.Event.Message != nil { | ||
e.Data, _, err = ed.data.GetMessageDataCached(ed.ctx, e.Event.Message) | ||
} | ||
// Individual events (in reality there is only ever i==0 for this case) | ||
if err == nil && !ed.batch { | ||
err = ed.transport.DeliveryRequest(ed.ctx, ed.connID, ed.subscription.definition, e.Event, e.Data) | ||
} | ||
if err != nil { | ||
ed.deliveryResponse(&core.EventDeliveryResponse{ID: e.Event.ID, Rejected: true}) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a nit, but feels like this could be moved outside of the loop (ie down to line 431). Took me a few reads to convince myself that all errors were handled across all branches. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But will leave it with you on what you think is ultimately clearer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, you actually point out a problem here, the reason it's in the loop is we still need to nack the other events. So I need to fix the logic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done - would appreciate you taking a 2nd look if you don't mind |
||
|
||
if err == nil { | ||
err = ed.transport.DeliveryRequest(ed.ctx, ed.connID, ed.subscription.definition, event, data) | ||
} | ||
if err != nil { | ||
ed.deliveryResponse(&core.EventDeliveryResponse{ID: event.ID, Rejected: true}) | ||
// In batch mode we do one dispatch of the whole set as one | ||
if err == nil && ed.batch { | ||
err = ed.transport.BatchDeliveryRequest(ed.ctx, ed.connID, ed.subscription.definition, eventsWithData) | ||
if err != nil { | ||
// nack everything on behalf of the failed delivery | ||
for _, e := range events { | ||
ed.deliveryResponse(&core.EventDeliveryResponse{ID: e.Event.ID, Rejected: true}) | ||
} | ||
} | ||
} | ||
|
||
case <-ed.ctx.Done(): | ||
return | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually the behavior you got before, because the code that had (a very, very long time ago) been started to implement batch-timeout had been disabled. Making a separate comment to highlight where.