Repository: pingcap/tidb
File: pkg/statistics/histogram.go (Go)

Code before:
		result := min(lessCountB, hg.NotNullCount()-lessCountA)
		rangeEst = DefaultRowEst(min(result, lowEqual+ndvAvg))
	}
	// LessCounts are equal only if no valid buckets or both values are out of range
	isInValidBucket := lessCountA != lessCountB
	// If values in the same bucket, use skewRatio to adjust the range estimate to account for potential skew.
	if isInValidBucket && bktIndexA == bktIndexB {
		// sctx may be nil for stats version 1
		if sctx != nil {
			skewRatio := sctx.GetSessionVars().RiskRangeSkewRatio
			sctx.GetSessionVars().RecordRelevantOptVar(vardef.TiDBOptRiskRangeSkewRatio)
			// Worst case skew is if the range includes all the rows in the bucket
			skewEstimate := hg.Buckets[bktIndexA].Count
			if bktIndexA > 0 {
				skewEstimate -= hg.Buckets[bktIndexA-1].Count
			}
			// If range does not include last value of its bucket, remove the repeat count from the skew estimate.
			if lessCountB <= float64(hg.Buckets[bktIndexA].Count-hg.Buckets[bktIndexA].Repeat) {
				skewEstimate -= hg.Buckets[bktIndexA].Repeat
			}
			if skewRatio > 0 {
				// Cap the max estimate to 2X the estimate.
				// TODO: RiskRangeSkewRatio is predominantly used for outOfRangeRowCount, and
				//       it's usage is diluated here. Consider how to address this if
				//       issues are reported here regarding under-estimation for in-bucket ranges.
				rangeEst = CalculateSkewRatioCounts(rangeEst.Est, rangeEst.Est*2, skewRatio)
			}
			// Report the full max estimate for risk estimation usage in compareCandidates (skylinePruning).
			rangeEst.MaxEst = max(rangeEst.MaxEst, float64(skewEstimate))
		}
	}
	return rangeEst
}

// CalculateSkewRatioCounts calculates the default, min, and max skew estimates given a skew ratio.
func CalculateSkewRatioCounts(estimate, skewEstimate, skewRatio float64) RowEstimate {
	skewDiff := skewEstimate - estimate
	// Add a "ratio" of the skewEstimate to adjust the default row estimate.
	skewAmt := max(0, skewDiff*skewRatio)
	maxSkewAmt := min(skewDiff, 2*skewAmt)
	return RowEstimate{estimate + skewAmt, estimate, estimate + maxSkewAmt}
}

// RowEstimate stores the min, default, and max row count estimates.
type RowEstimate struct {
	Est    float64
	MinEst float64
	MaxEst float64
}

// DefaultRowEst returns a RowEstimate with same value for all three fields

Review comment (suggestion):
`CalculateSkewRatioCounts(rangeEst.Est, rangeEst.Est*2, skewRatio)` can inflate the in-bucket estimate beyond the bucket’s maximum possible row count when the range already covers more than half of the bucket (because `rangeEst.Est*2` may exceed `skewEstimate`). To keep the estimate physically plausible while still capping impact, cap the second argument with the computed `skewEstimate` (e.g., use `min(rangeEst.Est*2, float64(skewEstimate))`).
```suggestion
				// Cap the max estimate to 2X the estimate, but not beyond the bucket's maximum possible row count.
				// TODO: RiskRangeSkewRatio is predominantly used for outOfRangeRowCount, and
				//       it's usage is diluated here. Consider how to address this if
				//       issues are reported here regarding under-estimation for in-bucket ranges.
				rangeEst = CalculateSkewRatioCounts(rangeEst.Est, min(rangeEst.Est*2, float64(skewEstimate)), skewRatio)
```

Code after:
		result := min(lessCountB, hg.NotNullCount()-lessCountA)
		rangeEst = DefaultRowEst(min(result, lowEqual+ndvAvg))
	}
	// LessCounts are equal only if no valid buckets or both values are out of range
	isInValidBucket := lessCountA != lessCountB
	// If values in the same bucket, use skewRatio to adjust the range estimate to account for potential skew.
	if isInValidBucket && bktIndexA == bktIndexB {
		// sctx may be nil for stats version 1
		if sctx != nil {
			skewRatio := sctx.GetSessionVars().RiskRangeSkewRatio
			sctx.GetSessionVars().RecordRelevantOptVar(vardef.TiDBOptRiskRangeSkewRatio)
			// Worst case skew is if the range includes all the rows in the bucket
			skewEstimate := hg.Buckets[bktIndexA].Count
			if bktIndexA > 0 {
				skewEstimate -= hg.Buckets[bktIndexA-1].Count
			}
			// If range does not include last value of its bucket, remove the repeat count from the skew estimate.
			if lessCountB <= float64(hg.Buckets[bktIndexA].Count-hg.Buckets[bktIndexA].Repeat) {
				skewEstimate -= hg.Buckets[bktIndexA].Repeat
			}
			if skewRatio > 0 {
				// Cap the max estimate to 2X the estimate, but not beyond the bucket's maximum possible row count.
				// TODO: RiskRangeSkewRatio is predominantly used for outOfRangeRowCount, and
				//       it's usage is diluted here. Consider how to address this if
				//       issues are reported here regarding under-estimation for in-bucket ranges.
				rangeEst = CalculateSkewRatioCounts(rangeEst.Est, min(rangeEst.Est*2, float64(skewEstimate)), skewRatio)
			}
			// Report the full max estimate for risk estimation usage in compareCandidates (skylinePruning).
			rangeEst.MaxEst = max(rangeEst.MaxEst, float64(skewEstimate))
		}
	}
	return rangeEst
}

// CalculateSkewRatioCounts calculates the default, min, and max skew estimates given a skew ratio.
func CalculateSkewRatioCounts(estimate, skewEstimate, skewRatio float64) RowEstimate {
	skewDiff := skewEstimate - estimate
	// Add a "ratio" of the skewEstimate to adjust the default row estimate.
	skewAmt := max(0, skewDiff*skewRatio)
	maxSkewAmt := min(skewDiff, 2*skewAmt)
	return RowEstimate{estimate + skewAmt, estimate, estimate + maxSkewAmt}
}

// RowEstimate stores the min, default, and max row count estimates.
type RowEstimate struct {
	Est    float64
	MinEst float64
	MaxEst float64
}

// DefaultRowEst returns a RowEstimate with same value for all three fields