feat(proxy): query cache + Grafana API parity#2
Merged
Conversation
Add an optional in-memory query-result cache to promclick-proxy and close
the Grafana-critical Prometheus API compatibility gaps so PromClick can act
as a seamless drop-in replacement for Prometheus behind Grafana.
Query cache (new proxy/cache package): an LRU with per-entry TTL storing
pre-rendered response bytes, keyed by (query, start, end, step), with a
singleflight group collapsing concurrent identical queries into one
evaluation. Windows whose end is within cache.max_freshness of now are served
but not stored, since that data is still being written. Wires up the existing
but previously dead cache.{enabled,max_size,ttl,max_freshness} config; default
disabled, so behaviour is unchanged unless explicitly enabled.
API parity:
- /api/v1/labels and /api/v1/label/{name}/values now honour match[] (plus
best-effort start/end), so template variables like label_values(up, instance)
are scoped to the selected metric instead of returning every value in the
TSDB. POST is now accepted on label values, matching Prometheus.
- /api/v1/series parses each match[] with the Prometheus parser, applies all
label matchers, supports multiple selectors, dedupes by fingerprint, and uses
the label cache when a concrete metric name is present. Its ClickHouse
fallback now decodes the JSON-string labels column correctly.
- /api/v1/metadata is populated (one unknown-typed entry per metric) and
honours metric/limit.
All matcher values rendered into ClickHouse SQL go through chEscape, keeping
the match[]-driven paths free of SQL injection. Adds cache unit tests and the
first HTTP handler-level tests.
Signed-off-by: Sergio Rua <sergio.rua@digitalis.io>
Extend the Helm chart release workflow to trigger on v* tags in addition to main pushes. On a tag it packages every chart, versions them to the tag, pushes to GHCR, and cuts a GitHub Release with the chart tarballs attached via the gh CLI. Branch pushes keep the prior changed-charts-only behaviour. Signed-off-by: Sergio Rua <sergio.rua@digitalis.io>
rgooding
approved these changes
Jul 9, 2026
rgooding
left a comment
There was a problem hiding this comment.
Approved but I'm skeptical about the benefits of an in-memory cache of an exact PromQL query, especially in a distributed system.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
promclick-proxya seamless drop-in for Prometheus behind Grafana, and adds an optional in-memory query-result cache. Scoped to the Grafana → Prometheus API → ClickHouse query path.Query cache (new
proxy/cachepackage)(query, start, end, step).singleflightcollapses concurrent identical queries into a single evaluation (the big win under dashboard load — many panels/viewers issuing the same query).cache.max_freshnessof now are served but not stored (data still arriving).cache.{enabled,max_size,ttl,max_freshness}config. Default disabled — behaviour unchanged unless enabled.xxhashandgolang.org/x/syncwere already present (promoted indirect → direct).Grafana-critical API parity
/api/v1/labels,/api/v1/label/{name}/values: honourmatch[](plus best-effortstart/end), solabel_values(up, instance)scopes to the metric instead of returning every value in the TSDB.POSTnow accepted on label values, matching Prometheus./api/v1/series: parses eachmatch[]with the Prometheus parser, applies all label matchers, supports multiple selectors, dedupes by fingerprint, uses the label cache when a concrete metric name is present. Its ClickHouse fallback now decodes the JSON-stringlabelscolumn correctly (the old hand-rolled parser silently returned empty label sets forStringlabel columns)./api/v1/metadata: populated (oneunknown-typed entry per metric), honoursmetric/limit.Security
All matcher values rendered into ClickHouse SQL go through
chEscape(equality, inequality, regex), keeping thematch[]-driven paths free of SQL injection.Tests
proxy/cache/cache_test.go: LRU eviction, TTL expiry, singleflight de-dup, key stability, no-store path.proxy/server/handlers/parity_test.go: first HTTP handler-level tests —seriesmatcher enforcement, scopedlabel_values, scopedlabels, populatedmetadata, WHERE-builder + escaping unit tests.go build/go vet/go test ./...green in both modules;helm lintpasses.Docs
CHANGELOG, README (
cache:block + TODO),deploy/proxy.yaml, and the Helm_helpers.tpldefault config updated. Documented cache defaults matchproxy/config/proxy.go.Out of scope / notes
start/endon labels/series are accepted but not used to prune series (the label cache has no time dimension);match[]scoping is the correctness fix Grafana needs.@modifier, scalar/string result types are intentionally out of scope (per plan).staticcheckU1000 dead symbols (query.go:604,:1059, from upstream6fc3ca1) anderrcheckonbufiointermediate writes inencodeMatrix(relocated verbatim from the originalwriteMatrixResponse; errors surface at the checkedFlush()).Verify live
docker compose up -d, then compare PromClick vs the reference Prometheus datasource in Grafana (:3000):query_rangetwice →path=cache-hit, byte-identical; 20 concurrent identical → onecache-miss.label/instance/values?match[]=upreturns onlyup's instances.Assisted-by: Claude Code