-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
(v2.11) [IMPROVED] stream sourcing: wq/interest stream with limits #5774
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -3098,6 +3098,17 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco | |
mset.clMu.Unlock() | ||
} | ||
|
||
if mset.inflightSubjects != nil { | ||
mset.clMu.Lock() | ||
n := mset.inflightSubjects[subject] | ||
if n > 1 { | ||
mset.inflightSubjects[subject]-- | ||
} else { | ||
delete(mset.inflightSubjects, subject) | ||
} | ||
mset.clMu.Unlock() | ||
} | ||
|
||
if err != nil { | ||
if err == errLastSeqMismatch { | ||
|
||
|
@@ -7966,7 +7977,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [ | |
name, stype, store := mset.cfg.Name, mset.cfg.Storage, mset.store | ||
s, js, jsa, st, r, tierName, outq, node := mset.srv, mset.js, mset.jsa, mset.cfg.Storage, mset.cfg.Replicas, mset.tier, mset.outq, mset.node | ||
maxMsgSize, lseq := int(mset.cfg.MaxMsgSize), mset.lseq | ||
interestPolicy, discard, maxMsgs, maxBytes := mset.cfg.Retention != LimitsPolicy, mset.cfg.Discard, mset.cfg.MaxMsgs, mset.cfg.MaxBytes | ||
interestPolicy, discard, maxMsgs, maxBytes, discardNewPerSubject, maxMsgsPer := mset.cfg.Retention != LimitsPolicy, mset.cfg.Discard, mset.cfg.MaxMsgs, mset.cfg.MaxBytes, mset.cfg.DiscardNewPer, mset.cfg.MaxMsgsPer | ||
isLeader, isSealed, compressOK := mset.isLeader(), mset.cfg.Sealed, mset.compressOK | ||
mset.mu.RUnlock() | ||
|
||
|
@@ -8143,27 +8154,36 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [ | |
mset.clseq = lseq + mset.clfs | ||
} | ||
|
||
// Check if we have an interest policy and discard new with max msgs or bytes. | ||
// Check if we have an interest or working queue retention and discard new with max msgs or bytes. | ||
// We need to deny here otherwise it could succeed on some peers and not others | ||
// depending on consumer ack state. So we deny here, if we allow that means we know | ||
// it would succeed on every peer. | ||
if interestPolicy && discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0) { | ||
if interestPolicy && discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0 || (maxMsgsPer > 0 && discardNewPerSubject)) { | ||
// Track inflight. | ||
if mset.inflight == nil { | ||
mset.inflight = make(map[uint64]uint64) | ||
} | ||
|
||
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. Remove nl? 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. Can not as that's how go fmt wants it |
||
if stype == FileStorage { | ||
mset.inflight[mset.clseq] = fileStoreMsgSize(subject, hdr, msg) | ||
} else { | ||
mset.inflight[mset.clseq] = memStoreMsgSize(subject, hdr, msg) | ||
} | ||
|
||
if mset.inflightSubjects == nil { | ||
mset.inflightSubjects = make(map[string]uint64) | ||
} | ||
|
||
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. Remove nl 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. Can not remove newline as go fmt puts it back 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. Really? It does not for me.. |
||
mset.inflightSubjects[subject]++ | ||
|
||
var state StreamState | ||
mset.store.FastState(&state) | ||
|
||
var err error | ||
if maxMsgs > 0 && state.Msgs+uint64(len(mset.inflight)) > uint64(maxMsgs) { | ||
err = ErrMaxMsgs | ||
if maxMsgs > 0 { | ||
if state.Msgs+uint64(len(mset.inflight)) > uint64(maxMsgs) { | ||
err = ErrMaxMsgs | ||
} | ||
} else if maxBytes > 0 { | ||
// TODO(dlc) - Could track this rollup independently. | ||
var bytesPending uint64 | ||
|
@@ -8173,9 +8193,24 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [ | |
if state.Bytes+bytesPending > uint64(maxBytes) { | ||
err = ErrMaxBytes | ||
} | ||
} else if maxMsgsPer > 0 && discardNewPerSubject { | ||
totals := mset.store.SubjectsTotals(subject) | ||
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. This could be very large.. We are getting this each time? 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. Only for wq/interest + discard new + discard new per subject streams. 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. This can be very slow based on the shape of the stream. This concerns me. As does tracking inflight. |
||
total := totals[subject] | ||
if (total + mset.inflightSubjects[subject]) > uint64(maxMsgsPer) { | ||
err = ErrMaxMsgsPerSubject | ||
} | ||
} | ||
|
||
if err != nil { | ||
delete(mset.inflight, mset.clseq) | ||
n := mset.inflightSubjects[subject] | ||
|
||
if n > 1 { | ||
mset.inflightSubjects[subject] = n - 1 | ||
} else { | ||
delete(mset.inflightSubjects, subject) | ||
} | ||
|
||
mset.clMu.Unlock() | ||
if canRespond { | ||
var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -336,20 +336,21 @@ type stream struct { | |
|
||
// TODO(dlc) - Hide everything below behind two pointers. | ||
// Clustered mode. | ||
sa *streamAssignment // What the meta controller uses to assign streams to peers. | ||
node RaftNode // Our RAFT node for the stream's group. | ||
catchup atomic.Bool // Used to signal we are in catchup mode. | ||
catchups map[string]uint64 // The number of messages that need to be caught per peer. | ||
syncSub *subscription // Internal subscription for sync messages (on "$JSC.SYNC"). | ||
infoSub *subscription // Internal subscription for stream info requests. | ||
clMu sync.Mutex // The mutex for clseq and clfs. | ||
clseq uint64 // The current last seq being proposed to the NRG layer. | ||
clfs uint64 // The count (offset) of the number of failed NRG sequences used to compute clseq. | ||
inflight map[uint64]uint64 // Inflight message sizes per clseq. | ||
lqsent time.Time // The time at which the last lost quorum advisory was sent. Used to rate limit. | ||
uch chan struct{} // The channel to signal updates to the monitor routine. | ||
compressOK bool // True if we can do message compression in RAFT and catchup logic | ||
inMonitor bool // True if the monitor routine has been started. | ||
sa *streamAssignment // What the meta controller uses to assign streams to peers. | ||
node RaftNode // Our RAFT node for the stream's group. | ||
catchup atomic.Bool // Used to signal we are in catchup mode. | ||
catchups map[string]uint64 // The number of messages that need to be caught per peer. | ||
syncSub *subscription // Internal subscription for sync messages (on "$JSC.SYNC"). | ||
infoSub *subscription // Internal subscription for stream info requests. | ||
clMu sync.Mutex // The mutex for clseq, clfs, inflight and inflightSubjects. | ||
clseq uint64 // The current last seq being proposed to the NRG layer. | ||
clfs uint64 // The count (offset) of the number of failed NRG sequences used to compute clseq. | ||
inflight map[uint64]uint64 // Inflight message sizes per clseq. | ||
inflightSubjects map[string]uint64 // Inflight number of messages per subject. | ||
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. This could get pretty big in terms of in memory state.. 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. I assumed that because it's only storing in flight messages, that the size would be 0 most of the time and go maybe up to a few thousands temporarily during message bursts, so still acceptable IMHO. I did hesitate adding the support for new per subject to the PR because I couldn't think of a good use case off the top of my head (doesn't mean one doesn't exist), but it needed to be handled and I thought it would be better to actually implement it rather than try to prevent it (which is not that easy/clean to do because what you want to forbid specifically is discard new+new per subject+sourcing (and you can add/remove sources at any time)). |
||
lqsent time.Time // The time at which the last lost quorum advisory was sent. Used to rate limit. | ||
uch chan struct{} // The channel to signal updates to the monitor routine. | ||
compressOK bool // True if we can do message compression in RAFT and catchup logic | ||
inMonitor bool // True if the monitor routine has been started. | ||
|
||
// Direct get subscription. | ||
directSub *subscription | ||
|
@@ -3471,7 +3472,7 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { | |
// Can happen temporarily all the time during normal operations when the sourcing stream | ||
// is working queue/interest with a limit and discard new. | ||
// TODO - Improve sourcing to WQ with limit and new to use flow control rather than re-creating the consumer. | ||
if errors.Is(err, ErrMaxMsgs) { | ||
if errors.Is(err, ErrMaxMsgs) || errors.Is(err, ErrMaxBytes) || errors.Is(err, ErrMaxMsgsPerSubject) { | ||
// Do not need to do a full retry that includes finding the last sequence in the stream | ||
// for that source. Just re-create starting with the seq we couldn't store instead. | ||
mset.mu.Lock() | ||
|
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.
We always tracking these?
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.
Or only for discard new per subject?
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.
Only for discard new + discard new per subject for wq or interest streams (see line#7932)