diff --git a/CHANGELOG.md b/CHANGELOG.md index f331b45fb3a..0c2e87ed7a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ * [BUGFIX] Fix frontend parsing error on cached responses [#3759](https://github.com/grafana/tempo/pull/3759) (@mdisibio) * [BUGFIX] Fix autocomplete of a query using scoped instrinsics [#3865](https://github.com/grafana/tempo/pull/3865) (@mdisibio) * [BUGFIX] Fix metrics query histograms and quantiles on traceDuration [#3879](https://github.com/grafana/tempo/pull/3879) (@mdisibio) +* [BUGFIX] Fix divide by 0 bug in query frontend exemplar calculations [#3936](https://github.com/grafana/tempo/pull/3936) (@mdisibio) * [BUGFIX] max_global_traces_per_user: take into account ingestion.tenant_shard_size when converting to local limit [#3618](https://github.com/grafana/tempo/pull/3618) (@kvrhdn) * [BUGFIX] Fix http connection reuse on GCP and AWS by reading io.EOF through the http body. [#3760](https://github.com/grafana/tempo/pull/3760) (@bmteller) * [BUGFIX] Improved handling of complete blocks in localblocks processor after enabling flusing [#3805](https://github.com/grafana/tempo/pull/3805) (@mapno) diff --git a/modules/frontend/metrics_query_range_sharder.go b/modules/frontend/metrics_query_range_sharder.go index 942d5de776f..bc7983ed036 100644 --- a/modules/frontend/metrics_query_range_sharder.go +++ b/modules/frontend/metrics_query_range_sharder.go @@ -368,7 +368,7 @@ func (s *queryRangeSharder) buildBackendRequests(ctx context.Context, tenantID s queryHash := hashForQueryRangeRequest(&searchReq) - exemplars := s.exemplarsPerShard(uint32(len(metas))) + exemplarsPerBlock := s.exemplarsPerShard(uint32(len(metas))) for _, m := range metas { if m.EndTime.Before(m.StartTime) { // Ignore blocks with bad timings from debugging @@ -380,6 +380,13 @@ func (s *queryRangeSharder) buildBackendRequests(ctx context.Context, tenantID s continue } + exemplars := exemplarsPerBlock + if exemplars > 0 { + // Scale the number of exemplars per block to match the size + // of each sub request on this block. For very small blocks or other edge cases, return at least 1. + exemplars = max(uint32(float64(exemplars)*float64(m.TotalRecords)/float64(pages)), 1) + } + for startPage := 0; startPage < int(m.TotalRecords); startPage += pages { subR := parent.Clone(ctx) @@ -415,7 +422,7 @@ func (s *queryRangeSharder) buildBackendRequests(ctx context.Context, tenantID s Size_: m.Size, FooterSize: m.FooterSize, DedicatedColumns: dc, - Exemplars: max(exemplars/(m.TotalRecords/uint32(pages)), 1), + Exemplars: exemplars, } subR = api.BuildQueryRangeRequest(subR, queryRangeReq)