Fix backup performance regression with includedNamespaces ["*"]

Commit 8ac8f49b5 ("Remove wildcard check from getNamespacesToList")
removed the optimization that prevented "*" from being expanded to
individual namespace names. This caused getNamespacesToList to return
all namespace names instead of "" (cross-namespace listing), resulting
in N separate API list calls per resource type instead of 1.

On clusters with many namespaces (e.g. 178 on an ACM cluster), this
means ~35,000 API calls instead of ~200, causing backups to take
18-20 minutes for just 8 items.

Restore the "*" special case in ShouldExpandWildcards so that plain
"*" is not expanded, and restore the ShouldInclude("*") check in
getNamespacesToList so that cross-namespace listing is used.

The restore fix from 8ac8f49b5 (fromBackup flag) is preserved since
restores already return false before reaching the "*" check.

Namespace exclusion continues to work correctly: the nsTracker filters
excluded namespace objects via ShouldInclude, and backupItem filters
namespace-scoped resources at line 124 of item_backupper.go.

Fixes #9869

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
This commit is contained in:
Shubham Pampattiwar
2026-06-10 10:04:14 -07:00
parent 7ffef7609e
commit ea1f23f3f6
6 changed files with 33 additions and 16 deletions
@@ -0,0 +1 @@
Fix backup performance regression when using includedNamespaces ["*"] by restoring cross-namespace API listing optimization
+6
View File
@@ -5407,6 +5407,8 @@ func TestBackupNamespaces(t *testing.T) {
want: []string{
"resources/namespaces/cluster/ns-1.json",
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
"resources/namespaces/cluster/ns-3.json",
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
},
},
{
@@ -5440,6 +5442,10 @@ func TestBackupNamespaces(t *testing.T) {
want: []string{
"resources/namespaces/cluster/ns-1.json",
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
"resources/namespaces/cluster/ns-2.json",
"resources/namespaces/v1-preferredversion/cluster/ns-2.json",
"resources/namespaces/cluster/ns-3.json",
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
},
+7 -4
View File
@@ -633,15 +633,18 @@ func coreGroupResourcePriority(resource string) int {
}
// getNamespacesToList examines ie and resolves the includes and excludes to a full list of
// namespaces to list. If ie is nil, the result is just "" (list across all namespaces).
// Otherwise, the result is a list of every included namespace minus all excluded ones.
// Because the namespace IE filter is expanded from 1.18, there is no need to consider
// wildcard characters anymore.
// namespaces to list. If ie is nil or it includes *, the result is just "" (list across all
// namespaces). Otherwise, the result is a list of every included namespace minus all excluded ones.
func getNamespacesToList(ie *collections.NamespaceIncludesExcludes) []string {
if ie == nil {
return []string{""}
}
if ie.ShouldInclude("*") {
// "" means all namespaces
return []string{""}
}
var list []string
for _, n := range ie.GetIncludes() {
if ie.ShouldInclude(n) {
@@ -1063,13 +1063,13 @@ func TestExpandIncludesExcludes(t *testing.T) {
fromBackup: true,
},
{
name: "asterisk alone - should expand",
name: "asterisk alone - should not expand",
includes: []string{"*"},
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedIncludes: []string{"default", "kube-system", "test"},
expectedIncludes: []string{"*"},
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectedWildcardExpanded: false,
expectError: false,
fromBackup: true,
},
+7
View File
@@ -26,6 +26,13 @@ func ShouldExpandWildcards(includes []string, excludes []string, fromBackup bool
wildcardFound := false
for _, include := range includes {
// "*" alone means "match all" - don't expand, so that
// getNamespacesToList can use a single cross-namespace API call
// instead of per-namespace calls.
if include == "*" {
return false
}
if containsWildcardPattern(include) {
wildcardFound = true
}
+9 -9
View File
@@ -23,11 +23,11 @@ func TestShouldExpandWildcards(t *testing.T) {
expected: false,
},
{
name: "includes has star - should expand",
name: "includes has star - should not expand",
includes: []string{"*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
expected: false,
},
{
excludes: []string{"ns3", "ns4"},
@@ -35,18 +35,18 @@ func TestShouldExpandWildcards(t *testing.T) {
expected: false,
},
{
name: "includes has star - should expand",
name: "includes has star with wildcard excludes - should not expand",
includes: []string{"*"},
excludes: []string{"ns1"},
excludes: []string{"ns*"},
fromBackup: true,
expected: true,
expected: false,
},
{
name: "includes has star after a wildcard pattern - should expand",
name: "includes has star after a wildcard pattern - should not expand",
includes: []string{"ns*", "*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
expected: false,
},
{
name: "includes has wildcard pattern",
@@ -70,11 +70,11 @@ func TestShouldExpandWildcards(t *testing.T) {
expected: true,
},
{
name: "includes has star and wildcard - should expand",
name: "includes has star and wildcard - should not expand",
includes: []string{"*", "ns*"},
excludes: []string{},
fromBackup: true,
expected: true,
expected: false,
},
{
name: "double asterisk should be detected as wildcard",