prodriguezdefino opened a new pull request, #41072:
URL: https://github.com/apache/beam/pull/41072

   Problem:
   In Stage.Execute (sdks/go/pkg/beam/runners/prism/internal/stage.go), Prism 
records final bundle metrics before requesting metadata for unknown short IDs 
from the SDK worker:
   
       unknownIDs := j.ContributeFinalMetrics(resp)
       if len(unknownIDs) > 0 {
           md := wk.MonitoringMetadata(ctx, unknownIDs)
           j.AddMetricShortIDs(md)
       }
   
   metricsStore.contributeMetrics 
(sdks/go/pkg/beam/runners/prism/internal/jobservices/metrics.go) stores only 
one payload per unresolved short ID in unprocessedPayloads [2]map[string][]byte:
   
       m.unprocessedPayloads[d][short] = payload
   
   wk.MonitoringMetadata is a gRPC call to the worker and runs without m.mu 
held. When two or more concurrent bundles of the same stage finish before 
AddMetricShortIDs runs, each later bundle overwrites 
m.unprocessedPayloads[committed][short], and AddShortIDs replays only the last 
bundle's payload. Data output is intact, but GetJobMetrics under-counts the 
metric.
   
   Fix:
   Change unprocessedPayloads to [2]map[string][][]byte. Append committed 
payloads for unresolved short IDs, replace tentative payloads with the latest 
snapshot, replay buffered payloads in arrival order inside AddShortIDs, and 
delete only resolved short IDs so other pending short IDs are preserved. Add 
unit and race tests in metrics_test.go.
   
   Reproduction (Python SDK on PrismRunner):
   ```python
   import threading
   
   import apache_beam as beam
   from apache_beam.metrics.metric import Metrics
   from apache_beam.options.pipeline_options import PipelineOptions
   from apache_beam.transforms.trigger import AccumulationMode, AfterCount, 
Repeatedly
   from apache_beam.transforms.window import GlobalWindows
   
   BARRIER = threading.Barrier(4)
   
   class ConcurrentCounterDoFn(beam.DoFn):
       def __init__(self):
           self.counter = Metrics.counter("ns", "bundles")
   
       def process(self, element):
           BARRIER.wait(timeout=10)
           self.counter.inc(1)
           yield element
   
   if __name__ == "__main__":
       options = PipelineOptions(["--runner=PrismRunner", 
"--environment_type=LOOPBACK"])
       p = beam.Pipeline(options=options)
       _ = (
           p
           | beam.Create([("k1", 1), ("k2", 1), ("k3", 1), ("k4", 1)])
           | beam.WindowInto(
               GlobalWindows(),
               trigger=Repeatedly(AfterCount(1)),
               accumulation_mode=AccumulationMode.DISCARDING,
           )
           | beam.GroupByKey()
           | beam.ParDo(ConcurrentCounterDoFn())
       )
       res = p.run()
       res.wait_until_finish()
       counters = res.metrics().query()["counters"]
       committed = counters[0].committed
       print("committed:", committed)
       assert committed == 4, f"expected 4 committed updates, got {committed}"
   ```
   
   Before the fix: `committed` is `1` (or `< 4`), and the assertion fails.
   After the fix: `committed: 4`.
   
   <!--
   
   Thank you for your contribution! Follow this checklist to help us 
incorporate your contribution quickly and easily:
   
    - [ ] If this contribution is large, please file an Apache [Individual 
Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
    - [ ] Replace these instructions with a description of your change and why 
you have made it.
    - [ ] Mention the appropriate issue in your description (for example: 
`addresses #123` or `fixes #456`).
    - [ ] Update `CHANGES.md` with noteworthy changes.
   
   See [CONTRIBUTING.md]() for more details.
   
   -->
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to