-
Notifications
You must be signed in to change notification settings - Fork 4.6k
[Prism] Support injecting triggered bundle for a batch of elements. #36219
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
Merged
shunping
merged 8 commits into
apache:master
from
shunping:prism-trigger-bundle-in-batch
Sep 22, 2025
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0721f21
Support injecting trigger bundle for a batch of elements.
shunping af58908
Override streaming mode if there is an unbounded pcollection.
shunping 7b21ecd
Refactor some code.
shunping bc648d5
Enable prism on faild pipelines and rebench.
shunping 56c4e68
Merge branch 'master' into prism-trigger-bundle-in-batch
shunping 6093a72
Add tests for streaming and batch mode on data trigger for prism.
shunping 560537b
Revert "Enable prism on faild pipelines and rebench."
shunping a62e312
Fix the newly added tests.
shunping File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,6 +184,8 @@ type Config struct { | |
| MaxBundleSize int | ||
| // Whether to use real-time clock as processing time | ||
| EnableRTC bool | ||
| // Whether to process the data in a streaming mode | ||
| StreamingMode bool | ||
| } | ||
|
|
||
| // ElementManager handles elements, watermarks, and related errata to determine | ||
|
|
@@ -1296,6 +1298,43 @@ func (ss *stageState) AddPending(em *ElementManager, newPending []element) int { | |
| return ss.kind.addPending(ss, em, newPending) | ||
| } | ||
|
|
||
| func (ss *stageState) injectTriggeredBundlesIfReady(em *ElementManager, window typex.Window, key string) int { | ||
| // Check on triggers for this key. | ||
| // We use an empty linkID as the key into state for aggregations. | ||
| count := 0 | ||
| if ss.state == nil { | ||
| ss.state = make(map[LinkID]map[typex.Window]map[string]StateData) | ||
| } | ||
| lv, ok := ss.state[LinkID{}] | ||
| if !ok { | ||
| lv = make(map[typex.Window]map[string]StateData) | ||
| ss.state[LinkID{}] = lv | ||
| } | ||
| wv, ok := lv[window] | ||
| if !ok { | ||
| wv = make(map[string]StateData) | ||
| lv[window] = wv | ||
| } | ||
| state := wv[key] | ||
| endOfWindowReached := window.MaxTimestamp() < ss.input | ||
| ready := ss.strat.IsTriggerReady(triggerInput{ | ||
| newElementCount: 1, | ||
| endOfWindowReached: endOfWindowReached, | ||
| }, &state) | ||
|
|
||
| if ready { | ||
| state.Pane = computeNextTriggeredPane(state.Pane, endOfWindowReached) | ||
| } | ||
| // Store the state as triggers may have changed it. | ||
| ss.state[LinkID{}][window][key] = state | ||
|
|
||
| // If we're ready, it's time to fire! | ||
| if ready { | ||
| count += ss.buildTriggeredBundle(em, key, window) | ||
| } | ||
| return count | ||
| } | ||
|
|
||
| // addPending for aggregate stages behaves likes stateful stages, but don't need to handle timers or a separate window | ||
| // expiration condition. | ||
| func (*aggregateStageKind) addPending(ss *stageState, em *ElementManager, newPending []element) int { | ||
|
|
@@ -1315,6 +1354,13 @@ func (*aggregateStageKind) addPending(ss *stageState, em *ElementManager, newPen | |
| if ss.pendingByKeys == nil { | ||
| ss.pendingByKeys = map[string]*dataAndTimers{} | ||
| } | ||
|
|
||
| type windowKey struct { | ||
| window typex.Window | ||
| key string | ||
| } | ||
| pendingWindowKeys := set[windowKey]{} | ||
|
|
||
| count := 0 | ||
| for _, e := range newPending { | ||
| count++ | ||
|
|
@@ -1327,37 +1373,18 @@ func (*aggregateStageKind) addPending(ss *stageState, em *ElementManager, newPen | |
| ss.pendingByKeys[string(e.keyBytes)] = dnt | ||
| } | ||
| heap.Push(&dnt.elements, e) | ||
| // Check on triggers for this key. | ||
| // We use an empty linkID as the key into state for aggregations. | ||
| if ss.state == nil { | ||
| ss.state = make(map[LinkID]map[typex.Window]map[string]StateData) | ||
| } | ||
| lv, ok := ss.state[LinkID{}] | ||
| if !ok { | ||
| lv = make(map[typex.Window]map[string]StateData) | ||
| ss.state[LinkID{}] = lv | ||
| } | ||
| wv, ok := lv[e.window] | ||
| if !ok { | ||
| wv = make(map[string]StateData) | ||
| lv[e.window] = wv | ||
| } | ||
| state := wv[string(e.keyBytes)] | ||
| endOfWindowReached := e.window.MaxTimestamp() < ss.input | ||
| ready := ss.strat.IsTriggerReady(triggerInput{ | ||
| newElementCount: 1, | ||
| endOfWindowReached: endOfWindowReached, | ||
| }, &state) | ||
|
|
||
| if ready { | ||
| state.Pane = computeNextTriggeredPane(state.Pane, endOfWindowReached) | ||
| if em.config.StreamingMode { | ||
| // In streaming mode, we check trigger readiness on each element | ||
| count += ss.injectTriggeredBundlesIfReady(em, e.window, string(e.keyBytes)) | ||
| } else { | ||
| // In batch mode, we store key + window pairs here and check trigger readiness for each of them later. | ||
| pendingWindowKeys.insert(windowKey{window: e.window, key: string(e.keyBytes)}) | ||
| } | ||
| // Store the state as triggers may have changed it. | ||
| ss.state[LinkID{}][e.window][string(e.keyBytes)] = state | ||
|
|
||
| // If we're ready, it's time to fire! | ||
| if ready { | ||
| count += ss.buildTriggeredBundle(em, e.keyBytes, e.window) | ||
| } | ||
| if !em.config.StreamingMode { | ||
| for wk := range pendingWindowKeys { | ||
| count += ss.injectTriggeredBundlesIfReady(em, wk.window, wk.key) | ||
| } | ||
| } | ||
| return count | ||
|
|
@@ -1493,9 +1520,9 @@ func (ss *stageState) savePanes(bundID string, panesInBundle []bundlePane) { | |
| // buildTriggeredBundle must be called with the stage.mu lock held. | ||
| // When in discarding mode, returns 0. | ||
| // When in accumulating mode, returns the number of fired elements to maintain a correct pending count. | ||
| func (ss *stageState) buildTriggeredBundle(em *ElementManager, key []byte, win typex.Window) int { | ||
| func (ss *stageState) buildTriggeredBundle(em *ElementManager, key string, win typex.Window) int { | ||
| var toProcess []element | ||
| dnt := ss.pendingByKeys[string(key)] | ||
| dnt := ss.pendingByKeys[key] | ||
| var notYet []element | ||
|
|
||
| rb := RunBundle{StageID: ss.ID, BundleID: "agg-" + em.nextBundID(), Watermark: ss.input} | ||
|
|
@@ -1524,7 +1551,7 @@ func (ss *stageState) buildTriggeredBundle(em *ElementManager, key []byte, win t | |
| } | ||
| dnt.elements = append(dnt.elements, notYet...) | ||
| if dnt.elements.Len() == 0 { | ||
| delete(ss.pendingByKeys, string(key)) | ||
| delete(ss.pendingByKeys, key) | ||
| } else { | ||
| // Ensure the heap invariants are maintained. | ||
| heap.Init(&dnt.elements) | ||
|
|
@@ -1537,15 +1564,15 @@ func (ss *stageState) buildTriggeredBundle(em *ElementManager, key []byte, win t | |
| { | ||
| win: win, | ||
| key: string(key), | ||
| pane: ss.state[LinkID{}][win][string(key)].Pane, | ||
| pane: ss.state[LinkID{}][win][key].Pane, | ||
| }, | ||
| } | ||
|
|
||
| ss.makeInProgressBundle( | ||
| func() string { return rb.BundleID }, | ||
| toProcess, | ||
| ss.input, | ||
| singleSet(string(key)), | ||
|
Contributor
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 this over here probably made the string(key) (when key is []byte) moot anyway. |
||
| singleSet(key), | ||
| nil, | ||
| panesInBundle, | ||
| ) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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'll note that Go does some magic when passing a string cast []byte as a map key inline, which avoids allocating. That's why this method took in a []byte for the key, instead of eagerly converting it.