Skip to content

prometheus: fix scrape duration growing unbounded (#13667)#13696

Open
PrashantBhanage wants to merge 2 commits into
apache:ghi13586-prometheusDrainage-20from
PrashantBhanage:fix/13667-clean
Open

prometheus: fix scrape duration growing unbounded (#13667)#13696
PrashantBhanage wants to merge 2 commits into
apache:ghi13586-prometheusDrainage-20from
PrashantBhanage:fix/13667-clean

Conversation

@PrashantBhanage

Copy link
Copy Markdown

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

N/A — backend-only change, no UI impact.

How Has This Been Tested?

  • Added unit tests for the TTL guard behavior.
  • Verified repeated scrapes within the refresh interval reuse cached metrics.
  • Verified scrapes after the interval trigger a new metrics recomputation.
  • Built the Prometheus integration module successfully.

How did you try to break this feature and the system with this change?

  • Sent concurrent /metrics scrape requests to verify the bounded executor and synchronization prevent overlapping recomputation.
  • Verified the executor shuts down cleanly in stop().
  • Verified changing prometheus.exporter.metrics.min.refresh.interval at runtime changes the refresh behavior without requiring a restart.

Three changes scoped exclusively to the prometheus exporter plugin:

1. Bounded HTTP executor — give the HttpServer a FixedThreadPool(2)
   instead of the JDK default single-threaded executor; shut it down
   cleanly in stop().

2. TTL guard on updateMetrics() — add a synchronized guard with a
   last-run timestamp so scrapes arriving faster than the configurable
   minimum interval (prometheus.exporter.metrics.min.refresh.interval,
   default 5 s) reuse the previously computed metrics instead of
   triggering a new recomputation.

3. Timing instrumentation — wrap updateMetrics() body with
   System.nanoTime() start/end and log elapsed wall-clock time at
   info level on every run (including exception path), so slow
   sub-collectors can be identified from logs.

Fixes: apache#13667
Ref: apache#13586
Comment on lines +116 to +119
if (httpExecutor != null) {
httpExecutor.shutdownNow();
logger.debug("Shut down Prometheus exporter http executor");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should httpExecutor also be set to null on httpServer?

something like

Suggested change
if (httpExecutor != null) {
httpExecutor.shutdownNow();
logger.debug("Shut down Prometheus exporter http executor");
}
if (httpExecutor != null) {
httpExecutor.shutdownNow();
logger.debug("Shut down Prometheus exporter http executor");
}
httpServer.setExecutor(null); // ¡¡¡¡¡Did not test!!!!!

?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I genuinely don’t understand this part of the change, I’ll read the co-pilot comment on the original issue as well. So if the executor is shutdown(), can start() still be called on it? (for instance)
And why a newFixedThreadPool of 2?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fair points:

  • setExecutor(null): not functionally needed since start() always
    creates a fresh HttpServer instance (the old one, executor included,
    just gets discarded/GC'd — and JDK's HttpServer can't be restarted
    after stop() anyway). But happy to add it defensively, no downside.
  • FixedThreadPool(2): kept deliberately small since the TTL guard
    means most scrapes now skip updateMetrics() entirely — 2 threads is
    just enough to stop one slow request blocking others, without
    over-engineering. Can make it configurable if you'd prefer.

Will push a follow-up commit nulling out the executor per your suggestion.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I genuinely don’t understand this part of the change, I’ll read the co-pilot comment on the original issue as well. So if the executor is shutdown(), can start() still be called on it? (for instance) And why a newFixedThreadPool of 2?

Pushed 8637b6d — scoped setExecutor(null) inside the existing
httpServer null-check (your suggested diff had it unconditional, which
would NPE if start() never created a server) and nulled out
httpExecutor too.

@DaanHoogland DaanHoogland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @PrashantBhanage your code looks mostly good. some small thing i want to find out before including it in the other PR.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 4.14%. Comparing base (7835881) to head (8e3694b).

❗ There is a different number of reports uploaded between BASE (7835881) and HEAD (8e3694b). Click for more details.

HEAD has 3 uploads less than BASE
Flag BASE (7835881) HEAD (8e3694b)
uitests 2 1
unittests 2 0
Additional details and impacted files
@@                          Coverage Diff                          @@
##             ghi13586-prometheusDrainage-20   #13696       +/-   ##
=====================================================================
- Coverage                             16.26%    4.14%   -12.12%     
=====================================================================
  Files                                  5667      405     -5262     
  Lines                                500732    33025   -467707     
  Branches                              60804     5898    -54906     
=====================================================================
- Hits                                  81436     1370    -80066     
+ Misses                               410189    31479   -378710     
+ Partials                               9107      176     -8931     
Flag Coverage Δ
uitests 4.14% <ø> (ø)
unittests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Scope httpServer.setExecutor(null) inside the existing httpServer
null-check to avoid a NullPointerException if start() never created
a server (e.g. exporter disabled, or the IOException path was taken).
Also null out the httpExecutor field after shutdown for defensive
hygiene, per review feedback on PR apache#13696.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants