-
Notifications
You must be signed in to change notification settings - Fork 344
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
Support nack backoff policy for SDK #660
Support nack backoff policy for SDK #660
Conversation
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
|
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
|
||
// If enabled, the default implementation of NackBackoffPolicy will be used to calculate the delay time of | ||
// nack backoff, Default: false. | ||
EnableDefaultNackBackoffPolicy bool |
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.
Why is this needed EnableDefaultNackBackoffPolicy
?. If the NackBackoffPolicy
is not supplied we can just the default?
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.
If there is no EnableDefaultNackBackoffPolicy
, it will invade the existing code logic. When the NackBackoffPolicy policy is empty, suppose we use the default NackBackoffPolicy, then when the user uses the Nack(Message) interface, the new implementation will be used.
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.
To me a cleaner API is to just have NackBackoffPolicy
and expose the basic/default policy. If the policy is not set than it uses the current behavior. This way there is only 1 configuration knob to worry about.
// current behavior
ConsumerOpts{}
// custom behavior
ConsumerOpts{
NackBackoffPolicy: pulsar.NewExpNackBackoffPolicy(),
}
pulsar/negative_backoff_policy.go
Outdated
type defaultNackBackoffPolicy struct{} | ||
|
||
func (nbp *defaultNackBackoffPolicy) Next(redeliveryCount uint32) int64 { | ||
minNackTimeMs := int64(1000 * 10) // 10sec |
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.
Let's use the time.Duration constants
10 * time.Seconds
10 * time.Minutes
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.
Because the <<
operation is required to cooperate with redeliveryCount, the unit conversion is still required, which will be converted to time.Duration in subsequent use.
return minNackTimeMs | ||
} | ||
|
||
return int64(math.Min(math.Abs(float64(minNackTimeMs<<redeliveryCount)), float64(maxNackTimeMs))) |
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.
Can you add some comments to what this logic is doing. For me it's difficult to look and just understand it.
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.
Sure, will add comment for this change
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.
Here we will first get the redeliveryCount object from the CommandMessage, and then start the << operation from minNackTimeMs to calculate the current length of time that nack needs to be executed, and then compare it with maxNackTimeMs, and take their maximum value as the nack duration. nack will increase from minNackTimeMs to maxNackTimeMs according to the above rule
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.
Nit let's make these constants
minNackTimeMs := int64(1000 * 30) // 30sec
maxNackTimeMs := 1000 * 60 * 10 // 10min
and please add some comments about how this int64(math.Min(math.Abs(float64(minNackTimeMs<<redeliveryCount)), float64(maxNackTimeMs)))
works
pulsar/negative_acks_tracker.go
Outdated
} | ||
} | ||
} | ||
|
||
func (t *negativeAcksTracker) Close() { | ||
// allow Close() to be invoked multiple times by consumer_partition to avoid panic | ||
t.doneOnce.Do(func() { | ||
t.tick.Stop() |
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.
How is the ticker getting cleanup now?
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.
In the current implementation situation, if we use the t.ticker in the struct, there will be a data race, so now we use the temporary variables of the ticker, and there is no good way to see how to close the temporarily created ticker.
pulsar/negative_acks_tracker.go
Outdated
func newNegativeAcksTracker(rc redeliveryConsumer, delay time.Duration, | ||
nackBackoffPolicy NackBackoffPolicy, logger log.Logger) *negativeAcksTracker { | ||
|
||
t := new(negativeAcksTracker) |
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 can just be var t *negativeAcksTracker
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.
They are the same effect
// the CommandMessage, so for the original default Nack() logic, we still keep the negativeAcksTracker created | ||
// when we open a gorutine to execute the logic of `t.track()`. But for the NackBackoffPolicy method, we need | ||
// to execute the logic of `t.track()` when AddMessage(). | ||
if nackBackoffPolicy != nil { |
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.
I'm a little confused on why we need an if statement. Shouldn't the default Implementation of the NackBackoffPolicy
be what the current behavior is? The benefit of the interface is to simply the code and delegate to the implementation.
bp := nackBackoffPolicy
if bp == nil {
bp = newDefaultBackoffPolicy(delay)
}
t = &negativeAcksTracker{
doneCh: make(chan interface{}),
negativeAcks: make(map[messageID]time.Time),
nackBackoff: bp,
rc: rc,
log: logger,
}
Thoughts?
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.
Yes, agree with your point of view. The problem here is because, for nackbackoff, we can't directly get the corresponding nackDelayTime, we need to get the redeliveryCount through the CommandMessage and then calculate the nackDelayTime, then we can determine the time.NewTicker based on the nackDelayTime. It is precisely because of such a relationship that the if statement is added
@@ -76,14 +95,48 @@ func (t *negativeAcksTracker) Add(msgID messageID) { | |||
t.negativeAcks[batchMsgID] = targetTime | |||
} | |||
|
|||
func (t *negativeAcksTracker) track() { | |||
func (t *negativeAcksTracker) AddMessage(msg Message) { |
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.
Why is there a new method here?
Also, it looks like state is changing here without a lock. If multiple go routines call this at once multiple tracking routines could be started right?
Can the tracking go routine just be started at creation time?
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.
Because we need to get redeliveryCount through the Message interface
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran <[email protected]>
Signed-off-by: xiaolongran [email protected]
Master Issue: #658
Motivation
Support nack backoff policy
Modifications
EnableDefaultNackBackoffPolicy
andNackBackoffPolicy
options in ConsumerOptions.Nack(Message)
interface