Repository navigation
Conversation
|
/gemini review |
|
/gemini review |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #41275 +/- ##
============================================
+ Coverage 57.48% 57.64% +0.15%
Complexity 2492 2492
============================================
Files 1204 1198 -6
Lines 171265 171357 +92
Branches 2103 2103
============================================
+ Hits 98456 98782 +326
+ Misses 70157 69923 -234
Partials 2652 2652
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
/gemini review |
There was a problem hiding this comment.
📋 Review Summary
This PR optimizes MetricsContainer cell lookups (get_counter, get_distribution, get_gauge, get_string_set, get_bounded_trie) by caching _TypedMetricName instances per cell_type on metric_name._typed_metric_names. While this avoids repeated _TypedMetricName allocations and MetricName.fast_name() computations on hot paths, storing _TypedMetricName back on MetricName.__dict__ creates a reference cycle (MetricName -> _typed_metric_names -> _TypedMetricName -> MetricName) that breaks pickling and deep-copying of MetricName instances after lookup.
🔍 General Feedback
- Unpickling /
deepcopyfailure due to cyclic__reduce__reconstruction:_TypedMetricName.__reduce__reconstructs via_TypedMetricName(self.cell_type, self.metric_name), and_TypedMetricName.__init__immediately callsmetric_name.fast_name(). When aMetricNamewith_typed_metric_namesin its__dict__is unpickled or passed tocopy.deepcopy,_TypedMetricName(cell_type, mn)is invoked whilemn.__dict__is still being built (before theBUILDstep populatesmn.name,mn.namespace, etc.), raisingAttributeError: 'MetricName' object has no attribute 'name'. - Recommended fix & test coverage: Define
__reduce__(or__getstate__/__setstate__) onMetricNameinsdks/python/apache_beam/metrics/metricbase.pyso transient_typed_metric_namescaches are excluded from serialization, and add a unit test inexecution_test.pyverifying thatpickle.loads(pickle.dumps(metric_name))andcopy.deepcopy(metric_name)succeed afterMetricsContainer.get_counter(metric_name).
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
| try: | ||
| cache = metric_name._typed_metric_names | ||
| except AttributeError: | ||
| cache = metric_name._typed_metric_names = {} |
There was a problem hiding this comment.
🟠 High: Caching _TypedMetricName(cell_type, metric_name) in metric_name._typed_metric_names introduces a reference cycle (metric_name -> _typed_metric_names -> _TypedMetricName -> metric_name) that causes pickle.loads(pickle.dumps(metric_name)), dill.loads(dill.dumps(metric_name)), and copy.deepcopy(metric_name) (as well as MetricKey / MetricResult containing metric_name) to fail with AttributeError: 'MetricName' object has no attribute 'name' once metric_name has been passed to MetricsContainer.get_*.
During unpickling or deepcopy of metric_name, Python allocates an uninitialized MetricName.__new__(MetricName) and memoizes it before reconstructing its __dict__. Reconstructing _typed_metric_names invokes _TypedMetricName.__reduce__() -> _TypedMetricName(cell_type, metric_name), whose __init__ calls metric_name.fast_name() before metric_name.__dict__ has been populated.
To fix this, define __reduce__ on MetricName in sdks/python/apache_beam/metrics/metricbase.py so the transient cache is not serialized:
def __reduce__(self):
return MetricName, (self.namespace, self.name, self.urn, self.labels)There was a problem hiding this comment.
Great catch! Fixed in d29f414: updated _TypedMetricName.__reduce__ and __init__ to pass fast_name directly so unpickling / pickler / deepcopy of a MetricName cycle does not invoke metric_name.fast_name() before metric_name.__dict__ is restored, and added TestMetricsContainer.test_pickle_and_deepcopy_metric_name_after_caching.
…ckling and deepcopy
|
/gemini review |
There was a problem hiding this comment.
📋 Review Summary
This PR optimizes metric lookups in MetricsContainer (get_counter, get_distribution, get_gauge, get_string_set, get_bounded_trie) by caching _TypedMetricName instances per cell_type on MetricName. It also updates _TypedMetricName.__reduce__ and __init__ to accept fast_name directly so that MetricName instances containing cached _TypedMetricName references remain safely serializable and copyable.
🔍 General Feedback
- The caching mechanism is clean, safe, and avoids redundant
_TypedMetricNameallocations,MetricName.fast_name()string formatting, and hash computations on hot metric update paths. - The previous serialization cycle issue during
pickle/dill/deepcopyhas been properly resolved and covered with unit tests inexecution_test.py.
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
Note: this PR was auto-generated by tooling. Before moving it out of draft, the author has done a full review of the PR, specifically evaluating it on the following criteria:
Full details of the methodology of generation and review can be found here - Apache Beam Python SDK Performance PRs Methodology & Branches Impact Ranking
If you have concerns with the PR or with the underlying methodology used to generate this, please comment below - they will be addressed by the author not the tooling :)
Summary of changes:
Optimizes
MetricsContainer._get_typed_metric_name(get_counter,get_distribution,get_gauge,get_string_set,get_bounded_trie) insdks/python/apache_beam/metrics/execution.py:_TypedMetricName(cell_type, metric_name)onmetric_name._typed_metric_namesinstead of allocating a new_TypedMetricNamewrapper object on every metric cell lookup.Dataflow benchmarks showing improvement:
2026-11-12_13_27_21-102437325936402480762026-11-12_13_27_21-17180255097007214814n1-standard-8,--autoscaling_algorithm=NONE --num_workers=1 --max_num_workers=1) Cloud Dataflow pipeline running40,000,000elements (240,000,000metric lookups) · 2×ParDo(MetricsContainer.get_counter), built-in transform stage execution time improved from2,297,401 ms->1,543,987 ms(1.49xfaster,-32.8%) (total single-worker job vCPU time:3353s->2611s).Additional benchmarks
upstream/mastervsupstream/master+ this PR alone):bench_31_metrics_container_typed_name_cache(c2feb961eef) (on branchperf/py-benchmarks-suite) — 640,000MetricsContainer.get_counter(mn).inc()calls improved from377.38 ms->140.37 ms(2.69xfaster,-62.8%,-237.01 ms) in strict isolation.n2-standard-4Benchmark (p28_ms:13_metrics_container_typed_name_640k&subbench_ms:03_metrics_container_lookup_ms): On a Cloud Dataflown2-standard-4worker, 640,000MetricsContainer.get_counter(mn)lookups improved from567 ms->216 ms(2.63xfaster,-61.9%), and 512,000get_counter(MetricName).inc()updates inWorkerSubsystemBenchDoFnimproved from517 ms->180 ms(2.87xfaster).core_window_row_combine&s8-loadtest): Included inrun_core_window_row_combine_pipeline(c2feb961eef) (3,000,000 generated events -> 9,000,000 sliding-windowed elements onn2-standard-4), which improved end-to-end worker stage execution time by 1.80x (-44.5%,599.6s->332.9s).Testing
pytest sdks/python/apache_beam/metrics/execution_test.py.