Promql label functions - #155688
Draft
eyalkoren wants to merge 2 commits into
Draft
Conversation
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.
Closes #136256
Adds the PromQL metadata-manipulation functions
label_replaceandlabel_join, lowering them into the ES|QL time-series pipeline. This is intentionally a draft: it implements the functions by manipulating the series identity (_timeseries) blob, which leaves twowithout-related cases diverging from Prometheus. v1 will avoid_timeseriesmanipulation entirely, so this PR is kept as the reference for the full, identity-manipulating approach if we choose to pursue it later.The core challenge:
_timeseriesidentityLabel functions change a series' identity (its label set). In ES|QL a series identity is carried as an opaque
_timeseriesblob, and adding/renaming/deleting a label on a vector that must retain its identity means writing into that blob (PromqlSetLabel). Two properties of the model make this hard:without(x)is lowered to askipFieldNamesfilter on the block loader that assembles the_timeseriesblob. That blob is built purely from the index's stored dimension fields (mappingLookup.dimensionFieldMappers()), so a label derived bylabel_replace/label_join— which has no dimension mapper — is never in the candidate set, and excluding it is a no-op.label_replacethen writes the derived label into the already-built blob later in the compute pipeline, after the only exclusion step has run._timeseries, downstream stages group by the blob as-is; there is no cheap way to drop a single derived label back out of it.By contrast,
by(...)and bare aggregation never touch the blob: the derived label is materialized as a concrete column used directly as a grouping key (by), or the identity is dropped and series collapse (bare). Those paths behave correctly; the paths that require blob manipulation are where behavior diverges.Implementation
Analysis.
label_replaceandlabel_joinare PromQL built-ins (PromqlBuiltinFunctionDefinitions/PromqlFunctionRegistry,FunctionType.METADATA_MANIPULATION). Rather than the generic PromQL function builder,ResolvePromqlFunctionsresolves them to a dedicated logical node,MetadataManipulationFunction. Because the destination label is brand-new (absent from the index mapping), the node mints a stabledestinationattribute once — theGrok/Dissectgenerated-attribute pattern — and the analyzer adds it to the PromQL resolution scope, so an enclosingby(dst)/KEEP dstbinds to the derived label exactly like a stored one.Translation (
TranslatePromqlToEsqlPlan#translateMetadataManipulation) lowers the node to ES|QL primitives:without/ group-all) keep the opaque_timeseriesblob; an enclosingbydrops_timeseriesand consumes the destination as a concrete column._tsid+ time bucket) when the child is a raw vector, exposing the source-label columns the derivation reads.label_replace→PromqlRegexExtract, an internal scalar using RE2/J for byte-for-byte Prometheus parity (RE2 syntax,(?P<name>), no backreferences) with GoExpandreplacement ($1,${name});label_join→ aCONCAT-based join of the source labels with the separator.The derived value encodes Prometheus's three outcomes: set (non-empty), delete (matched-but-empty →
""), no-op (no match →null, leaving any pre-existing destination untouched)._timeseriesblob viaPromqlSetLabel. That scalar rewrites the blob's single passthrough namespace (labelsfor Prometheus data,attributesfor OTel, with__name__special-cased) and re-emits keys in canonical sorted order, so two series whose label sets become equal serialize to byte-identical blobs and collapse on the same key.PromqlCollisionCheckat the seam (before any outer aggregate), keyed on the exposed identity plus the time bucket. Two distinct series mapped onto the same identity within a bucket fail the query with PromQL's "vector cannot contain metrics with the same labelset" error instead of silently merging. The check is a coordinator-only artifact (PromqlCollisionCheckExec/PromqlCollisionCheckOperator) and is never serialized.Compatibility. The two internal scalars (
PromqlRegexExtract,PromqlSetLabel) are versioned writeables gated by thepromql_label_functionstransport version; RE2/J is added as a new dependency (licenses included).Not yet supported
Two deviations from Prometheus, verified by execution and captured as intentionally-failing tests in
x-pack/plugin/esql/src/internalClusterTest/.../action/PromqlLabelFunctionsUnsupportedIT.java(left red on purpose — they document the gaps):withouton a derived label — e.g.sum without (region) (label_replace(m, "region", "$1", "pod", "(.+)")). Prometheus dropsregion; here it survives in the identity, because the exclusion is applied as askipFieldNamesfilter when the block loader builds the blob from stored dimensions, where the derived label does not exist.without ()over a relabel — Prometheus groups by every label, so all distinct series survive; here the_timeseriesidentity is dropped and distinct series collapse into one.What is handled: the relabel collision guard (two series relabeled onto the same labelset within a bucket → PromQL "vector cannot contain metrics with the same labelset" error). Those cases were checked and behave as Prometheus does, including in-place delete/overwrite and nested relabels.
Why it's a draft, and the v1 direction
All the divergences stem from
_timeseriesmanipulation, so v1 will not supportwithout. The open question for v1 is whetherlabel_replace/label_joincan be supported for the non-withoutgrouping modes — bare (NONE) andby-labels — without any_timeseriesmanipulation, since those already surface the derived label as a concrete column rather than writing it into the blob. This PR remains open as the reference implementation for the full approach should we later decide to manipulate_timeseries.