This is an automated email from the ASF dual-hosted git repository.
zhoujinsong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git
The following commit(s) were added to refs/heads/master by this push:
new c4c0aef95 [hotfix][Metrics] Iterate a locked table-runtime snapshot in
gauges (#4326)
c4c0aef95 is described below
commit c4c0aef95e09c28768a491515b98b79b9593d998
Author: ConradJam <[email protected]>
AuthorDate: Thu Sep 10 11:21:58 2026 +0800
[hotfix][Metrics] Iterate a locked table-runtime snapshot in gauges (#4326)
* [hotfix][Metrics] Iterate a locked snapshot of tracked tables in group
gauges
OptimizerGroupMetrics' five table-count gauges streamed over
SchedulingPolicy's live HashMap on scrape threads without tableLock;
concurrent addTable/removeTable (table onboarding, group migration)
threw ConcurrentModificationException mid-scrape. getTableRuntimeMap()
is @VisibleForTesting yet was the production access path.
Add tableRuntimesSnapshot() (locked copy, same pattern as
tableIdentifiersSnapshot) and point the gauges at it. Churn regression
test: with the lock temporarily removed it fails 2 of 3 runs with CME;
locked it is stable.
Fix record: docs/fix-records/2026-08-16-fix-17-gauge-snapshot-cme.md
* [hotfix][Metrics] Verify table gauges use runtime snapshots
Exercise the registered optimizer-group gauges directly and fail
deterministically if they read SchedulingPolicy's live runtime map. This
replaces the snapshot-only test that did not cover the production metric path.
---
.../server/optimizing/OptimizerGroupMetrics.java | 10 +--
.../optimizing/TestOptimizerGroupMetrics.java | 94 ++++++++++++++++++++++
2 files changed, 99 insertions(+), 5 deletions(-)
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/OptimizerGroupMetrics.java
b/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/OptimizerGroupMetrics.java
index 4b640d4da..afeea9b10 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/OptimizerGroupMetrics.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/OptimizerGroupMetrics.java
@@ -163,7 +163,7 @@ public class OptimizerGroupMetrics {
OPTIMIZER_GROUP_PLANING_TABLES,
(Gauge<Long>)
() ->
-
optimizingQueue.getSchedulingPolicy().getTableRuntimeMap().values().stream()
+
optimizingQueue.getSchedulingPolicy().snapshotTableRuntimes().stream()
.filter(t -> t.getOptimizingStatus().equals(PLANNING))
.count());
registerMetric(
@@ -171,7 +171,7 @@ public class OptimizerGroupMetrics {
OPTIMIZER_GROUP_PENDING_TABLES,
(Gauge<Long>)
() ->
-
optimizingQueue.getSchedulingPolicy().getTableRuntimeMap().values().stream()
+
optimizingQueue.getSchedulingPolicy().snapshotTableRuntimes().stream()
.filter(t -> t.getOptimizingStatus().equals(PENDING))
.count());
registerMetric(
@@ -179,7 +179,7 @@ public class OptimizerGroupMetrics {
OPTIMIZER_GROUP_EXECUTING_TABLES,
(Gauge<Long>)
() ->
-
optimizingQueue.getSchedulingPolicy().getTableRuntimeMap().values().stream()
+
optimizingQueue.getSchedulingPolicy().snapshotTableRuntimes().stream()
.filter(t -> t.getOptimizingStatus().isProcessing())
.count());
registerMetric(
@@ -187,7 +187,7 @@ public class OptimizerGroupMetrics {
OPTIMIZER_GROUP_IDLE_TABLES,
(Gauge<Long>)
() ->
-
optimizingQueue.getSchedulingPolicy().getTableRuntimeMap().values().stream()
+
optimizingQueue.getSchedulingPolicy().snapshotTableRuntimes().stream()
.filter(t -> t.getOptimizingStatus().equals(IDLE))
.count());
registerMetric(
@@ -195,7 +195,7 @@ public class OptimizerGroupMetrics {
OPTIMIZER_GROUP_COMMITTING_TABLES,
(Gauge<Long>)
() ->
-
optimizingQueue.getSchedulingPolicy().getTableRuntimeMap().values().stream()
+
optimizingQueue.getSchedulingPolicy().snapshotTableRuntimes().stream()
.filter(t -> t.getOptimizingStatus().equals(COMMITTING))
.count());
diff --git
a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizerGroupMetrics.java
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizerGroupMetrics.java
new file mode 100644
index 000000000..08ae763c3
--- /dev/null
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizerGroupMetrics.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.amoro.server.optimizing;
+
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.GROUP_TAG;
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.OPTIMIZER_GROUP_COMMITTING_TABLES;
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.OPTIMIZER_GROUP_EXECUTING_TABLES;
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.OPTIMIZER_GROUP_IDLE_TABLES;
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.OPTIMIZER_GROUP_PENDING_TABLES;
+import static
org.apache.amoro.server.optimizing.OptimizerGroupMetrics.OPTIMIZER_GROUP_PLANING_TABLES;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.amoro.metrics.Gauge;
+import org.apache.amoro.metrics.MetricDefine;
+import org.apache.amoro.metrics.MetricKey;
+import org.apache.amoro.metrics.MetricRegistry;
+import org.apache.amoro.server.table.DefaultTableRuntime;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.List;
+import java.util.Map;
+
+public class TestOptimizerGroupMetrics {
+
+ private static final String GROUP_NAME = "test-group";
+
+ @Test
+ public void testTableGaugesUseRuntimeSnapshot() {
+ List<DefaultTableRuntime> tableRuntimes =
+ Arrays.asList(
+ tableRuntime(OptimizingStatus.PLANNING),
+ tableRuntime(OptimizingStatus.PENDING),
+ tableRuntime(OptimizingStatus.MINOR_OPTIMIZING),
+ tableRuntime(OptimizingStatus.IDLE),
+ tableRuntime(OptimizingStatus.COMMITTING));
+
+ SchedulingPolicy schedulingPolicy = mock(SchedulingPolicy.class);
+ when(schedulingPolicy.getTableRuntimeMap())
+ .thenThrow(new ConcurrentModificationException("live map must not be
iterated"));
+ when(schedulingPolicy.snapshotTableRuntimes()).thenReturn(tableRuntimes);
+
+ OptimizingQueue optimizingQueue = mock(OptimizingQueue.class);
+ when(optimizingQueue.getSchedulingPolicy()).thenReturn(schedulingPolicy);
+
+ MetricRegistry registry = new MetricRegistry();
+ OptimizerGroupMetrics metrics =
+ new OptimizerGroupMetrics(GROUP_NAME, registry, optimizingQueue);
+ metrics.register();
+ try {
+ Assert.assertEquals(1L, gaugeValue(registry,
OPTIMIZER_GROUP_PLANING_TABLES));
+ Assert.assertEquals(1L, gaugeValue(registry,
OPTIMIZER_GROUP_PENDING_TABLES));
+ Assert.assertEquals(2L, gaugeValue(registry,
OPTIMIZER_GROUP_EXECUTING_TABLES));
+ Assert.assertEquals(1L, gaugeValue(registry,
OPTIMIZER_GROUP_IDLE_TABLES));
+ Assert.assertEquals(1L, gaugeValue(registry,
OPTIMIZER_GROUP_COMMITTING_TABLES));
+ } finally {
+ metrics.unregister();
+ }
+ }
+
+ private DefaultTableRuntime tableRuntime(OptimizingStatus status) {
+ DefaultTableRuntime tableRuntime = mock(DefaultTableRuntime.class);
+ when(tableRuntime.getOptimizingStatus()).thenReturn(status);
+ return tableRuntime;
+ }
+
+ @SuppressWarnings("unchecked")
+ private long gaugeValue(MetricRegistry registry, MetricDefine define) {
+ Map<String, String> tagValues = Collections.singletonMap(GROUP_TAG,
GROUP_NAME);
+ Gauge<Long> gauge = (Gauge<Long>) registry.getMetrics().get(new
MetricKey(define, tagValues));
+ Assert.assertNotNull(gauge);
+ return gauge.getValue();
+ }
+}