Optimize Prometheus reporter snapshot updates - #18562
Conversation
| private Integer prometheusReporterPort = 9091; | ||
|
|
||
| /** Whether Prometheus metrics are collected asynchronously into a cached snapshot. */ | ||
| private boolean prometheusReporterAsyncUpdate = true; |
There was a problem hiding this comment.
Adds the default-on feature flag to the shared metric configuration so async snapshot behavior is explicit, reloadable, and included in configuration equality and hash state.
| properties, | ||
| isConfigNode))); | ||
|
|
||
| loadConfig.setPrometheusReporterAsyncUpdate( |
There was a problem hiding this comment.
Loads the new switch while accepting the global and legacy metric-prefixed forms, preserving compatibility with existing node configuration conventions.
| serverTransport = serverTransport.secure(spec -> spec.sslContext(sslContext)); | ||
| } | ||
| httpServer = serverTransport.bindNow(); | ||
| if (METRIC_CONFIG.isPrometheusReporterAsyncUpdate()) { |
There was a problem hiding this comment.
Starts the periodic snapshot task only for async mode; synchronous mode continues to scrape on demand, while the cached response keeps Reactor request threads independent of expensive metric collection.
| @Override | ||
| public boolean stop() { | ||
| public synchronized boolean stop() { | ||
| stopSnapshotUpdater(); |
There was a problem hiding this comment.
Stops and disposes the snapshot task with the reporter so reloads, shutdown, and failed starts do not leave a scheduler behind.
| break; | ||
| case PROMETHEUS: | ||
| reporter = new PrometheusReporter(metricManager); | ||
| if (METRIC_CONFIG.isPrometheusReporterAsyncUpdate()) { |
There was a problem hiding this comment.
Creates the IoTDB-managed scheduler only when async snapshot updates are enabled; the disabled path now constructs the synchronous reporter without allocating a thread pool.
| // -------------------------- Metrics -------------------------- | ||
| SYSTEM_SCHEDULE_METRICS("SystemScheduleMetrics"), | ||
| RESOURCE_CONTROL_DISK_STATISTIC("ResourceControl-DataRegionDiskStatistics"), | ||
| PROMETHEUS_REPORTER_SNAPSHOT_UPDATER("PrometheusReporter-Snapshot-Updater"), |
There was a problem hiding this comment.
Registers a stable thread name for the snapshot updater and includes it in the metrics thread classification so the new background work remains observable.
| assertEquals(MetricLevel.ALL, metricConfig.getMetricLevel()); | ||
| assertEquals(10, (int) metricConfig.getAsyncCollectPeriodInSecond()); | ||
| assertEquals(9090, (int) metricConfig.getPrometheusReporterPort()); | ||
| assertEquals(false, metricConfig.isPrometheusReporterAsyncUpdate()); |
There was a problem hiding this comment.
Covers parsing the explicitly disabled setting; the companion DataNode assertion verifies the enabled value and protects the default/alias behavior.
| # The snapshot is refreshed every 15 seconds, matching Prometheus's default scrape interval. | ||
| # effectiveMode: restart | ||
| # Datatype: boolean | ||
| prometheus_reporter_async_update=true |
There was a problem hiding this comment.
Documents the default-enabled switch, its 15-second refresh cadence, and restart semantics in the shipped configuration template.
| | `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` | |
There was a problem hiding this comment.
Exposes the new operator-facing setting in the metrics documentation and records the default behavior.
| 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"; |
There was a problem hiding this comment.
Adds the asynchronous snapshot failure message to the i18n catalog so updater errors use the project localization mechanism.
| 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 异步更新监控项快照失败"; |
There was a problem hiding this comment.
Keeps the Chinese i18n catalog in key parity with the English catalog for the new updater failure message.
| } | ||
| String metrics = | ||
| METRIC_CONFIG.isPrometheusReporterAsyncUpdate() | ||
| ? getMetricsSnapshot() |
There was a problem hiding this comment.
The cache now uses a null value as an explicit uninitialized state, and this request path synchronously scrapes on the first request. This closes the startup window where AbstractMetricService has started the reporter but has not yet bound metric sets, which previously returned HTTP 200 with an empty body. The fallback is covered by IoTDBMetricIT.testPrometheusReporterWithoutAuth.
| } | ||
| // Delay the first background scrape until metric sets have been bound by the metric service. | ||
| snapshotUpdateFuture = | ||
| snapshotUpdateExecutor.scheduleAtFixedRate( |
There was a problem hiding this comment.
The first scheduled scrape is delayed until the normal 15-second refresh interval. MetricService starts reporters before AbstractMetricService binds metric sets, so scheduling at delay 0 creates the empty-snapshot race reported by CI. The delayed initial run preserves the existing cadence while the request fallback above covers requests arriving sooner.
| // 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) { |
There was a problem hiding this comment.
The updater ignores only the pre-initialization empty scrape, identified by a still-null cache. Once any snapshot has been published, an empty result is retained so metrics removed at runtime are reflected instead of leaving stale series in the cached response. The asynchronous path remains off Reactor request threads.
HTHou
left a comment
There was a problem hiding this comment.
Re-reviewed the latest revision. The initial snapshot race is addressed by the explicit uninitialized state, first-request fallback, and delayed background refresh. I found no remaining blocking issues.
| snapshotUpdateFuture = null; | ||
| } | ||
| if (snapshotUpdateExecutor != null) { | ||
| snapshotUpdateExecutor.shutdownNow(); |
There was a problem hiding this comment.
[P2] Recreate the managed scheduler when the reporter restarts
stopSnapshotUpdater() always shuts down and clears the executor supplied by MetricService. If this reporter is started again through the public AbstractMetricService.stop/start(ReporterType) APIs, or retried after start() failed, startSnapshotUpdater() falls back to a raw daemon executor. That replacement is not registered with JMX or ThreadPoolMetrics, and its lowercase name does not match ThreadName.PROMETHEUS_REPORTER_SNAPSHOT_UPDATER, so CPU metrics classify it as UNKNOWN. Please retain an executor supplier/factory and create a new IoTDB-managed executor on every start (or recreate the reporter) rather than losing the managed executor after the first stop or failed start.
| reporter = | ||
| new PrometheusReporter( | ||
| metricManager, | ||
| IoTDBThreadPoolFactory.newSingleThreadScheduledExecutor( |
There was a problem hiding this comment.
[P3] Avoid registering the scheduler before duplicate reporters are rejected
setMetricReporterList() preserves duplicates, while CompositeReporter.addReporter() rejects them only after this constructor has run. With PROMETHEUS,PROMETHEUS, the second IoTDBThreadPoolFactory executor registers under the same pool name and overwrites the first entry in ThreadPoolMetrics; the second reporter is then discarded. The active updater's thread_pool_* gauges therefore point at the never-started executor. Please deduplicate before constructing reporters, make addReporter() report acceptance, or allocate the scheduler lazily in start().
| 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()); |
There was a problem hiding this comment.
Caideyipi P2 fixed: the reporter now stores a scheduler factory and invokes it on every start, so stop/start and failed-start retries receive a fresh executor. The service supplies an IoTDB-managed factory, preserving JMX/ThreadPoolMetrics registration and the Prometheus thread classification across restarts; the standalone constructor retains its daemon executor fallback.
| case PROMETHEUS: | ||
| reporter = new PrometheusReporter(metricManager); | ||
| if (METRIC_CONFIG.isPrometheusReporterAsyncUpdate()) { | ||
| // Defer pool creation until start so duplicate reporters rejected below do not |
There was a problem hiding this comment.
Caideyipi P3 fixed: pool creation is now deferred into the reporter start lifecycle. If duplicate PROMETHEUS entries are rejected by CompositeReporter, no IoTDB thread pool has been created or registered for the discarded reporter, so thread-pool gauges continue to describe the active updater.
Summary
Validation