Skip to content

Commit da57e58

Browse files
authored
[Prism] Fix an issue on pane info being overwritten by different bundles. (#36188)
1 parent 5f0ac3c commit da57e58

3 files changed

Lines changed: 125 additions & 25 deletions

File tree

sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ func (em *ElementManager) PersistBundle(rb RunBundle, col2Coders map[string]PCol
851851
element{
852852
window: w,
853853
timestamp: et,
854-
pane: stage.kind.updatePane(stage, pn, w, keyBytes),
854+
pane: stage.kind.getPaneOrDefault(stage, pn, w, keyBytes, rb.BundleID),
855855
elmBytes: elmBytes,
856856
keyBytes: keyBytes,
857857
sequence: seq,
@@ -905,6 +905,7 @@ func (em *ElementManager) PersistBundle(rb RunBundle, col2Coders map[string]PCol
905905
delete(stage.inprogressKeys, k)
906906
}
907907
delete(stage.inprogressKeysByBundle, rb.BundleID)
908+
delete(stage.bundlePanes, rb.BundleID)
908909

909910
// Adjust holds as needed.
910911
for h, c := range newHolds {
@@ -1170,12 +1171,13 @@ type stageState struct {
11701171
sideInputs map[LinkID]map[typex.Window][][]byte // side input data for this stage, from {tid, inputID} -> window
11711172

11721173
// Fields for stateful stages which need to be per key.
1173-
pendingByKeys map[string]*dataAndTimers // pending input elements by Key, if stateful.
1174-
inprogressKeys set[string] // all keys that are assigned to bundles.
1175-
inprogressKeysByBundle map[string]set[string] // bundle to key assignments.
1176-
state map[LinkID]map[typex.Window]map[string]StateData // state data for this stage, from {tid, stateID} -> window -> userKey
1177-
stateTypeLen map[LinkID]func([]byte) int // map from state to a function that will produce the total length of a single value in bytes.
1178-
bundlesToInject []RunBundle // bundlesToInject are triggered bundles that will be injected by the watermark loop to avoid premature pipeline termination.
1174+
pendingByKeys map[string]*dataAndTimers // pending input elements by Key, if stateful.
1175+
inprogressKeys set[string] // all keys that are assigned to bundles.
1176+
inprogressKeysByBundle map[string]set[string] // bundle to key assignments.
1177+
state map[LinkID]map[typex.Window]map[string]StateData // state data for this stage, from {tid, stateID} -> window -> userKey
1178+
stateTypeLen map[LinkID]func([]byte) int // map from state to a function that will produce the total length of a single value in bytes.
1179+
bundlesToInject []RunBundle // bundlesToInject are triggered bundles that will be injected by the watermark loop to avoid premature pipeline termination.
1180+
bundlePanes map[string]map[typex.Window]map[string]typex.PaneInfo // PaneInfo snapshot for bundles, from BundleID -> window -> userKey
11791181

11801182
// Accounting for handling watermark holds for timers.
11811183
// We track the count of timers with the same hold, and clear it from
@@ -1187,6 +1189,13 @@ type stageState struct {
11871189
processingTimeTimers *timerHandler
11881190
}
11891191

1192+
// bundlePane holds pane info for a bundle.
1193+
type bundlePane struct {
1194+
win typex.Window
1195+
key string
1196+
pane typex.PaneInfo
1197+
}
1198+
11901199
// stageKind handles behavioral differences between ordinary, stateful, and aggregation stage kinds.
11911200
//
11921201
// kinds should be stateless, and stageState retains all state for the stage,
@@ -1195,10 +1204,11 @@ type stageKind interface {
11951204
// addPending handles adding new pending elements to the stage appropriate for the kind.
11961205
addPending(ss *stageState, em *ElementManager, newPending []element) int
11971206
// buildEventTimeBundle handles building bundles for the stage per it's kind.
1198-
buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, minTs mtime.Time, newKeys set[string], holdsInBundle map[mtime.Time]int, schedulable bool, pendingAdjustment int)
1207+
buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, minTs mtime.Time, newKeys set[string],
1208+
holdsInBundle map[mtime.Time]int, panesInBundle []bundlePane, schedulable bool, pendingAdjustment int)
11991209

1200-
// updatePane based on the stage state.
1201-
updatePane(ss *stageState, pane typex.PaneInfo, w typex.Window, keyBytes []byte) typex.PaneInfo
1210+
// getPaneOrDefault based on the stage state, element metadata, and bundle id.
1211+
getPaneOrDefault(ss *stageState, defaultPane typex.PaneInfo, w typex.Window, keyBytes []byte, bundID string) typex.PaneInfo
12021212
}
12031213

12041214
// ordinaryStageKind represents stages that have no special behavior associated with them.
@@ -1207,17 +1217,17 @@ type ordinaryStageKind struct{}
12071217

12081218
func (*ordinaryStageKind) String() string { return "OrdinaryStage" }
12091219

1210-
func (*ordinaryStageKind) updatePane(ss *stageState, pane typex.PaneInfo, w typex.Window, keyBytes []byte) typex.PaneInfo {
1211-
return pane
1220+
func (*ordinaryStageKind) getPaneOrDefault(ss *stageState, defaultPane typex.PaneInfo, w typex.Window, keyBytes []byte, bundID string) typex.PaneInfo {
1221+
return defaultPane
12121222
}
12131223

12141224
// statefulStageKind require keyed elements, and handles stages with stateful transforms, with state and timers.
12151225
type statefulStageKind struct{}
12161226

12171227
func (*statefulStageKind) String() string { return "StatefulStage" }
12181228

1219-
func (*statefulStageKind) updatePane(ss *stageState, pane typex.PaneInfo, w typex.Window, keyBytes []byte) typex.PaneInfo {
1220-
return pane
1229+
func (*statefulStageKind) getPaneOrDefault(ss *stageState, defaultPane typex.PaneInfo, w typex.Window, keyBytes []byte, bundID string) typex.PaneInfo {
1230+
return defaultPane
12211231
}
12221232

12231233
// aggregateStageKind handles stages that perform aggregations over their primary inputs.
@@ -1226,9 +1236,12 @@ type aggregateStageKind struct{}
12261236

12271237
func (*aggregateStageKind) String() string { return "AggregateStage" }
12281238

1229-
func (*aggregateStageKind) updatePane(ss *stageState, pane typex.PaneInfo, w typex.Window, keyBytes []byte) typex.PaneInfo {
1239+
func (*aggregateStageKind) getPaneOrDefault(ss *stageState, defaultPane typex.PaneInfo, w typex.Window, keyBytes []byte, bundID string) typex.PaneInfo {
12301240
ss.mu.Lock()
12311241
defer ss.mu.Unlock()
1242+
if pane, ok := ss.bundlePanes[bundID][w][string(keyBytes)]; ok {
1243+
return pane
1244+
}
12321245
return ss.state[LinkID{}][w][string(keyBytes)].Pane
12331246
}
12341247

@@ -1459,6 +1472,24 @@ func computeNextWatermarkPane(pane typex.PaneInfo) typex.PaneInfo {
14591472
return pane
14601473
}
14611474

1475+
func (ss *stageState) savePanes(bundID string, panesInBundle []bundlePane) {
1476+
if len(panesInBundle) == 0 {
1477+
return
1478+
}
1479+
if ss.bundlePanes == nil {
1480+
ss.bundlePanes = make(map[string]map[typex.Window]map[string]typex.PaneInfo)
1481+
}
1482+
if ss.bundlePanes[bundID] == nil {
1483+
ss.bundlePanes[bundID] = make(map[typex.Window]map[string]typex.PaneInfo)
1484+
}
1485+
for _, p := range panesInBundle {
1486+
if ss.bundlePanes[bundID][p.win] == nil {
1487+
ss.bundlePanes[bundID][p.win] = make(map[string]typex.PaneInfo)
1488+
}
1489+
ss.bundlePanes[bundID][p.win][p.key] = p.pane
1490+
}
1491+
}
1492+
14621493
// buildTriggeredBundle must be called with the stage.mu lock held.
14631494
// When in discarding mode, returns 0.
14641495
// When in accumulating mode, returns the number of fired elements to maintain a correct pending count.
@@ -1502,13 +1533,23 @@ func (ss *stageState) buildTriggeredBundle(em *ElementManager, key []byte, win t
15021533
if ss.inprogressKeys == nil {
15031534
ss.inprogressKeys = set[string]{}
15041535
}
1536+
panesInBundle := []bundlePane{
1537+
{
1538+
win: win,
1539+
key: string(key),
1540+
pane: ss.state[LinkID{}][win][string(key)].Pane,
1541+
},
1542+
}
1543+
15051544
ss.makeInProgressBundle(
15061545
func() string { return rb.BundleID },
15071546
toProcess,
15081547
ss.input,
15091548
singleSet(string(key)),
15101549
nil,
1550+
panesInBundle,
15111551
)
1552+
15121553
ss.bundlesToInject = append(ss.bundlesToInject, rb)
15131554
// Bundle is marked in progress here to prevent a race condition.
15141555
em.refreshCond.L.Lock()
@@ -1612,26 +1653,27 @@ func (ss *stageState) startEventTimeBundle(watermark mtime.Time, genBundID func(
16121653
}()
16131654
ss.mu.Lock()
16141655
defer ss.mu.Unlock()
1615-
toProcess, minTs, newKeys, holdsInBundle, stillSchedulable, accumulatingPendingAdjustment := ss.kind.buildEventTimeBundle(ss, watermark)
1656+
toProcess, minTs, newKeys, holdsInBundle, panesInBundle, stillSchedulable, accumulatingPendingAdjustment := ss.kind.buildEventTimeBundle(ss, watermark)
16161657

16171658
if len(toProcess) == 0 {
16181659
// If we have nothing, there's nothing to progress.
16191660
return "", false, stillSchedulable, accumulatingPendingAdjustment
16201661
}
16211662

1622-
bundID := ss.makeInProgressBundle(genBundID, toProcess, minTs, newKeys, holdsInBundle)
1663+
bundID := ss.makeInProgressBundle(genBundID, toProcess, minTs, newKeys, holdsInBundle, panesInBundle)
1664+
16231665
return bundID, true, stillSchedulable, accumulatingPendingAdjustment
16241666
}
16251667

16261668
// buildEventTimeBundle for ordinary stages processes all pending elements.
1627-
func (*ordinaryStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, minTs mtime.Time, newKeys set[string], holdsInBundle map[mtime.Time]int, schedulable bool, pendingAdjustment int) {
1669+
func (*ordinaryStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, minTs mtime.Time, newKeys set[string], holdsInBundle map[mtime.Time]int, _ []bundlePane, schedulable bool, pendingAdjustment int) {
16281670
toProcess = ss.pending
16291671
ss.pending = nil
1630-
return toProcess, mtime.MaxTimestamp, nil, nil, true, 0
1672+
return toProcess, mtime.MaxTimestamp, nil, nil, nil, true, 0
16311673
}
16321674

16331675
// buildEventTimeBundle for stateful stages, processes all elements that are before the input watermark time.
1634-
func (*statefulStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, _ mtime.Time, _ set[string], _ map[mtime.Time]int, schedulable bool, pendingAdjustment int) {
1676+
func (*statefulStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, _ mtime.Time, _ set[string], _ map[mtime.Time]int, _ []bundlePane, schedulable bool, pendingAdjustment int) {
16351677
minTs := mtime.MaxTimestamp
16361678
// TODO: Allow configurable limit of keys per bundle, and elements per key to improve parallelism.
16371679
// TODO: when we do, we need to ensure that the stage remains schedualable for bundle execution, for remaining pending elements and keys.
@@ -1715,11 +1757,11 @@ keysPerBundle:
17151757
// If we're out of data, and timers were not cleared then the watermark is accurate.
17161758
stillSchedulable := !(len(ss.pendingByKeys) == 0 && !timerCleared)
17171759

1718-
return toProcess, minTs, newKeys, holdsInBundle, stillSchedulable, 0
1760+
return toProcess, minTs, newKeys, holdsInBundle, nil, stillSchedulable, 0
17191761
}
17201762

17211763
// buildEventTimeBundle for aggregation stages, processes all elements that are within the watermark for completed windows.
1722-
func (*aggregateStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, _ mtime.Time, _ set[string], _ map[mtime.Time]int, schedulable bool, pendingAdjustment int) {
1764+
func (*aggregateStageKind) buildEventTimeBundle(ss *stageState, watermark mtime.Time) (toProcess elementHeap, _ mtime.Time, _ set[string], _ map[mtime.Time]int, panesInBundle []bundlePane, schedulable bool, pendingAdjustment int) {
17231765
minTs := mtime.MaxTimestamp
17241766
// TODO: Allow configurable limit of keys per bundle, and elements per key to improve parallelism.
17251767
// TODO: when we do, we need to ensure that the stage remains schedualable for bundle execution, for remaining pending elements and keys.
@@ -1814,6 +1856,13 @@ keysPerBundle:
18141856
}
18151857
ss.state[LinkID{}][elm.window][string(elm.keyBytes)] = state
18161858

1859+
// Save latest PaneInfo for this window + key pair. It will be used in PersistBundle.
1860+
panesInBundle = append(panesInBundle, bundlePane{
1861+
win: elm.window,
1862+
key: string(elm.keyBytes),
1863+
pane: ss.state[LinkID{}][elm.window][string(elm.keyBytes)].Pane,
1864+
})
1865+
18171866
// The pane is already correct for this key + window + firing.
18181867
if ss.strat.Accumulating && !state.Pane.IsLast {
18191868
// If this isn't the last pane, then we must add the element back to the pending store for subsequent firings.
@@ -1835,7 +1884,7 @@ keysPerBundle:
18351884
// If this is an aggregate, we need a watermark change in order to reschedule
18361885
stillSchedulable := false
18371886

1838-
return toProcess, minTs, newKeys, holdsInBundle, stillSchedulable, accumulatingPendingAdjustment
1887+
return toProcess, minTs, newKeys, holdsInBundle, panesInBundle, stillSchedulable, accumulatingPendingAdjustment
18391888
}
18401889

18411890
func (ss *stageState) startProcessingTimeBundle(em *ElementManager, emNow mtime.Time, genBundID func() string) (string, bool, bool) {
@@ -1910,14 +1959,14 @@ func (ss *stageState) startProcessingTimeBundle(em *ElementManager, emNow mtime.
19101959
// If we have nothing
19111960
return "", false, stillSchedulable
19121961
}
1913-
bundID := ss.makeInProgressBundle(genBundID, toProcess, minTs, newKeys, holdsInBundle)
1962+
bundID := ss.makeInProgressBundle(genBundID, toProcess, minTs, newKeys, holdsInBundle, nil)
19141963
return bundID, true, stillSchedulable
19151964
}
19161965

19171966
// makeInProgressBundle is common code to store a set of elements as a bundle in progress.
19181967
//
19191968
// Callers must hold the stage lock.
1920-
func (ss *stageState) makeInProgressBundle(genBundID func() string, toProcess []element, minTs mtime.Time, newKeys set[string], holdsInBundle map[mtime.Time]int) string {
1969+
func (ss *stageState) makeInProgressBundle(genBundID func() string, toProcess []element, minTs mtime.Time, newKeys set[string], holdsInBundle map[mtime.Time]int, panesInBundle []bundlePane) string {
19211970
// Catch the ordinary case for the minimum timestamp.
19221971
if toProcess[0].timestamp < minTs {
19231972
minTs = toProcess[0].timestamp
@@ -1941,6 +1990,9 @@ func (ss *stageState) makeInProgressBundle(genBundID func() string, toProcess []
19411990
ss.inprogressKeysByBundle[bundID] = newKeys
19421991
ss.inprogressKeys.merge(newKeys)
19431992
ss.inprogressHoldsByBundle[bundID] = holdsInBundle
1993+
1994+
// Save latest PaneInfo for PersistBundle
1995+
ss.savePanes(bundID, panesInBundle)
19441996
return bundID
19451997
}
19461998

@@ -2156,6 +2208,7 @@ func (ss *stageState) createOnWindowExpirationBundles(newOut mtime.Time, em *Ele
21562208
wm,
21572209
usedKeys,
21582210
map[mtime.Time]int{wm: 1},
2211+
nil,
21592212
)
21602213
ss.expiryWindowsByBundles[rb.BundleID] = win
21612214

sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@
6969
from apache_beam.testing.test_stream import TestStream
7070
from apache_beam.testing.util import assert_that
7171
from apache_beam.testing.util import equal_to
72+
from apache_beam.testing.util import has_at_least_one
7273
from apache_beam.tools import utils
7374
from apache_beam.transforms import environments
75+
from apache_beam.transforms import trigger
7476
from apache_beam.transforms import userstate
7577
from apache_beam.transforms import window
7678
from apache_beam.transforms.periodicsequence import PeriodicImpulse
@@ -1594,6 +1596,22 @@ def test_group_by_key_with_empty_pcoll_elements(self):
15941596
| beam.GroupByKey())
15951597
assert_that(res, equal_to([]))
15961598

1599+
def test_first_pane(self):
1600+
with self.create_pipeline() as p:
1601+
res = (
1602+
p | beam.Create([1, 2])
1603+
| beam.WithKeys(0)
1604+
| beam.WindowInto(
1605+
window.GlobalWindows(),
1606+
trigger=trigger.Repeatedly(trigger.AfterCount(1)),
1607+
accumulation_mode=trigger.AccumulationMode.ACCUMULATING,
1608+
allowed_lateness=0,
1609+
)
1610+
| beam.GroupByKey()
1611+
| beam.Values())
1612+
has_at_least_one(res, lambda e, t, w, p: p.is_first)
1613+
has_at_least_one(res, lambda e, t, w, p: p.index == 0)
1614+
15971615

15981616
# These tests are kept in a separate group so that they are
15991617
# not ran in the FnApiRunnerTestWithBundleRepeat which repeats

sdks/python/apache_beam/testing/util.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from apache_beam.transforms import window
3333
from apache_beam.transforms.core import Create
3434
from apache_beam.transforms.core import DoFn
35+
from apache_beam.transforms.core import Filter
3536
from apache_beam.transforms.core import Map
3637
from apache_beam.transforms.core import ParDo
3738
from apache_beam.transforms.core import WindowInto
@@ -45,6 +46,7 @@
4546
'assert_that',
4647
'equal_to',
4748
'equal_to_per_window',
49+
'has_at_least_one',
4850
'is_empty',
4951
'is_not_empty',
5052
'matches_all',
@@ -377,6 +379,33 @@ def AssertThat(pcoll, *args, **kwargs):
377379
return assert_that(pcoll, *args, **kwargs)
378380

379381

382+
def has_at_least_one(input, criterion, label="has_at_least_one"):
383+
pipeline = input.pipeline
384+
# similar to assert_that, we choose a label if it already exists.
385+
if label in pipeline.applied_labels:
386+
label_idx = 2
387+
while f"{label}_{label_idx}" in pipeline.applied_labels:
388+
label_idx += 1
389+
label = f"{label}_{label_idx}"
390+
391+
def _apply_criterion(
392+
e=DoFn.ElementParam,
393+
t=DoFn.TimestampParam,
394+
w=DoFn.WindowParam,
395+
p=DoFn.PaneInfoParam):
396+
if criterion(e, t, w, p):
397+
return e, t, w, p
398+
399+
def _not_empty(actual):
400+
actual = list(actual)
401+
if not actual:
402+
raise BeamAssertException('Failed assert: nothing matches the criterion')
403+
404+
result = input | label >> Map(_apply_criterion) | label + "_filter" >> Filter(
405+
lambda e: e is not None)
406+
assert_that(result, _not_empty)
407+
408+
380409
def open_shards(glob_pattern, mode='rt', encoding='utf-8'):
381410
"""Returns a composite file of all shards matching the given glob pattern.
382411

0 commit comments

Comments
 (0)