This commit is contained in:
Armando Ruocco 2026-07-16 12:01:10 +02:00 committed by GitHub
commit 97a1e508cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 93 additions and 13 deletions

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/cloudnative-pg/api v1.30.0 github.com/cloudnative-pg/api v1.30.0
github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260709152604-43158c204df1 github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260709152604-43158c204df1
github.com/cloudnative-pg/cloudnative-pg v1.30.0 github.com/cloudnative-pg/cloudnative-pg v1.30.0
github.com/cloudnative-pg/cnpg-i v0.5.0 github.com/cloudnative-pg/cnpg-i v0.5.1-0.20260715125838-10dead49aded
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2 github.com/cloudnative-pg/cnpg-i-machinery v0.4.2
github.com/cloudnative-pg/machinery v0.5.0 github.com/cloudnative-pg/machinery v0.5.0
github.com/onsi/ginkgo/v2 v2.32.0 github.com/onsi/ginkgo/v2 v2.32.0

4
go.sum
View File

@ -24,8 +24,8 @@ github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260709152604-43158c204df1 h1:+
github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260709152604-43158c204df1/go.mod h1:ZQLkdpk44FW5/BGWzABTOEcV9qPbwC+rbdscg2I8mBI= github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260709152604-43158c204df1/go.mod h1:ZQLkdpk44FW5/BGWzABTOEcV9qPbwC+rbdscg2I8mBI=
github.com/cloudnative-pg/cloudnative-pg v1.30.0 h1:fnhVq44xXx97MNiuvJsPrX1vSjYbgdyBK5MSGfdHdp0= github.com/cloudnative-pg/cloudnative-pg v1.30.0 h1:fnhVq44xXx97MNiuvJsPrX1vSjYbgdyBK5MSGfdHdp0=
github.com/cloudnative-pg/cloudnative-pg v1.30.0/go.mod h1:QkolwBOWZ+GvAiJt6KpDSymwkpf0K19/p4Q6MQlTM8U= github.com/cloudnative-pg/cloudnative-pg v1.30.0/go.mod h1:QkolwBOWZ+GvAiJt6KpDSymwkpf0K19/p4Q6MQlTM8U=
github.com/cloudnative-pg/cnpg-i v0.5.0 h1:/TOzpNT6cwNgrpftTtrnLKdoHgMwd+88vZgXjlVgXeE= github.com/cloudnative-pg/cnpg-i v0.5.1-0.20260715125838-10dead49aded h1:yutE8NcQ7HsQCmw2KDGoPcci/6mib2JK/CLWQ8mkBBQ=
github.com/cloudnative-pg/cnpg-i v0.5.0/go.mod h1:7Gh4+UzhBpGhr4DreB1GN9wGYfvxwXCXZUyVt3zE/3I= github.com/cloudnative-pg/cnpg-i v0.5.1-0.20260715125838-10dead49aded/go.mod h1:cdt+QK7+B/5cXXYQSnIE4H/fz7Oc8m60NlWN2HLptlE=
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2 h1:0reS9MtyLYINHXQ/MfxJ9jp39hhBf8e3Qdj+T5Nsq6I= github.com/cloudnative-pg/cnpg-i-machinery v0.4.2 h1:0reS9MtyLYINHXQ/MfxJ9jp39hhBf8e3Qdj+T5Nsq6I=
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2/go.mod h1:gvrKabgxXq0zGthXGucemDdsxakLEQDMxn43M4HLW30= github.com/cloudnative-pg/cnpg-i-machinery v0.4.2/go.mod h1:gvrKabgxXq0zGthXGucemDdsxakLEQDMxn43M4HLW30=
github.com/cloudnative-pg/machinery v0.5.0 h1:hhTnkzn+AiN3NmbjCQ6RXj5rfqV3K6arzq6kdXAzcnQ= github.com/cloudnative-pg/machinery v0.5.0 h1:hhTnkzn+AiN3NmbjCQ6RXj5rfqV3K6arzq6kdXAzcnQ=

View File

@ -27,6 +27,7 @@ import (
"path" "path"
"time" "time"
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
"github.com/cloudnative-pg/barman-cloud/pkg/archiver" "github.com/cloudnative-pg/barman-cloud/pkg/archiver"
barmanCommand "github.com/cloudnative-pg/barman-cloud/pkg/command" barmanCommand "github.com/cloudnative-pg/barman-cloud/pkg/command"
barmanCredentials "github.com/cloudnative-pg/barman-cloud/pkg/credentials" barmanCredentials "github.com/cloudnative-pg/barman-cloud/pkg/credentials"
@ -249,9 +250,11 @@ func (w WALServiceImplementation) Restore(
"Restoring WAL file", "Restoring WAL file",
"objectStore", objectStore.Name, "objectStore", objectStore.Name,
"serverName", serverName, "serverName", serverName,
"walName", walName) "walName", walName,
"mode", request.GetMode())
return &wal.WALRestoreResult{}, w.restoreFromBarmanObjectStore( return &wal.WALRestoreResult{}, w.restoreFromBarmanObjectStore(
ctx, configuration.Cluster, &objectStore, serverName, walName, destinationPath) ctx, configuration.Cluster, &objectStore, serverName, walName, destinationPath,
request.GetMode() == wal.WALRestoreRequest_MODE_REWIND)
} }
// resolveRestoreObjectStore selects the object store and server name to use when // resolveRestoreObjectStore selects the object store and server name to use when
@ -288,6 +291,7 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
serverName string, serverName string,
walName string, walName string,
destinationPath string, destinationPath string,
rewindMode bool,
) error { ) error {
contextLogger := log.FromContext(ctx) contextLogger := log.FromContext(ctx)
startTime := time.Now() startTime := time.Now()
@ -331,8 +335,10 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
return nil return nil
} }
// We skip this step if streaming connection is not available // Step 2: return error if the end-of-wal-stream flag is set.
if isStreamingAvailable(cluster, w.InstanceName) { // We skip this step if the flag machinery does not apply to this invocation
useEndOfWALStreamFlag := shouldUseEndOfWALStreamFlag(cluster, w.InstanceName, rewindMode)
if useEndOfWALStreamFlag {
if err := checkEndOfWALStreamFlag(walRestorer); err != nil { if err := checkEndOfWALStreamFlag(walRestorer); err != nil {
return err return err
} }
@ -340,10 +346,7 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
// Step 3: gather the WAL files names to restore. If the required file isn't a regular WAL, we download it directly. // Step 3: gather the WAL files names to restore. If the required file isn't a regular WAL, we download it directly.
var walFilesList []string var walFilesList []string
maxParallel := 1 maxParallel := maxWALFilesPerInvocation(barmanConfiguration, rewindMode)
if barmanConfiguration.Wal != nil && barmanConfiguration.Wal.MaxParallel > 1 {
maxParallel = barmanConfiguration.Wal.MaxParallel
}
if IsWALFile(walName) { if IsWALFile(walName) {
// If this is a regular WAL file, we try to prefetch // If this is a regular WAL file, we try to prefetch
if walFilesList, err = gatherWALFilesToRestore(walName, maxParallel); err != nil { if walFilesList, err = gatherWALFilesToRestore(walName, maxParallel); err != nil {
@ -365,9 +368,9 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
return classifyWALRestoreError(walStatus[0].WalName, walStatus[0].Err) return classifyWALRestoreError(walStatus[0].WalName, walStatus[0].Err)
} }
// We skip this step if streaming connection is not available // We skip this step if the flag machinery does not apply to this invocation
endOfWALStream := isEndOfWALStream(walStatus) endOfWALStream := isEndOfWALStream(walStatus)
if isStreamingAvailable(cluster, w.InstanceName) && endOfWALStream { if useEndOfWALStreamFlag && endOfWALStream {
contextLogger.Info( contextLogger.Info(
"Set end-of-wal-stream flag as one of the WAL files to be prefetched was not found") "Set end-of-wal-stream flag as one of the WAL files to be prefetched was not found")
@ -415,6 +418,38 @@ func (w WALServiceImplementation) SetFirstRequired(
panic("implement me") panic("implement me")
} }
// maxWALFilesPerInvocation returns how many WAL files a single restore
// invocation is allowed to fetch, the requested one included. Prefetching is
// disabled when restoring on behalf of pg_rewind, which walks the WAL
// backward: the following segments would never be requested, and past the end
// of the timeline they do not even exist
func maxWALFilesPerInvocation(barmanConfiguration *barmanapi.BarmanObjectStoreConfiguration, rewindMode bool) int {
if rewindMode {
return 1
}
if barmanConfiguration.Wal != nil && barmanConfiguration.Wal.MaxParallel > 1 {
return barmanConfiguration.Wal.MaxParallel
}
return 1
}
// shouldUseEndOfWALStreamFlag returns true when the end-of-wal-stream flag
// machinery applies to the current invocation. The flag makes the following
// invocation fail, so that PostgreSQL stops polling the WAL archive and
// switches to streaming replication. It does not apply when no streaming
// connection is available, nor when restoring on behalf of pg_rewind:
// pg_rewind cannot fall back to streaming replication, and a stale flag would
// make it abort on a segment that is available in the archive
func shouldUseEndOfWALStreamFlag(cluster *cnpgv1.Cluster, podName string, rewindMode bool) bool {
if rewindMode {
return false
}
return isStreamingAvailable(cluster, podName)
}
// isStreamingAvailable checks if this pod can replicate via streaming connection. // isStreamingAvailable checks if this pod can replicate via streaming connection.
func isStreamingAvailable(cluster *cnpgv1.Cluster, podName string) bool { func isStreamingAvailable(cluster *cnpgv1.Cluster, podName string) bool {
if cluster == nil { if cluster == nil {

View File

@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0
package common package common
import ( import (
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -96,3 +97,47 @@ var _ = Describe("resolveRestoreObjectStore", func() {
"cluster-server", "cluster-store"), "cluster-server", "cluster-store"),
) )
}) })
var _ = Describe("maxWALFilesPerInvocation", func() {
configWithMaxParallel := func(maxParallel int) *barmanapi.BarmanObjectStoreConfiguration {
return &barmanapi.BarmanObjectStoreConfiguration{
Wal: &barmanapi.WalBackupConfiguration{MaxParallel: maxParallel},
}
}
DescribeTable(
"computes how many WAL files a single invocation may fetch",
func(cfg *barmanapi.BarmanObjectStoreConfiguration, rewindMode bool, want int) {
Expect(maxWALFilesPerInvocation(cfg, rewindMode)).To(Equal(want))
},
Entry("no WAL configuration", &barmanapi.BarmanObjectStoreConfiguration{}, false, 1),
Entry("parallel restore configured", configWithMaxParallel(8), false, 8),
// pg_rewind walks the timeline backwards: prefetching must stay off no
// matter what the object store configuration asks for
Entry("rewind mode overrides the configured parallelism", configWithMaxParallel(8), true, 1),
)
})
var _ = Describe("shouldUseEndOfWALStreamFlag", func() {
clusterWithPrimary := func(currentPrimary string) *cnpgv1.Cluster {
return &cnpgv1.Cluster{
Status: cnpgv1.ClusterStatus{CurrentPrimary: currentPrimary},
}
}
DescribeTable(
"decides whether the end-of-wal-stream flag machinery applies",
func(cluster *cnpgv1.Cluster, podName string, rewindMode bool, want bool) {
Expect(shouldUseEndOfWALStreamFlag(cluster, podName, rewindMode)).To(Equal(want))
},
Entry("replica with streaming available", clusterWithPrimary("cluster-1"), "cluster-2", false, true),
Entry("primary cannot stream from anyone", clusterWithPrimary("cluster-1"), "cluster-1", false, false),
// pg_rewind cannot fall back to streaming replication: the flag machinery
// must stay off even where a standby would use it
Entry("rewind mode wins over streaming availability", clusterWithPrimary("cluster-1"), "cluster-2", true, false),
)
})