diff --git a/app/vmselect/promql/eval.go b/app/vmselect/promql/eval.go index 6c683f461..eeb60f723 100644 --- a/app/vmselect/promql/eval.go +++ b/app/vmselect/promql/eval.go @@ -399,7 +399,64 @@ func evalBinaryOp(qt *querytracer.Tracer, ec *EvalConfig, be *metricsql.BinaryOp return rv, nil } +func canPushdownCommonFilters(be *metricsql.BinaryOpExpr) bool { + switch strings.ToLower(be.Op) { + case "or", "default": + return false + } + if isAggrFuncWithoutGrouping(be.Left) || isAggrFuncWithoutGrouping(be.Right) { + return false + } + return true +} + +func isAggrFuncWithoutGrouping(e metricsql.Expr) bool { + afe, ok := e.(*metricsql.AggrFuncExpr) + if !ok { + return false + } + return len(afe.Modifier.Args) == 0 +} + func execBinaryOpArgs(qt *querytracer.Tracer, ec *EvalConfig, exprFirst, exprSecond metricsql.Expr, be *metricsql.BinaryOpExpr) ([]*timeseries, []*timeseries, error) { + if !canPushdownCommonFilters(be) { + // Execute exprFirst and exprSecond in parallel, since it is impossible to pushdown common filters + // from exprFirst to exprSecond. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886 + qt = qt.NewChild("execute left and right sides of %q in parallel", be.Op) + defer qt.Done() + var wg sync.WaitGroup + + var tssFirst []*timeseries + var errFirst error + qtFirst := qt.NewChild("expr1") + wg.Add(1) + go func() { + defer wg.Done() + tssFirst, errFirst = evalExpr(qtFirst, ec, exprFirst) + qtFirst.Done() + }() + + var tssSecond []*timeseries + var errSecond error + qtSecond := qt.NewChild("expr2") + wg.Add(1) + go func() { + defer wg.Done() + tssSecond, errSecond = evalExpr(qtSecond, ec, exprSecond) + qtSecond.Done() + }() + + wg.Wait() + if errFirst != nil { + return nil, nil, errFirst + } + if errSecond != nil { + return nil, nil, errFirst + } + return tssFirst, tssSecond, nil + } + // Execute binary operation in the following way: // // 1) execute the exprFirst @@ -427,15 +484,9 @@ func execBinaryOpArgs(qt *querytracer.Tracer, ec *EvalConfig, exprFirst, exprSec if err != nil { return nil, nil, err } - switch strings.ToLower(be.Op) { - case "or": - // Do not pushdown common label filters from tssFirst for `or` operation, since this can filter out the needed time series from tssSecond. - // See https://prometheus.io/docs/prometheus/latest/querying/operators/#logical-set-binary-operators for details. - default: - lfs := getCommonLabelFilters(tssFirst) - lfs = metricsql.TrimFiltersByGroupModifier(lfs, be) - exprSecond = metricsql.PushdownBinaryOpFilters(exprSecond, lfs) - } + lfs := getCommonLabelFilters(tssFirst) + lfs = metricsql.TrimFiltersByGroupModifier(lfs, be) + exprSecond = metricsql.PushdownBinaryOpFilters(exprSecond, lfs) tssSecond, err := evalExpr(qt, ec, exprSecond) if err != nil { return nil, nil, err diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index df35dcf98..f7806c460 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,8 @@ The following tip changes can be tested by building VictoriaMetrics components f ## tip +* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): execute left and right sides of certain operations in parallel. For example, `q1 or q2`, `aggr_func(q1) q2`, `q1 aggr_func(q1)`. This may improve query performance if VictoriaMetrics has enough free resources for parallel processing of both sides of the operation. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886). + * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): restart all the scrape jobs during [config reload](https://docs.victoriametrics.com/vmagent.html#configuration-update) after `global` section is changed inside `-promscrape.config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2884). * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly assume role with AWS ECS credentials. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2875). Thanks to @transacid for [the fix](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2876). * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): return series from `q1` if `q2` doesn't return matching time series in the query `q1 ifnot q2`. Previously series from `q1` weren't returned in this case.