Repository: kubernetes/kubernetes (language: go)

Issue:
PodTopologySpread plugin with multiple constraints with the same topology key incorrectly counts matching pods on node
### What happened?

In PodTopologySpread PreFilter, while processing the nodes, their count is not aggregated, but overwritten by the last constraint with the same topologyKey:
https://github.com/kubernetes/kubernetes/blob/810e9e212ec5372d16b655f57b9231d8654a2179/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go#L276-L285

Probably, PreScore counting is also wrong, as it probably duplicate the pods:
https://github.com/kubernetes/kubernetes/blob/810e9e212ec5372d16b655f57b9231d8654a2179/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go#L164-L180

### What did you expect to happen?

`countPodsMatchSelector` should process all constraints per topologyKey at once to aggregate, but not duplicate the counts for pods on node.

### How can we reproduce it (as minimally and precisely as possible)?

I found the issue refactoring the plugin's code, but probably unexpected behavior of topology spreading will happen when defining multiple topology spread constraints with the same topologyKey on a single pod.

### Anything else we need to know?

/sig scheduling
/assign

### Kubernetes version

<details>

```console
$ kubectl version
# paste output here
```

</details>


### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>



Discussion / hints:
This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

Fix patch:
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/common.go b/pkg/scheduler/framework/plugins/podtopologyspread/common.go
index 6f5898fe4e378..5896aad65ff83 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/common.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/common.go
@@ -27,11 +27,6 @@ import (
 	"k8s.io/utils/ptr"
 )
 
-type topologyPair struct {
-	key   string
-	value string
-}
-
 // topologySpreadConstraint is an internal version for v1.TopologySpreadConstraint
 // and where the selector is parsed.
 // Fields are exported for comparison during testing.
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
index 11322a4cc2dce..0d95c16ba6733 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go
@@ -19,6 +19,7 @@ package podtopologyspread
 import (
 	"context"
 	"fmt"
+	"maps"
 	"math"
 
 	v1 "k8s.io/api/core/v1"
@@ -31,7 +32,7 @@ import (
 const preFilterStateKey = "PreFilter" + Name
 
 // preFilterState computed at PreFilter and used at Filter.
-// It combines TpKeyToCriticalPaths and TpPairToMatchNum to represent:
+// It combines CriticalPaths and TpValueToMatchNum to represent:
 // (1) critical paths where the least pods are matched on each spread constraint.
 // (2) number of pods matched on each spread constraint.
 // A nil preFilterState denotes it's not set at all (in PreFilter phase);
@@ -39,29 +40,23 @@ const preFilterStateKey = "PreFilter" + Name
 // Fields are exported for comparison during testing.
 type preFilterState struct {
 	Constraints []topologySpreadConstraint
-	// We record 2 critical paths instead of all critical paths here.
-	// criticalPaths[0].MatchNum always holds the minimum matching number.
-	// criticalPaths[1].MatchNum is always greater or equal to criticalPaths[0].MatchNum, but
+	// CriticalPaths is a slice indexed by constraint index.
+	// Per each entry, we record 2 critical paths instead of all critical paths.
+	// CriticalPaths[i][0].MatchNum always holds the minimum matching number.
+	// CriticalPaths[i][1].MatchNum is always greater or equal to CriticalPaths[i][0].MatchNum, but
 	// it's not guaranteed to be the 2nd minimum match number.
-	TpKeyToCriticalPaths map[string]*criticalPaths
-	// TpKeyToDomainsNum is keyed with topologyKey, and valued with the number of domains.
-	TpKeyToDomainsNum map[string]int
-	// TpPairToMatchNum is keyed with topologyPair, and valued with the number of matching pods.
-	TpPairToMatchNum map[topologyPair]int
+	CriticalPaths []*criticalPaths
+	// TpValueToMatchNum is a slice indexed by constraint index.
+	// Each entry is keyed with topology value, and valued with the number of matching pods.
+	TpValueToMatchNum []map[string]int
 }
 
 // minMatchNum returns the global minimum for the calculation of skew while taking MinDomains into account.
-func (s *preFilterState) minMatchNum(tpKey string, minDomains int32) (int, error) {
-	paths, ok := s.TpKeyToCriticalPaths[tpKey]
-	if !ok {
-		return 0, fmt.Errorf("failed to retrieve path by topology key")
-	}
+func (s *preFilterState) minMatchNum(constraintID int, minDomains int32) (int, error) {
+	paths := s.CriticalPaths[constraintID]
 
 	minMatchNum := paths[0].MatchNum
-	domainsNum, ok := s.TpKeyToDomainsNum[tpKey]
-	if !ok {
-		return 0, fmt.Errorf("failed to retrieve the number of domains by topology key")
-	}
+	domainsNum := len(s.TpValueToMatchNum[constraintID])
 
 	if domainsNum < int(minDomains) {
 		// When the number of eligible domains with matching topology keys is less than `minDomains`,
@@ -79,17 +74,15 @@ func (s *preFilterState) Clone() framework.StateData {
 	}
 	copy := preFilterState{
 		// Constraints are shared because they don't change.
-		Constraints:          s.Constraints,
-		TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(s.TpKeyToCriticalPaths)),
-		// The number of domains does not change as a result of AddPod/RemovePod methods on PreFilter Extensions
-		TpKeyToDomainsNum: s.TpKeyToDomainsNum,
-		TpPairToMatchNum:  make(map[topologyPair]int, len(s.TpPairToMatchNum)),
+		Constraints:       s.Constraints,
+		CriticalPaths:     make([]*criticalPaths, len(s.CriticalPaths)),
+		TpValueToMatchNum: make([]map[string]int, len(s.TpValueToMatchNum)),
 	}
-	for tpKey, paths := range s.TpKeyToCriticalPaths {
-		copy.TpKeyToCriticalPaths[tpKey] = &criticalPaths{paths[0], paths[1]}
+	for i, paths := range s.CriticalPaths {
+		copy.CriticalPaths[i] = &criticalPaths{paths[0], paths[1]}
 	}
-	for tpPair, matchNum := range s.TpPairToMatchNum {
-		copy.TpPairToMatchNum[tpPair] = matchNum
+	for i, tpMap := range s.TpValueToMatchNum {
+		copy.TpValueToMatchNum[i] = maps.Clone(tpMap)
 	}
 	return &copy
 }
@@ -200,7 +193,7 @@ func (pl *PodTopologySpread) updateWithPod(s *preFilterState, updatedPod, preemp
 	}
 
 	podLabelSet := labels.Set(updatedPod.Labels)
-	for _, constraint := range s.Constraints {
+	for i, constraint := range s.Constraints {
 		if !constraint.Selector.Matches(podLabelSet) {
 			continue
 		}
@@ -210,10 +203,9 @@ func (pl *PodTopologySpread) updateWithPod(s *preFilterState, updatedPod, preemp
 			continue
 		}
 
-		k, v := constraint.TopologyKey, node.Labels[constraint.TopologyKey]
-		pair := topologyPair{key: k, value: v}
-		s.TpPairToMatchNum[pair] += delta
-		s.TpKeyToCriticalPaths[k].update(v, s.TpPairToMatchNum[pair])
+		v := node.Labels[constraint.TopologyKey]
+		s.TpValueToMatchNum[i][v] += delta
+		s.CriticalPaths[i].update(v, s.TpValueToMatchNum[i][v])
 	}
 }
 
@@ -232,6 +224,12 @@ func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error
 	return s, nil
 }
 
+type topologyCount struct {
+	topologyValue string
+	constraintID  int
+	count         int
+}
+
 // calPreFilterState computes preFilterState describing how pods are spread on topologies.
 func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) (*preFilterState, error) {
 	constraints, err := pl.getConstraints(pod)
@@ -248,15 +246,18 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod)
 	}
 
 	s := preFilterState{
-		Constraints:          constraints,
-		TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(constraints)),
-		TpPairToMatchNum:     make(map[topologyPair]int, sizeHeuristic(len(allNodes), constraints)),
+		Constraints:       constraints,
+		CriticalPaths:     make([]*criticalPaths, len(constraints)),
+		TpValueToMatchNum: make([]map[string]int, len(constraints)),
+	}
+	for i := 0; i < len(constraints); i++ {
+		s.TpValueToMatchNum[i] = make(map[string]int, sizeHeuristic(len(allNodes), constraints[i]))
 	}
 
-	tpCountsByNode := make([]map[topologyPair]int, len(allNodes))
+	tpCountsByNode := make([][]topologyCount, len(allNodes))
 	requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod)
-	processNode := func(i int) {
-		nodeInfo := allNodes[i]
+	processNode := func(n int) {
+		nodeInfo := allNodes[n]
 		node := nodeInfo.Node()
 
 		if !pl.enableNodeInclusionPolicyInPodTopologySpread {
@@ -272,38 +273,39 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod)
 			return
 		}
 
-		tpCounts := make(map[topologyPair]int, len(constraints))
-		for _, c := range constraints {
+		tpCounts := make([]topologyCount, 0, len(constraints))
+		for i, c := range constraints {
 			if pl.enableNodeInclusionPolicyInPodTopologySpread &&
 				!c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) {
 				continue
 			}
 
-			pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]}
+			value := node.Labels[c.TopologyKey]
 			count := countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace)
-			tpCounts[pair] = count
+			tpCounts = append(tpCounts, topologyCount{
+				topologyValue: value,
+				constraintID:  i,
+				count:         count,
+			})
 		}
-		tpCountsByNode[i] = tpCounts
+		tpCountsByNode[n] = tpCounts
 	}
 	pl.parallelizer.Until(ctx, len(allNodes), processNode, pl.Name())
 
 	for _, tpCounts := range tpCountsByNode {
-		for tp, count := range tpCounts {
-			s.TpPairToMatchNum[tp] += count
+		// tpCounts might not hold all the constraints, so index can't be used here as constraintID.
+		for _, tpCount := range tpCounts {
+			s.TpValueToMatchNum[tpCount.constraintID][tpCount.topologyValue] += tpCount.count
 		}
 	}
-	s.TpKeyToDomainsNum = make(map[string]int, len(constraints))
-	for tp := range s.TpPairToMatchNum {
-		s.TpKeyToDomainsNum[tp.key]++
-	}
 
-	// calculate min match for each topology pair
+	// calculate min match for each constraint and topology value
 	for i := 0; i < len(constraints); i++ {
-		key := constraints[i].TopologyKey
-		s.TpKeyToCriticalPaths[key] = newCriticalPaths()
-	}
-	for pair, num := range s.TpPairToMatchNum {
-		s.TpKeyToCriticalPaths[pair.key].update(pair.value, num)
+		s.CriticalPaths[i] = newCriticalPaths()
+
+		for value, num := range s.TpValueToMatchNum[i] {
+			s.CriticalPaths[i].update(value, num)
+		}
 	}
 
 	return &s, nil
@@ -325,7 +327,7 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C
 
 	logger := klog.FromContext(ctx)
 	podLabelSet := labels.Set(pod.Labels)
-	for _, c := range s.Constraints {
+	for i, c := range s.Constraints {
 		tpKey := c.TopologyKey
 		tpVal, ok := node.Labels[c.TopologyKey]
 		if !ok {
@@ -335,9 +337,9 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C
 
 		// judging criteria:
 		// 'existing matching num' + 'if self-match (1 or 0)' - 'global minimum' <= 'maxSkew'
-		minMatchNum, err := s.minMatchNum(tpKey, c.MinDomains)
+		minMatchNum, err := s.minMatchNum(i, c.MinDomains)
 		if err != nil {
-			logger.Error(err, "Internal error occurred while retrieving value precalculated in PreFilter", "topologyKey", tpKey, "paths", s.TpKeyToCriticalPaths)
+			logger.Error(err, "Internal error occurred while retrieving value precalculated in PreFilter", "topologyKey", tpKey, "paths", s.CriticalPaths[i])
 			continue
 		}
 
@@ -346,11 +348,7 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C
 			selfMatchNum = 1
 		}
 
-		pair := topologyPair{key: tpKey, value: tpVal}
-		matchNum := 0
-		if tpCount, ok := s.TpPairToMatchNum[pair]; ok {
-			matchNum = tpCount
-		}
+		matchNum := s.TpValueToMatchNum[i][tpVal]
 		skew := matchNum + selfMatchNum - minMatchNum
 		if skew > int(c.MaxSkew) {
 			logger.V(5).Info("Node failed spreadConstraint: matchNum + selfMatchNum - minMatchNum > maxSkew", "node", klog.KObj(node), "topologyKey", tpKey, "matchNum", matchNum, "selfMatchNum", selfMatchNum, "minMatchNum", minMatchNum, "maxSkew", c.MaxSkew)
@@ -361,11 +359,9 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C
 	return nil
 }
 
-func sizeHeuristic(nodes int, constraints []topologySpreadConstraint) int {
-	for _, c := range constraints {
-		if c.TopologyKey == v1.LabelHostname {
-			return nodes
-		}
+func sizeHeuristic(nodes int, constraint topologySpreadConstraint) int {
+	if constraint.TopologyKey == v1.LabelHostname {
+		return nodes
 	}
 	return 0
 }
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go b/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go
index b377ce6a5171d..d18dd3b6a9bb1 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go
@@ -37,8 +37,9 @@ type preScoreState struct {
 	Constraints []topologySpreadConstraint
 	// IgnoredNodes is a set of node names which miss some Constraints[*].topologyKey.
 	IgnoredNodes sets.Set[string]
-	// TopologyPairToPodCounts is keyed with topologyPair, and valued with the number of matching pods.
-	TopologyPairToPodCounts map[topologyPair]*int64
+	// TopologyValueToPodCounts is a slice indexed by constraint index.
+	// Each entry is keyed with topology value, and valued with the number of matching pods.
+	TopologyValueToPodCounts []map[string]*int64
 	// TopologyNormalizingWeight is the weight we give to the counts per topology.
 	// This allows the pod counts of smaller topologies to not be watered down by
 	// bigger ones.
@@ -76,6 +77,10 @@ func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, fi
 	if len(s.Constraints) == 0 {
 		return nil
 	}
+	s.TopologyValueToPodCounts = make([]map[string]*int64, len(s.Constraints))
+	for i := 0; i < len(s.Constraints); i++ {
+		s.TopologyValueToPodCounts[i] = make(map[string]*int64)
+	}
 	topoSize := make([]int, len(s.Constraints))
 	for _, node := range filteredNodes {
 		if requireAllTopologies && !nodeLabelsMatchSpreadConstraints(node.Node().Labels, s.Constraints) {
@@ -89,9 +94,9 @@ func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, fi
 			if constraint.TopologyKey == v1.LabelHostname {
 				continue
 			}
-			pair := topologyPair{key: constraint.TopologyKey, value: node.Node().Labels[constraint.TopologyKey]}
-			if s.TopologyPairToPodCounts[pair] == nil {
-				s.TopologyPairToPodCounts[pair] = new(int64)
+			value := node.Node().Labels[constraint.TopologyKey]
+			if s.TopologyValueToPodCounts[i][value] == nil {
+				s.TopologyValueToPodCounts[i][value] = new(int64)
 				topoSize[i]++
 			}
 		}
@@ -126,8 +131,7 @@ func (pl *PodTopologySpread) PreScore(
 	}
 
 	state := &preScoreState{
-		IgnoredNodes:            sets.New[string](),
-		TopologyPairToPodCounts: make(map[topologyPair]*int64),
+		IgnoredNodes: sets.New[string](),
 	}
 	// Only require that nodes have all the topology labels if using
 	// non-system-default spreading rules. This allows nodes that don't have a
@@ -145,8 +149,8 @@ func (pl *PodTopologySpread) PreScore(
 
 	// Ignore parsing errors for backwards compatibility.
 	requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod)
-	processAllNode := func(i int) {
-		nodeInfo := allNodes[i]
+	processAllNode := func(n int) {
+		nodeInfo := allNodes[n]
 		node := nodeInfo.Node()
 
 		if !pl.enableNodeInclusionPolicyInPodTopologySpread {
@@ -161,17 +165,17 @@ func (pl *PodTopologySpread) PreScore(
 			return
 		}
 
-		for _, c := range state.Constraints {
+		for i, c := range state.Constraints {
 			if pl.enableNodeInclusionPolicyInPodTopologySpread &&
 				!c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) {
 				continue
 			}
 
-			pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]}
+			value := node.Labels[c.TopologyKey]
 			// If current topology pair is not associated with any candidate node,
 			// continue to avoid unnecessary calculation.
 			// Per-node counts are also skipped, as they are done during Score.
-			tpCount := state.TopologyPairToPodCounts[pair]
+			tpCount := state.TopologyValueToPodCounts[i][value]
 			if tpCount == nil {
 				continue
 			}
@@ -214,8 +218,7 @@ func (pl *PodTopologySpread) Score(ctx context.Context, cycleState *framework.Cy
 			if c.TopologyKey == v1.LabelHostname {
 				cnt = int64(countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace))
 			} else {
-				pair := topologyPair{key: c.TopologyKey, value: tpVal}
-				cnt = *s.TopologyPairToPodCounts[pair]
+				cnt = *s.TopologyValueToPodCounts[i][tpVal]
 			}
 			score += scoreForCount(cnt, c.MaxSkew, s.TopologyNormalizingWeight[i])
 		}


Test patch:
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
index 13fa8011929ff..e007f75abcbda 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go
@@ -105,14 +105,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 0}, {"zone2", 0}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 0,
-					{key: "zone", value: "zone2"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 0}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 0,
+					"zone2": 0,
+				}},
 			},
 		},
 		{
@@ -144,14 +141,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 2}, {"zone1", 3}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 3,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 3,
+					"zone2": 2,
+				}},
 			},
 		},
 		{
@@ -183,14 +177,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 0}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 0,
-					{key: "zone", value: "zone2"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 0,
+					"zone2": 0,
+				}},
 			},
 		},
 		{
@@ -224,15 +215,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone3", 0}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 3,
-					{key: "zone", value: "zone2"}: 2,
-					{key: "zone", value: "zone3"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone3", 0}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 3,
+					"zone2": 2,
+					"zone3": 0,
+				}},
 			},
 		},
 		{
@@ -264,14 +252,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {"zone1", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 2,
-					{key: "zone", value: "zone2"}: 1,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 2,
+					"zone2": 1,
+				}},
 			},
 		},
 		{
@@ -314,18 +299,18 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 4},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 3}, {"zone2", 4}},
-					"node": {{"node-x", 0}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  4,
-					{key: "node", value: "node-a"}: 2,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-x"}: 0,
-					{key: "node", value: "node-y"}: 4,
+				CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-x", 0}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 4,
+					},
+					{
+						"node-a": 2,
+						"node-b": 1,
+						"node-x": 0,
+						"node-y": 4,
+					},
 				},
 			},
 		},
@@ -370,17 +355,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 3}, {"zone2", 4}},
-					"node": {{"node-b", 1}, {"node-a", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  4,
-					{key: "node", value: "node-a"}: 2,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-y"}: 4,
+				CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 1}, {"node-a", 2}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 4,
+					},
+					{
+						"node-a": 2,
+						"node-b": 1,
+						"node-y": 4,
+					},
 				},
 			},
 		},
@@ -418,17 +403,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 1}},
-					"node": {{"node-a", 0}, {"node-y", 0}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  1,
-					{key: "zone", value: "zone2"}:  0,
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-y"}: 0,
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}, {{"node-a", 0}, {"node-y", 0}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 1,
+						"zone2": 0,
+					},
+					{
+						"node-a": 0,
+						"node-b": 1,
+						"node-y": 0,
+					},
 				},
 			},
 		},
@@ -471,17 +456,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 3}, {"zone2", 4}},
-					"node": {{"node-b", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  4,
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 0,
-					{key: "node", value: "node-y"}: 2,
+				CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 4,
+					},
+					{
+						"node-a": 1,
+						"node-b": 0,
+						"node-y": 2,
+					},
 				},
 			},
 		},
@@ -526,17 +511,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 3}, {"zone2", 4}},
-					"node": {{"node-b", 1}, {"node-a", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  4,
-					{key: "node", value: "node-a"}: 2,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-y"}: 4,
+				CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 1}, {"node-a", 2}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 4,
+					},
+					{
+						"node-a": 2,
+						"node-b": 1,
+						"node-y": 4,
+					},
 				},
 			},
 		},
@@ -570,12 +555,8 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": newCriticalPaths(),
-					"rack": newCriticalPaths(),
-				},
-				TpPairToMatchNum: make(map[topologyPair]int),
+				CriticalPaths:     []*criticalPaths{newCriticalPaths(), newCriticalPaths()},
+				TpValueToMatchNum: []map[string]int{{}, {}},
 			},
 		},
 		{
@@ -612,11 +593,8 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": newCriticalPaths(),
-				},
-				TpPairToMatchNum: make(map[topologyPair]int),
+				CriticalPaths:     []*criticalPaths{newCriticalPaths()},
+				TpValueToMatchNum: []map[string]int{{}},
 			},
 		},
 		{
@@ -670,21 +648,18 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 3}, {"zone2", 4}},
-					"node": {{"node-x", 0}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  4,
-					{key: "node", value: "node-a"}: 2,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-x"}: 0,
-					{key: "node", value: "node-y"}: 4,
-				},
-				TpKeyToDomainsNum: map[string]int{
-					"zone": 2,
-					"node": 4,
+				CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-x", 0}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 4,
+					},
+					{
+						"node-a": 2,
+						"node-b": 1,
+						"node-x": 0,
+						"node-y": 4,
+					},
 				},
 			},
 		},
@@ -716,14 +691,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 1}, {"node-b", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -755,14 +727,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 1}, {"node-b", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -794,15 +763,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-c", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-c"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+					"node-c": 0,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -834,14 +800,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 1}, {"node-b", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -873,15 +836,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-c", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-c"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+					"node-c": 0,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -912,15 +872,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-c", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-c"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+					"node-c": 0,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -951,15 +908,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-c", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-c"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+					"node-c": 0,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -990,14 +944,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyHonor,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 1}, {"node-b", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -1029,15 +980,12 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyHonor,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-c", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-c"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 2,
+					"node-c": 0,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -1077,17 +1025,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 0}, {"zone2", 1}},
-					"node": {{"node-a", 0}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  0,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-x"}: 1,
+				CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-a", 0}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 0,
+						"zone2": 1,
+					},
+					{
+						"node-a": 0,
+						"node-b": 2,
+						"node-x": 1,
+					},
 				},
 			},
 			enableNodeInclusionPolicy: true,
@@ -1127,17 +1075,17 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 0}, {"zone2", 1}},
-					"node": {{"node-a", 0}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  0,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-x"}: 1,
+				CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-a", 0}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 0,
+						"zone2": 1,
+					},
+					{
+						"node-a": 0,
+						"node-b": 2,
+						"node-x": 1,
+					},
 				},
 			},
 			enableNodeInclusionPolicy: true,
@@ -1180,16 +1128,16 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 0}, {"zone2", 1}},
-					"node": {{"node-b", 0}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  0,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-b"}: 0,
-					{key: "node", value: "node-x"}: 1,
+				CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-b", 0}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 0,
+						"zone2": 1,
+					},
+					{
+						"node-b": 0,
+						"node-x": 1,
+					},
 				},
 			},
 			enableNodeInclusionPolicy: true,
@@ -1233,16 +1181,16 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyHonor,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 2}, {"zone2", 3}},
-					"node": {{"node-y", 1}, {"node-x", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  2,
-					{key: "zone", value: "zone2"}:  3,
-					{key: "node", value: "node-x"}: 2,
-					{key: "node", value: "node-y"}: 1,
+				CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 3}}, {{"node-y", 1}, {"node-x", 2}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 2,
+						"zone2": 3,
+					},
+					{
+						"node-x": 2,
+						"node-y": 1,
+					},
 				},
 			},
 			enableNodeInclusionPolicy: true,
@@ -1276,14 +1224,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 2}, {"zone1", 3}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 3,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 3,
+					"zone2": 2,
+				}},
 			},
 			enableMatchLabelKeys: false,
 		},
@@ -1316,14 +1261,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {"zone1", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 1,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 1,
+				}},
 			},
 			enableMatchLabelKeys: true,
 		},
@@ -1356,14 +1298,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 2}, {"zone1", 3}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 3,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 3,
+					"zone2": 2,
+				}},
 			},
 			enableMatchLabelKeys: true,
 		},
@@ -1396,14 +1335,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 0}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 0,
-					{key: "zone", value: "zone2"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 0,
+					"zone2": 0,
+				}},
 			},
 			enableMatchLabelKeys: true,
 		},
@@ -1436,14 +1372,11 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 0}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 0,
-					{key: "zone", value: "zone2"}: 0,
-				},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 0,
+					"zone2": 0,
+				}},
 			},
 		},
 		{
@@ -1475,16 +1408,64 @@ func TestPreFilterState(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 2}, {"zone1", 3}},
+				CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 3,
+					"zone2": 2,
+				}},
+			},
+			enableMatchLabelKeys: true,
+		},
+		{
+			name: "two spreadConstraints with the same topologyKey",
+			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
+				Obj(),
+			nodes: []*v1.Node{
+				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
+				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
+				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
+				st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
+			},
+			existingPods: []*v1.Pod{
+				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
+				st.MakePod().Name("p-x1").Node("node-x").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y2").Node("node-y").Label("bar", "").Obj(),
+			},
+			want: &preFilterState{
+				Constraints: []topologySpreadConstraint{
+					{
+						MaxSkew:            1,
+						TopologyKey:        "zone",
+						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
+						MinDomains:         1,
+						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
+						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
+					},
+					{
+						MaxSkew:            1,
+						TopologyKey:        "zone",
+						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
+						MinDomains:         1,
+						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
+						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
+					},
 				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 3,
-					{key: "zone", value: "zone2"}: 2,
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}, {{"zone1", 1}, {"zone2", 3}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 2,
+						"zone2": 1,
+					},
+					{
+						"zone1": 1,
+						"zone2": 3,
+					},
 				},
 			},
-			enableMatchLabelKeys: true,
 		},
 		{
 			name: "skip if not specified",
@@ -1564,15 +1545,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-b", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 0,
-				},
+				Constraints:   []topologySpreadConstraint{nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"node-b", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 0,
+				}},
 			},
 		},
 		{
@@ -1590,15 +1568,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 1}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 1,
+					"node-b": 1,
+				}},
 			},
 		},
 		{
@@ -1616,15 +1591,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 0}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"node-a", 0}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 0,
+					"node-b": 1,
+				}},
 			},
 		},
 		{
@@ -1642,15 +1614,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"node": {{"node-a", 0}, {"node-b", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"node-a", 0}, {"node-b", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"node-a": 0,
+					"node-b": 2,
+				}},
 			},
 		},
 		{
@@ -1667,17 +1636,17 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint, nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 1}},
-					"node": {{"node-x", 0}, {"node-a", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  1,
-					{key: "zone", value: "zone2"}:  0,
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-x"}: 0,
+				Constraints:   []topologySpreadConstraint{zoneConstraint, nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}, {{"node-x", 0}, {"node-a", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 1,
+						"zone2": 0,
+					},
+					{
+						"node-a": 1,
+						"node-x": 0,
+					},
 				},
 			},
 		},
@@ -1697,17 +1666,17 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint, nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 1}},
-					"node": {{"node-a", 1}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  1,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-x"}: 1,
+				Constraints:   []topologySpreadConstraint{zoneConstraint, nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}, {{"node-a", 1}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 1,
+						"zone2": 1,
+					},
+					{
+						"node-a": 1,
+						"node-x": 1,
+					},
 				},
 			},
 		},
@@ -1730,18 +1699,18 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint, nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {"zone1", 3}},
-					"node": {{"node-a", 1}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 2,
-					{key: "node", value: "node-x"}: 1,
+				Constraints:   []topologySpreadConstraint{zoneConstraint, nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 3}}, {{"node-a", 1}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 1,
+					},
+					{
+						"node-a": 1,
+						"node-b": 2,
+						"node-x": 1,
+					},
 				},
 			},
 		},
@@ -1775,17 +1744,17 @@ func TestPreFilterStateAddPod(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {"zone1", 2}},
-					"node": {{"node-a", 0}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  2,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 0,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-x"}: 2,
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}, {{"node-a", 0}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 2,
+						"zone2": 1,
+					},
+					{
+						"node-a": 0,
+						"node-b": 1,
+						"node-x": 2,
+					},
 				},
 			},
 		},
@@ -1819,17 +1788,17 @@ func TestPreFilterStateAddPod(t *testing.T) {
 						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
 					},
 				},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 1}},
-					"node": {{"node-a", 1}, {"node-b", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  1,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 1,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-x"}: 2,
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}, {{"node-a", 1}, {"node-b", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 1,
+						"zone2": 1,
+					},
+					{
+						"node-a": 1,
+						"node-b": 1,
+						"node-x": 2,
+					},
 				},
 			},
 		},
@@ -1849,14 +1818,11 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 1},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -1876,14 +1842,11 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 1},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -1903,15 +1866,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -1931,15 +1891,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 2,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -1959,15 +1916,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -1987,15 +1941,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 2,
-					{key: "zone", value: "zone1"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 2,
+					"zone1": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -2015,15 +1966,12 @@ func TestPreFilterStateAddPod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 2,
-					{key: "zone", value: "zone1"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 2,
+					"zone1": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -2100,15 +2048,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			deletedPodIdx: 0, // remove pod "p-a1"
 			nodeIdx:       0, // node-a
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 1,
+				}},
 			},
 		},
 		{
@@ -2131,15 +2076,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			deletedPodIdx: 0, // remove pod "p-a1"
 			nodeIdx:       0, // node-a
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 1}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 1,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 1,
+					"zone2": 2,
+				}},
 			},
 		},
 		{
@@ -2163,15 +2105,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			deletedPodIdx: 0, // remove pod "p-a0"
 			nodeIdx:       0, // node-a
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 2}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 2,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 2,
+					"zone2": 2,
+				}},
 			},
 		},
 		{
@@ -2195,15 +2134,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			deletedPod:    st.MakePod().Name("p-a0").Node("node-a").Label("bar", "").Obj(),
 			nodeIdx:       0, // node-a
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone1", 2}, {"zone2", 2}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}: 2,
-					{key: "zone", value: "zone2"}: 2,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 2}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone1": 2,
+					"zone2": 2,
+				}},
 			},
 		},
 		{
@@ -2227,18 +2163,18 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 			deletedPodIdx: 3, // remove pod "p-x1"
 			nodeIdx:       2, // node-x
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint, nodeConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {"zone1", 3}},
-					"node": {{"node-b", 1}, {"node-x", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone1"}:  3,
-					{key: "zone", value: "zone2"}:  1,
-					{key: "node", value: "node-a"}: 2,
-					{key: "node", value: "node-b"}: 1,
-					{key: "node", value: "node-x"}: 1,
+				Constraints:   []topologySpreadConstraint{zoneConstraint, nodeConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 3}}, {{"node-b", 1}, {"node-x", 1}}},
+				TpValueToMatchNum: []map[string]int{
+					{
+						"zone1": 3,
+						"zone2": 1,
+					},
+					{
+						"node-a": 2,
+						"node-b": 1,
+						"node-x": 1,
+					},
 				},
 			},
 		},
@@ -2258,14 +2194,11 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 1},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -2285,14 +2218,11 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 1},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -2312,15 +2242,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 0,
-					{key: "zone", value: "zone1"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 0,
+					"zone1": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: false,
 		},
@@ -2340,15 +2267,12 @@ func TestPreFilterStateRemovePod(t *testing.T) {
 				st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(),
 			},
 			want: &preFilterState{
-				Constraints:       []topologySpreadConstraint{zoneConstraint},
-				TpKeyToDomainsNum: map[string]int{"zone": 2},
-				TpKeyToCriticalPaths: map[string]*criticalPaths{
-					"zone": {{"zone2", 0}, {"zone1", 1}},
-				},
-				TpPairToMatchNum: map[topologyPair]int{
-					{key: "zone", value: "zone2"}: 0,
-					{key: "zone", value: "zone1"}: 1,
-				},
+				Constraints:   []topologySpreadConstraint{zoneConstraint},
+				CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}},
+				TpValueToMatchNum: []map[string]int{{
+					"zone2": 0,
+					"zone1": 1,
+				}},
 			},
 			enableNodeInclusionPolicy: true,
 		},
@@ -3397,6 +3321,67 @@ func TestMultipleConstraints(t *testing.T) {
 			},
 			enableNodeInclusionPolicy: true,
 		},
+		{
+			// 1. to fulfil first "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y)
+			// 2. to fulfil second "zone" constraint, incoming pod can be placed on any zone
+			// intersection of (1) and (2) returns node-x and node-y
+			name: "two Constraints on zone, spreads = [3/3, 2/1]",
+			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
+				Obj(),
+			nodes: []*v1.Node{
+				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
+				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
+				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
+				st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
+			},
+			existingPods: []*v1.Pod{
+				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
+				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
+				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
+			},
+			wantStatusCode: map[string]framework.Code{
+				"node-a": framework.Unschedulable,
+				"node-b": framework.Unschedulable,
+				"node-x": framework.Success,
+				"node-y": framework.Success,
+			},
+		},
+		{
+			// 1. to fulfil first "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y)
+			// 2. to fulfil second "zone" constraint, incoming pod can be placed on zone1 (node-a or node-b)
+			// intersection of (1) and (2) returns no node
+			name: "two Constraints on zone, spreads = [3/4, 2/1]",
+			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
+				SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
+				Obj(),
+			nodes: []*v1.Node{
+				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
+				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
+				st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
+				st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
+			},
+			existingPods: []*v1.Pod{
+				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
+				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
+				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
+				st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
+			},
+			wantStatusCode: map[string]framework.Code{
+				"node-a": framework.Unschedulable,
+				"node-b": framework.Unschedulable,
+				"node-x": framework.Unschedulable,
+				"node-y": framework.Unschedulable,
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go
index c38e0ed14c51d..56a1625c61374 100644
--- a/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go
+++ b/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go
@@ -156,9 +156,12 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
+				TopologyValueToPodCounts: []map[string]*int64{
+					{
+						"zone1": ptr.To[int64](0),
+						"zone2": ptr.To[int64](0),
+					},
+					{},
 				},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2), topologyNormalizingWeight(3)},
 			},
@@ -188,10 +191,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 		},
@@ -229,8 +232,11 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New("node-x"),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
+				TopologyValueToPodCounts: []map[string]*int64{
+					{
+						"zone1": ptr.To[int64](0),
+					},
+					{},
 				},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(2)},
 			},
@@ -271,9 +277,12 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: v1.LabelTopologyZone, value: "mars"}: ptr.To[int64](0),
-					{key: v1.LabelTopologyZone, value: ""}:     ptr.To[int64](0),
+				TopologyValueToPodCounts: []map[string]*int64{
+					{},
+					{
+						"mars": ptr.To[int64](0),
+						"":     ptr.To[int64](0),
+					},
 				},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(4), topologyNormalizingWeight(2)},
 			},
@@ -322,8 +331,11 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "planet", value: "mars"}: ptr.To[int64](0),
+				TopologyValueToPodCounts: []map[string]*int64{
+					{},
+					{
+						"mars": ptr.To[int64](0),
+					},
 				},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(1)},
 			},
@@ -363,9 +375,9 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{"planet", "mars"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"mars": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1)},
 			},
 		},
@@ -395,10 +407,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -429,10 +441,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -463,10 +475,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -497,10 +509,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -530,10 +542,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -563,10 +575,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
 					},
 				},
 				IgnoredNodes: sets.New[string](),
-				TopologyPairToPodCounts: map[topologyPair]*int64{
-					{key: "zone", value: "zone1"}: ptr.To[int64](0),
-					{key: "zone", value: "zone2"}: ptr.To[int64](0),
-				},
+				TopologyValueToPodCounts: []map[string]*int64{{
+					"zone1": ptr.To[int64](0),
+					"zone2": ptr.To[int64](0),
+				}},
 				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
 			},
 			enableNodeInclusionPolicy: true,
@@ -953,6 +965,33 @@ func TestPodTopologySpreadScore(t *testing.T) {
 				{Name: "node-x", Score: 63},
 			},
 		},
+		{
+			name: "two Constraints on zone, 2 out of 4 nodes are candidates",
+			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
+				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
+				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
+				Obj(),
+			existingPods: []*v1.Pod{
+				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
+				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
+				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
+				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
+			},
+			nodes: []*v1.Node{
+				st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(),
+				st.MakeNode().Name("node-x").Label("zone", "zone2").Obj(),
+			},
+			failedNodes: []*v1.Node{
+				st.MakeNode().Name("node-b").Label("zone", "zone1").Obj(),
+				st.MakeNode().Name("node-y").Label("zone", "zone2").Obj(),
+			},
+			want: []framework.NodeScore{
+				{Name: "node-a", Score: 85},
+				{Name: "node-x", Score: 100},
+			},
+		},
 		{
 			// If Constraints hold different labelSelectors, it's a little complex.
 			// +----------------------+------------------------+