Skip to content

Commit d6a7e60

Browse files
committed
Improve CostHashAgg with single-column NDV and spill-aware cost model
Two enhancements to CostHashAgg in CCostModelGPDB: 1. Single-column NDV optimization for local partial HashAgg: When GROUP BY has exactly 1 column, use GetNDVs() (global NDV from column statistics) instead of pci->Rows() to estimate the output row count of the local partial aggregation stage. GetNDVs() returns the global NDV directly, so no * UlHosts() scaling is needed. This lets the optimizer distinguish high-NDV cases (partial agg streams nearly as many rows as input, 2-phase has little benefit) from low-NDV cases (partial agg significantly reduces data before redistribution, 2-phase is preferred). Multi-column GROUP BY falls back to the original behavior: num_output_rows = pci->Rows() * UlHosts(). 2. Spill-aware cost model: When num_output_rows * width exceeds the spilling memory threshold (EcpHJSpillingMemThreshold, 50 MB), apply higher cost unit values to reflect disk I/O overhead. Uses the existing HJ spilling cost parameters (EcpHJFeedingTupColumnSpillingCostUnit etc.) which are already tuned for spilling scenarios. TPC-H benchmark: -14.3% overall (Q17 -60%, Q03 -6%). TPC-DS benchmark: -0.4% overall (Q59 -29%).
1 parent 32373aa commit d6a7e60

File tree

1 file changed

+80
-7
lines changed

1 file changed

+80
-7
lines changed

src/backend/gporca/libgpdbcost/src/CCostModelGPDB.cpp

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,35 @@ CCostModelGPDB::CostHashAgg(CMemoryPool *mp, CExpressionHandle &exprhdl,
832832
if ((COperator::EgbaggtypeLocal == popAgg->Egbaggtype()) &&
833833
popAgg->FGeneratesDuplicates())
834834
{
835-
num_output_rows = num_output_rows * pcmgpdb->UlHosts();
835+
// Use NDV of grouping columns from child statistics to estimate the
836+
// actual output rows of local partial aggregation, rather than relying
837+
// solely on GPORCA's cardinality estimate (which can be inflated after
838+
// multi-table joins).
839+
//
840+
// The local partial agg's output is bounded by the NDV of its grouping
841+
// key. GetNDVs() returns the global NDV (total across all segments),
842+
// so num_output_rows = global NDV, capped at global input rows.
843+
//
844+
// This lets the optimizer distinguish:
845+
// - High NDV (≈ input rows): partial agg streams nearly as many rows
846+
// as input → little benefit, cost approaches 1-phase.
847+
// - Low NDV (<<< input rows): partial agg significantly reduces data
848+
// before redistribution → 2-phase preferred.
849+
const CColRefArray *pdrgpcrGrpCols = popAgg->PdrgpcrGroupingCols();
850+
if (pci->Pcstats(0) != nullptr && pdrgpcrGrpCols->Size() == 1)
851+
{
852+
CColRef *colref = (*pdrgpcrGrpCols)[0];
853+
CDouble ndv = pci->Pcstats(0)->GetNDVs(colref);
854+
855+
num_output_rows =
856+
std::max(1.0,
857+
std::min(ndv.Get(),
858+
num_input_rows * pcmgpdb->UlHosts()));
859+
}
860+
else
861+
{
862+
num_output_rows = num_output_rows * pcmgpdb->UlHosts();
863+
}
836864
}
837865

838866
// get the number of grouping columns
@@ -852,9 +880,32 @@ CCostModelGPDB::CostHashAgg(CMemoryPool *mp, CExpressionHandle &exprhdl,
852880
pcmgpdb->GetCostModelParams()
853881
->PcpLookup(CCostModelParamsGPDB::EcpHashAggOutputTupWidthCostUnit)
854882
->Get();
883+
const CDouble dHashAggSpillingMemThreshold =
884+
pcmgpdb->GetCostModelParams()
885+
->PcpLookup(CCostModelParamsGPDB::EcpHJSpillingMemThreshold)
886+
->Get();
887+
const CDouble dHashAggInputTupColumnSpillingCostUnit =
888+
pcmgpdb->GetCostModelParams()
889+
->PcpLookup(
890+
CCostModelParamsGPDB::EcpHJFeedingTupColumnSpillingCostUnit)
891+
->Get();
892+
const CDouble dHashAggInputTupWidthSpillingCostUnit =
893+
pcmgpdb->GetCostModelParams()
894+
->PcpLookup(
895+
CCostModelParamsGPDB::EcpHJFeedingTupWidthSpillingCostUnit)
896+
->Get();
897+
const CDouble dHashAggOutputTupWidthSpillingCostUnit =
898+
pcmgpdb->GetCostModelParams()
899+
->PcpLookup(
900+
CCostModelParamsGPDB::EcpHJHashingTupWidthSpillingCostUnit)
901+
->Get();
855902
GPOS_ASSERT(0 < dHashAggInputTupColumnCostUnit);
856903
GPOS_ASSERT(0 < dHashAggInputTupWidthCostUnit);
857904
GPOS_ASSERT(0 < dHashAggOutputTupWidthCostUnit);
905+
GPOS_ASSERT(0 < dHashAggSpillingMemThreshold);
906+
GPOS_ASSERT(0 < dHashAggInputTupColumnSpillingCostUnit);
907+
GPOS_ASSERT(0 < dHashAggInputTupWidthSpillingCostUnit);
908+
GPOS_ASSERT(0 < dHashAggOutputTupWidthSpillingCostUnit);
858909

859910
// hashAgg cost contains three parts: build hash table, aggregate tuples, and output tuples.
860911
// 1. build hash table is correlated with the number of num_input_rows
@@ -863,12 +914,34 @@ CCostModelGPDB::CostHashAgg(CMemoryPool *mp, CExpressionHandle &exprhdl,
863914
// algorithm and thus is ignored.
864915
// 3. cost of output tuples is correlated with num_output_rows and
865916
// width of returning tuples.
866-
CCost costLocal = CCost(
867-
pci->NumRebinds() *
868-
(num_input_rows * ulGrpCols * dHashAggInputTupColumnCostUnit +
869-
num_input_rows * ulGrpCols * pci->Width() *
870-
dHashAggInputTupWidthCostUnit +
871-
num_output_rows * pci->Width() * dHashAggOutputTupWidthCostUnit));
917+
//
918+
// The hash table holds one entry per distinct group, so its memory
919+
// footprint is approximately num_output_rows * width. When this
920+
// exceeds the spilling threshold the aggregator writes batches to disk
921+
// and re-reads them, which is reflected by higher cost unit values.
922+
CCost costLocal(0);
923+
if (num_output_rows * pci->Width() <= dHashAggSpillingMemThreshold)
924+
{
925+
// groups fit in memory
926+
costLocal = CCost(
927+
pci->NumRebinds() *
928+
(num_input_rows * ulGrpCols * dHashAggInputTupColumnCostUnit +
929+
num_input_rows * ulGrpCols * pci->Width() *
930+
dHashAggInputTupWidthCostUnit +
931+
num_output_rows * pci->Width() * dHashAggOutputTupWidthCostUnit));
932+
}
933+
else
934+
{
935+
// groups spill to disk
936+
costLocal = CCost(
937+
pci->NumRebinds() *
938+
(num_input_rows * ulGrpCols *
939+
dHashAggInputTupColumnSpillingCostUnit +
940+
num_input_rows * ulGrpCols * pci->Width() *
941+
dHashAggInputTupWidthSpillingCostUnit +
942+
num_output_rows * pci->Width() *
943+
dHashAggOutputTupWidthSpillingCostUnit));
944+
}
872945
CCost costChild =
873946
CostChildren(mp, exprhdl, pci, pcmgpdb->GetCostModelParams());
874947

0 commit comments

Comments
 (0)