This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new e9dd92d13b6 Optimize Prometheus reporter snapshot updates (#18562)
e9dd92d13b6 is described below
commit e9dd92d13b65f417890d352cda4e8294f55c875d
Author: Jiang Tian <[email protected]>
AuthorDate: Mon Sep 7 09:45:25 2026 +0800
Optimize Prometheus reporter snapshot updates (#18562)
* Optimize Prometheus reporter snapshot updates
* fix compilation
* Fix Prometheus reporter initial snapshot race
* Fix Prometheus scheduler lifecycle and duplicate allocation
---
iotdb-core/metrics/ReadMe.md | 1 +
.../apache/iotdb/metrics/i18n/MetricsMessages.java | 1 +
.../apache/iotdb/metrics/i18n/MetricsMessages.java | 1 +
.../apache/iotdb/metrics/config/MetricConfig.java | 14 +++
.../metrics/config/MetricConfigDescriptor.java | 21 ++++
.../reporter/prometheus/PrometheusReporter.java | 120 ++++++++++++++++++++-
.../iotdb/metrics/config/MetricConfigTest.java | 16 +++
.../prometheus/PrometheusReporterTest.java | 75 +++++++++++++
.../conf/iotdb-system.properties.template | 6 ++
.../iotdb/commons/concurrent/ThreadName.java | 2 +
.../commons/service/metric/MetricService.java | 15 ++-
11 files changed, 267 insertions(+), 5 deletions(-)
diff --git a/iotdb-core/metrics/ReadMe.md b/iotdb-core/metrics/ReadMe.md
index f0eb361f0a2..6ab575abe09 100644
--- a/iotdb-core/metrics/ReadMe.md
+++ b/iotdb-core/metrics/ReadMe.md
@@ -63,6 +63,7 @@ Configure the metrics module through
`iotdb-system.properties`. The main options
| `dn(cn)_metric_level` | Initial metric level. | `OFF`, `CORE`, `IMPORTANT`,
`NORMAL`, `ALL` |
| `cn_metric_prometheus_reporter_port` | Prometheus HTTP port for ConfigNode.
| `9091` |
| `dn_metric_prometheus_reporter_port` | Prometheus HTTP port for DataNode. |
`9092` |
+| `prometheus_reporter_async_update` | Serve a cached Prometheus snapshot
refreshed every 15 seconds. | `true` |
More details, see the User Guide and the `iotdb-system.properties.template`
file.
diff --git
a/iotdb-core/metrics/interface/src/main/i18n/en/org/apache/iotdb/metrics/i18n/MetricsMessages.java
b/iotdb-core/metrics/interface/src/main/i18n/en/org/apache/iotdb/metrics/i18n/MetricsMessages.java
index 778da2b32bf..4cd7a7fd0a1 100644
---
a/iotdb-core/metrics/interface/src/main/i18n/en/org/apache/iotdb/metrics/i18n/MetricsMessages.java
+++
b/iotdb-core/metrics/interface/src/main/i18n/en/org/apache/iotdb/metrics/i18n/MetricsMessages.java
@@ -128,5 +128,6 @@ public final class MetricsMessages {
public static final String
LOG_IOTDBSESSIONREPORTER_START_WRITE_ARG_ARG_E79CDDAE = "IoTDBSessionReporter
start, write to {}:{}";
public static final String
LOG_PROMETHEUSREPORTER_STARTED_USE_PORT_ARG_A688FFC8 = "PrometheusReporter
started, use port {}";
public static final String
LOG_DETECTED_ERROR_TAKING_METRIC_TIMER_SNAPSHOT_WILL_DISCARD_METRIC_B7154169 =
"Detected an error when taking metric timer snapshot, will discard this metric";
+ public static final String
LOG_PROMETHEUSREPORTER_FAILED_TO_UPDATE_METRICS_SNAPSHOT_ASYNCHRONOUSLY_F19FE4E3
= "PrometheusReporter failed to update metrics snapshot asynchronously";
}
diff --git
a/iotdb-core/metrics/interface/src/main/i18n/zh/org/apache/iotdb/metrics/i18n/MetricsMessages.java
b/iotdb-core/metrics/interface/src/main/i18n/zh/org/apache/iotdb/metrics/i18n/MetricsMessages.java
index fd68351a939..968694f6eb0 100644
---
a/iotdb-core/metrics/interface/src/main/i18n/zh/org/apache/iotdb/metrics/i18n/MetricsMessages.java
+++
b/iotdb-core/metrics/interface/src/main/i18n/zh/org/apache/iotdb/metrics/i18n/MetricsMessages.java
@@ -124,5 +124,6 @@ public final class MetricsMessages {
public static final String
LOG_IOTDBSESSIONREPORTER_START_WRITE_ARG_ARG_E79CDDAE = "IoTDBSessionReporter
启动,写入 {}:{}";
public static final String
LOG_PROMETHEUSREPORTER_STARTED_USE_PORT_ARG_A688FFC8 = "PrometheusReporter
已启动,使用端口 {}";
public static final String
LOG_DETECTED_ERROR_TAKING_METRIC_TIMER_SNAPSHOT_WILL_DISCARD_METRIC_B7154169 =
"获取 metric timer 快照时检测到错误,将丢弃该 metric";
+ public static final String
LOG_PROMETHEUSREPORTER_FAILED_TO_UPDATE_METRICS_SNAPSHOT_ASYNCHRONOUSLY_F19FE4E3
= "PrometheusReporter 异步更新监控项快照失败";
}
diff --git
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java
index 2e0eaf5cb0b..c5d55162f8c 100644
---
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java
+++
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfig.java
@@ -52,6 +52,9 @@ public class MetricConfig {
/** The export port for prometheus to get metrics. */
private Integer prometheusReporterPort = 9091;
+ /** Whether Prometheus metrics are collected asynchronously into a cached
snapshot. */
+ private boolean prometheusReporterAsyncUpdate = true;
+
private String prometheusReporterUsername = "";
private String prometheusReporterPassword = "";
@@ -140,6 +143,14 @@ public class MetricConfig {
this.prometheusReporterPort = prometheusReporterPort;
}
+ public boolean isPrometheusReporterAsyncUpdate() {
+ return prometheusReporterAsyncUpdate;
+ }
+
+ public void setPrometheusReporterAsyncUpdate(boolean
prometheusReporterAsyncUpdate) {
+ this.prometheusReporterAsyncUpdate = prometheusReporterAsyncUpdate;
+ }
+
public boolean prometheusNeedAuth() {
return prometheusReporterUsername != null &&
!prometheusReporterUsername.isEmpty();
}
@@ -264,6 +275,7 @@ public class MetricConfig {
metricLevel = newMetricConfig.getMetricLevel();
asyncCollectPeriodInSecond =
newMetricConfig.getAsyncCollectPeriodInSecond();
prometheusReporterPort = newMetricConfig.getPrometheusReporterPort();
+ prometheusReporterAsyncUpdate =
newMetricConfig.isPrometheusReporterAsyncUpdate();
prometheusReporterUsername =
newMetricConfig.getPrometheusReporterUsername();
prometheusReporterPassword =
newMetricConfig.getPrometheusReporterPassword();
internalReporterType = newMetricConfig.getInternalReportType();
@@ -287,6 +299,7 @@ public class MetricConfig {
&& metricLevel.equals(anotherMetricConfig.getMetricLevel())
&&
asyncCollectPeriodInSecond.equals(anotherMetricConfig.getAsyncCollectPeriodInSecond())
&&
prometheusReporterPort.equals(anotherMetricConfig.getPrometheusReporterPort())
+ && prometheusReporterAsyncUpdate ==
anotherMetricConfig.isPrometheusReporterAsyncUpdate()
&&
iotdbReporterConfig.equals(anotherMetricConfig.getIoTDBReporterConfig())
&&
internalReporterType.equals(anotherMetricConfig.getInternalReportType());
}
@@ -298,6 +311,7 @@ public class MetricConfig {
metricLevel,
asyncCollectPeriodInSecond,
prometheusReporterPort,
+ prometheusReporterAsyncUpdate,
iotdbReporterConfig,
internalReporterType);
}
diff --git
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java
index 0d83d387409..113bff7ebcc 100644
---
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java
+++
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/config/MetricConfigDescriptor.java
@@ -129,6 +129,11 @@ public class MetricConfigDescriptor {
properties,
prefix)));
+ loadConfig.setPrometheusReporterAsyncUpdate(
+ Boolean.parseBoolean(
+ getPrometheusReporterAsyncUpdateProperty(
+ properties, prefix,
loadConfig.isPrometheusReporterAsyncUpdate())));
+
loadConfig.setPrometheusReporterUsername(
getPropertyWithoutPrefix(
"metric_prometheus_reporter_username",
@@ -222,6 +227,22 @@ public class MetricConfigDescriptor {
.orElse(defaultValue);
}
+ private String getPrometheusReporterAsyncUpdateProperty(
+ Properties properties, String prefix, boolean defaultValue) {
+ String value = properties.getProperty("prometheus_reporter_async_update");
+ if (value == null) {
+ // Keep accepting the metric-prefixed forms for compatibility with
node-specific configs.
+ value =
properties.getProperty("metric_prometheus_reporter_async_update");
+ }
+ if (value == null) {
+ value = properties.getProperty(prefix +
"prometheus_reporter_async_update");
+ }
+ if (value == null) {
+ value = properties.getProperty(prefix +
"metric_prometheus_reporter_async_update");
+ }
+ return value == null ? String.valueOf(defaultValue) : value.trim();
+ }
+
private static class MetricConfigDescriptorHolder {
private static final MetricConfigDescriptor INSTANCE = new
MetricConfigDescriptor();
}
diff --git
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java
index 7846e056f8c..00ae6c6b988 100644
---
a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java
+++
b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporter.java
@@ -66,29 +66,68 @@ import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
public class PrometheusReporter implements Reporter {
private static final Logger LOGGER =
LoggerFactory.getLogger(PrometheusReporter.class);
private static final MetricConfig METRIC_CONFIG =
MetricConfigDescriptor.getInstance().getMetricConfig();
+ private static final long PROMETHEUS_DEFAULT_SCRAPE_INTERVAL_SECONDS = 15;
private final AbstractMetricManager metricManager;
- private DisposableServer httpServer;
+ private final Supplier<ScheduledExecutorService>
snapshotUpdateExecutorSupplier;
+ private volatile ScheduledExecutorService snapshotUpdateExecutor;
+ private volatile DisposableServer httpServer;
+
+ /** A null snapshot means that no complete scrape has been published yet. */
+ private volatile String metricsSnapshot;
+
+ private volatile ScheduledFuture<?> snapshotUpdateFuture;
private static final String REALM = "metrics";
public static final String BASIC_AUTH_PREFIX = "Basic ";
public static final char DIVIDER_BETWEEN_USERNAME_AND_DIVIDER = ':';
+ /**
+ * Creates a reporter with a self-managed scheduler for compatibility with
standalone users.
+ * Server-side code should use the constructor accepting a scheduler factory.
+ */
public PrometheusReporter(AbstractMetricManager metricManager) {
+ this(metricManager,
PrometheusReporter::newStandaloneSnapshotUpdateExecutor);
+ }
+
+ /**
+ * Creates a reporter with a scheduler factory. The factory is invoked on
every start to obtain a
+ * fresh executor for the reporter lifecycle.
+ */
+ public PrometheusReporter(
+ AbstractMetricManager metricManager,
+ Supplier<ScheduledExecutorService> snapshotUpdateExecutorSupplier) {
this.metricManager = metricManager;
+ this.snapshotUpdateExecutorSupplier =
Objects.requireNonNull(snapshotUpdateExecutorSupplier);
+ }
+
+ private static ScheduledExecutorService
newStandaloneSnapshotUpdateExecutor() {
+ return Executors.newSingleThreadScheduledExecutor(
+ runnable -> {
+ Thread thread = new Thread(runnable,
"prometheus-reporter-snapshot-updater");
+ thread.setDaemon(true);
+ return thread;
+ });
}
@Override
@SuppressWarnings("java:S1181")
- public boolean start() {
+ public synchronized boolean start() {
if (httpServer != null) {
LOGGER.warn(MetricsMessages.PROMETHEUS_REPORTER_ALREADY_START);
return false;
}
+ // A reporter can be started again after its metric manager has been reset.
+ metricsSnapshot = null;
try {
HttpServer serverTransport =
HttpServer.create()
@@ -104,8 +143,12 @@ public class PrometheusReporter implements Reporter {
// authenticate not pass
return Mono.empty();
}
+ String metrics =
+ METRIC_CONFIG.isPrometheusReporterAsyncUpdate()
+ ? getMetricsSnapshot()
+ : scrape();
return res.header(HttpHeaderNames.CONTENT_TYPE,
"text/plain")
- .sendString(Mono.just(scrape()));
+ .sendString(Mono.just(metrics));
}));
if (METRIC_CONFIG.isEnableSSL()) {
SslContext sslContext;
@@ -122,9 +165,20 @@ public class PrometheusReporter implements Reporter {
serverTransport = serverTransport.secure(spec ->
spec.sslContext(sslContext));
}
httpServer = serverTransport.bindNow();
+ if (METRIC_CONFIG.isPrometheusReporterAsyncUpdate()) {
+ startSnapshotUpdater();
+ }
} catch (Throwable e) {
// catch Throwable rather than Exception here because the code above
might cause a
// NoClassDefFoundError
+ stopSnapshotUpdater();
+ if (httpServer != null) {
+ try {
+ httpServer.disposeNow(Duration.ofSeconds(10));
+ } catch (Exception ignored) {
+ // do nothing
+ }
+ }
httpServer = null;
LOGGER.warn(MetricsMessages.PROMETHEUS_REPORTER_START_FAILED, e);
return false;
@@ -135,6 +189,63 @@ public class PrometheusReporter implements Reporter {
return true;
}
+ @SuppressWarnings("unsafeThreadSchedule")
+ private void startSnapshotUpdater() {
+ // Keep metric collection off Reactor HTTP threads and avoid overlapping
scrapes.
+ if (snapshotUpdateExecutor == null || snapshotUpdateExecutor.isShutdown())
{
+ // Create a fresh executor for every start so a stopped reporter can be
started again with
+ // the same managed thread-pool factory.
+ snapshotUpdateExecutor =
Objects.requireNonNull(snapshotUpdateExecutorSupplier.get());
+ }
+ // Delay the first background scrape until metric sets have been bound by
the metric service.
+ snapshotUpdateFuture =
+ snapshotUpdateExecutor.scheduleAtFixedRate(
+ this::updateSnapshot,
+ PROMETHEUS_DEFAULT_SCRAPE_INTERVAL_SECONDS,
+ PROMETHEUS_DEFAULT_SCRAPE_INTERVAL_SECONDS,
+ TimeUnit.SECONDS);
+ }
+
+ private void updateSnapshot() {
+ try {
+ String snapshot = scrape();
+ // Do not publish an empty scrape taken before metric sets are bound.
The request path will
+ // synchronously scrape until the first complete snapshot is available.
Empty snapshots are
+ // published after initialization so removed metrics are not kept in the
cache indefinitely.
+ if (!snapshot.isEmpty() || metricsSnapshot != null) {
+ metricsSnapshot = snapshot;
+ }
+ } catch (Throwable t) {
+ LOGGER.error(
+ MetricsMessages
+
.LOG_PROMETHEUSREPORTER_FAILED_TO_UPDATE_METRICS_SNAPSHOT_ASYNCHRONOUSLY_F19FE4E3,
+ t);
+ }
+ }
+
+ private String getMetricsSnapshot() {
+ String snapshot = metricsSnapshot;
+ if (snapshot == null) {
+ snapshot = scrape();
+ if (!snapshot.isEmpty()) {
+ metricsSnapshot = snapshot;
+ }
+ }
+ return snapshot;
+ }
+
+ private void stopSnapshotUpdater() {
+ if (snapshotUpdateFuture != null) {
+ snapshotUpdateFuture.cancel(false);
+ snapshotUpdateFuture = null;
+ }
+ ScheduledExecutorService executor = snapshotUpdateExecutor;
+ snapshotUpdateExecutor = null;
+ if (executor != null) {
+ executor.shutdownNow();
+ }
+ }
+
private boolean authenticate(HttpServerRequest req, HttpServerResponse res) {
if (!METRIC_CONFIG.prometheusNeedAuth()) {
return true;
@@ -318,7 +429,8 @@ public class PrometheusReporter implements Reporter {
}
@Override
- public boolean stop() {
+ public synchronized boolean stop() {
+ stopSnapshotUpdater();
if (httpServer != null) {
try {
httpServer.disposeNow(Duration.ofSeconds(10));
diff --git
a/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/config/MetricConfigTest.java
b/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/config/MetricConfigTest.java
index 30d3bd1ff37..3d1d3a0b0cf 100644
---
a/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/config/MetricConfigTest.java
+++
b/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/config/MetricConfigTest.java
@@ -39,6 +39,7 @@ public class MetricConfigTest {
properties.setProperty("cn_metric_level", "ALL");
properties.setProperty("cn_metric_async_collect_period", "10");
properties.setProperty("cn_metric_prometheus_reporter_port", "9090");
+ properties.setProperty("prometheus_reporter_async_update", "false");
properties.setProperty("cn_metric_iotdb_reporter_host", "0.0.0.0");
properties.setProperty("cn_metric_iotdb_reporter_port", "6669");
properties.setProperty("cn_metric_iotdb_reporter_username", "user");
@@ -55,6 +56,7 @@ public class MetricConfigTest {
assertEquals(MetricLevel.ALL, metricConfig.getMetricLevel());
assertEquals(10, (int) metricConfig.getAsyncCollectPeriodInSecond());
assertEquals(9090, (int) metricConfig.getPrometheusReporterPort());
+ assertEquals(false, metricConfig.isPrometheusReporterAsyncUpdate());
IoTDBReporterConfig reporterConfig = metricConfig.getIoTDBReporterConfig();
assertEquals("0.0.0.0", reporterConfig.getHost());
@@ -75,6 +77,7 @@ public class MetricConfigTest {
properties.setProperty("dn_metric_level", "ALL");
properties.setProperty("dn_metric_async_collect_period", "10");
properties.setProperty("dn_metric_prometheus_reporter_port", "9090");
+ properties.setProperty("metric_prometheus_reporter_async_update", "true");
properties.setProperty("dn_metric_iotdb_reporter_host", "0.0.0.0");
properties.setProperty("dn_metric_iotdb_reporter_port", "6669");
properties.setProperty("dn_metric_iotdb_reporter_username", "user");
@@ -92,6 +95,7 @@ public class MetricConfigTest {
assertEquals(MetricLevel.ALL, metricConfig.getMetricLevel());
assertEquals(10, (int) metricConfig.getAsyncCollectPeriodInSecond());
assertEquals(9090, (int) metricConfig.getPrometheusReporterPort());
+ assertEquals(true, metricConfig.isPrometheusReporterAsyncUpdate());
IoTDBReporterConfig reporterConfig = metricConfig.getIoTDBReporterConfig();
assertEquals("0.0.0.0", reporterConfig.getHost());
@@ -103,4 +107,16 @@ public class MetricConfigTest {
assertEquals(5, (int) reporterConfig.getPushPeriodInSecond());
assertEquals(InternalReporterType.IOTDB,
metricConfig.getInternalReportType());
}
+
+ @Test
+ public void testMetricConfigWithCustomNodePrefix() {
+ Properties properties = new Properties();
+ properties.setProperty("sn_metric_prometheus_reporter_async_update",
"false");
+
+ MetricConfigDescriptor.getInstance().loadProps(properties, "sn_");
+
+ assertEquals(
+ false,
+
MetricConfigDescriptor.getInstance().getMetricConfig().isPrometheusReporterAsyncUpdate());
+ }
}
diff --git
a/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporterTest.java
b/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporterTest.java
new file mode 100644
index 00000000000..7072777ac66
--- /dev/null
+++
b/iotdb-core/metrics/interface/src/test/java/org/apache/iotdb/metrics/reporter/prometheus/PrometheusReporterTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.iotdb.metrics.reporter.prometheus;
+
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.impl.DoNothingMetricManager;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class PrometheusReporterTest {
+
+ @Test
+ public void testManagedExecutorRecreatedAfterRestart() {
+ MetricConfig metricConfig =
MetricConfigDescriptor.getInstance().getMetricConfig();
+ boolean originalAsyncUpdate =
metricConfig.isPrometheusReporterAsyncUpdate();
+ Integer originalPort = metricConfig.getPrometheusReporterPort();
+ metricConfig.setPrometheusReporterAsyncUpdate(true);
+ metricConfig.setPrometheusReporterPort(0);
+
+ AtomicInteger factoryCalls = new AtomicInteger();
+ List<ScheduledExecutorService> executors = new ArrayList<>();
+ PrometheusReporter reporter =
+ new PrometheusReporter(
+ new DoNothingMetricManager(),
+ () -> {
+ factoryCalls.incrementAndGet();
+ ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
+ executors.add(executor);
+ return executor;
+ });
+ try {
+ assertTrue(reporter.start());
+ assertEquals(1, factoryCalls.get());
+ assertTrue(reporter.stop());
+ assertTrue(executors.get(0).isShutdown());
+
+ assertTrue(reporter.start());
+ assertEquals(2, factoryCalls.get());
+ assertTrue(reporter.stop());
+ assertTrue(executors.get(1).isShutdown());
+ } finally {
+ reporter.stop();
+ metricConfig.setPrometheusReporterAsyncUpdate(originalAsyncUpdate);
+ metricConfig.setPrometheusReporterPort(originalPort);
+ executors.forEach(ScheduledExecutorService::shutdownNow);
+ }
+ }
+}
diff --git
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index 95a8154bba4..866dde0a94e 100644
---
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -382,6 +382,12 @@ metric_prometheus_reporter_username=
# Datatype: String
metric_prometheus_reporter_password=
+# Whether Prometheus metrics are collected asynchronously and served from a
cached snapshot.
+# The snapshot is refreshed every 15 seconds, matching Prometheus's default
scrape interval.
+# effectiveMode: restart
+# Datatype: boolean
+prometheus_reporter_async_update=true
+
# The reporters of metric module to report metrics
# If there are more than one reporter, please separate them by commas ",".
# Options: [JMX, PROMETHEUS]
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadName.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadName.java
index 606a08bde33..a0b62cd6a62 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadName.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadName.java
@@ -191,6 +191,7 @@ public enum ThreadName {
// -------------------------- Metrics --------------------------
SYSTEM_SCHEDULE_METRICS("SystemScheduleMetrics"),
RESOURCE_CONTROL_DISK_STATISTIC("ResourceControl-DataRegionDiskStatistics"),
+ PROMETHEUS_REPORTER_SNAPSHOT_UPDATER("PrometheusReporter-Snapshot-Updater"),
PROMETHEUS_REACTOR_HTTP_EPOLL("reactor-http-epoll"),
PROMETHEUS_REACTOR_HTTP_NIO("reactor-http-nio"),
PROMETHEUS_BOUNDED_ELASTIC("boundedElastic-evictor"),
@@ -394,6 +395,7 @@ public enum ThreadName {
Arrays.asList(
SYSTEM_SCHEDULE_METRICS,
RESOURCE_CONTROL_DISK_STATISTIC,
+ PROMETHEUS_REPORTER_SNAPSHOT_UPDATER,
PROMETHEUS_REACTOR_HTTP_EPOLL,
PROMETHEUS_REACTOR_HTTP_NIO,
PROMETHEUS_REACTOR_HTTP_EPOLL,
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
index 51357fe8998..4af78e3d9cf 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
@@ -19,6 +19,8 @@
package org.apache.iotdb.commons.service.metric;
+import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
+import org.apache.iotdb.commons.concurrent.ThreadName;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.exception.StartupException;
import org.apache.iotdb.commons.i18n.ServiceMessages;
@@ -74,7 +76,18 @@ public class MetricService extends AbstractMetricService
implements MetricServic
hasJmxReporter = true;
break;
case PROMETHEUS:
- reporter = new PrometheusReporter(metricManager);
+ if (METRIC_CONFIG.isPrometheusReporterAsyncUpdate()) {
+ // Defer pool creation until start so duplicate reporters rejected
below do not
+ // register an unused pool.
+ reporter =
+ new PrometheusReporter(
+ metricManager,
+ () ->
+
IoTDBThreadPoolFactory.newSingleThreadScheduledExecutor(
+
ThreadName.PROMETHEUS_REPORTER_SNAPSHOT_UPDATER.getName()));
+ } else {
+ reporter = new PrometheusReporter(metricManager);
+ }
break;
case IOTDB:
reporter = new IoTDBSessionReporter(metricManager);