Skip to content

Commit 0709374

Browse files
authored
PG: clean up replication pings, unify wal_sender_timeout handling (#4363)
* Make ReplPing private as it's only called within the PG connector * Unify wal_sender_timeout handling - started as deduping the constant but we'd really want to reduce catalog accesses on every batch and sticking to a canoncial value seems good. The struct thing still seems messy but that's the least messy shape I could think of without losing nuance
1 parent 4e41d2c commit 0709374

10 files changed

Lines changed: 61 additions & 57 deletions

File tree

docs/peerdb-architecture.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ classDiagram
228228
+EnsurePullability()
229229
+ExportTxSnapshot()
230230
+SetupReplication()
231-
+ReplPing()
232231
+PullFlowCleanup()
233232
}
234233

flow/connectors/bigquery/qrep_object_pull.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,6 @@ func (c *BigQueryConnector) SetupReplConn(context.Context, map[string]string) er
499499
return nil
500500
}
501501

502-
func (c *BigQueryConnector) ReplPing(context.Context) error {
503-
return nil
504-
}
505-
506502
func (c *BigQueryConnector) UpdateReplStateLastOffset(context.Context, model.CdcCheckpoint) error {
507503
return nil
508504
}

flow/connectors/core.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,6 @@ type CDCPullConnectorCore interface {
115115
// Methods related to retrieving and pushing records for this connector as a source and destination.
116116
SetupReplConn(context.Context, map[string]string) error
117117

118-
// Ping source to keep connection alive. Can be called concurrently with pulling records; skips ping in that case.
119-
ReplPing(context.Context) error
120-
121118
// Called when offset has been confirmed to destination
122119
UpdateReplStateLastOffset(ctx context.Context, lastOffset model.CdcCheckpoint) error
123120

flow/connectors/mongo/cdc.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -551,10 +551,6 @@ func (c *MongoConnector) SetupReplConn(context.Context, map[string]string) error
551551
return nil
552552
}
553553

554-
func (c *MongoConnector) ReplPing(context.Context) error {
555-
return nil
556-
}
557-
558554
func (c *MongoConnector) UpdateReplStateLastOffset(ctx context.Context, lastOffset model.CdcCheckpoint) error {
559555
return nil
560556
}

flow/connectors/mysql/cdc.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,6 @@ func (c *MySqlConnector) closeSyncerWithTimeout(syncer *replication.BinlogSyncer
352352
}
353353
}
354354

355-
func (c *MySqlConnector) ReplPing(context.Context) error {
356-
return nil
357-
}
358-
359355
func (c *MySqlConnector) UpdateReplStateLastOffset(ctx context.Context, lastOffset model.CdcCheckpoint) error {
360356
flowName := ctx.Value(shared.FlowNameKey).(string)
361357
return c.SetLastOffset(ctx, flowName, lastOffset)

flow/connectors/postgres/cdc.go

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"log/slog"
99
"maps"
10-
"regexp"
1110
"slices"
1211
"strings"
1312
"sync"
@@ -476,8 +475,6 @@ func clientSidePingPeriod(walSenderTimeout time.Duration) time.Duration {
476475
return 3 * walSenderTimeout / 4
477476
}
478477

479-
const NoWALSenderTimeoutSetting = "NONE"
480-
481478
// PullCdcRecords pulls records from req's cdc stream
482479
func PullCdcRecords[Items model.Items](
483480
ctx context.Context,
@@ -502,34 +499,16 @@ func PullCdcRecords[Items model.Items](
502499
}
503500

504501
// determine message wait period in function of idle and wal_sender timeouts
505-
var walSenderTimeout time.Duration
506-
if walSenderTimeoutStr, err := internal.PeerDBPostgresWalSenderTimeout(ctx, req.Env); err != nil {
507-
return fmt.Errorf("could't get wal_sender_timeout parameter: %w", err)
508-
} else if walSenderTimeoutStr != NoWALSenderTimeoutSetting {
509-
// If no units are provided, milliseconds are assumed
510-
if regexp.MustCompile(`^\d+$`).MatchString(walSenderTimeoutStr) {
511-
walSenderTimeoutStr += "ms"
512-
}
513-
if walSenderTimeout, err = time.ParseDuration(walSenderTimeoutStr); err != nil {
514-
return fmt.Errorf("failed to parse wal_sender_timeout value: %w", err)
515-
}
516-
if walSenderTimeout < 0 {
517-
return fmt.Errorf("invalid wal_sender_timeout value: %s", walSenderTimeout)
518-
}
519-
}
520502
// this value controls for how long the main message loop is blocked waiting for new messages from Postgres.
521-
var messageWaitPeriod time.Duration
522-
if walSenderTimeout <= 0 {
523-
// If no WAL sender timeout is set
524-
messageWaitPeriod = req.IdleTimeout
525-
} else {
503+
messageWaitPeriod := req.IdleTimeout
504+
if p.walSenderTimeout.isSet && p.walSenderTimeout.duration > 0 {
526505
// If set, consider for ping interval
527-
messageWaitPeriod = min(req.IdleTimeout, clientSidePingPeriod(walSenderTimeout))
506+
messageWaitPeriod = min(req.IdleTimeout, clientSidePingPeriod(p.walSenderTimeout.duration))
528507
}
529508

530509
logger.Debug("Message wait period determined",
531510
slog.Duration("messageWaitPeriod", messageWaitPeriod),
532-
slog.Duration("wal_sender_timeout", walSenderTimeout),
511+
slog.Duration("wal_sender_timeout", p.walSenderTimeout.duration),
533512
slog.Duration("req.IdleTimeout", req.IdleTimeout),
534513
)
535514

flow/connectors/postgres/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ func (c *PostgresConnector) createSlotAndPublication(
546546

547547
// create slot only after we succeeded in creating publication.
548548
if !s.SlotExists {
549-
conn, err := c.CreateReplConn(ctx, env)
549+
conn, _, err := c.CreateReplConn(ctx, env)
550550
if err != nil {
551551
return model.SetupReplicationResult{}, fmt.Errorf("[slot] error acquiring connection: %w", err)
552552
}

flow/connectors/postgres/postgres.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type PostgresConnector struct {
4747
rdsAuth *utils.RDSAuth
4848
connStr string
4949
metadataSchema string
50+
walSenderTimeout walSenderTimeout
5051
replLock sync.Mutex
5152
closeLock sync.Mutex
5253
pgVersion shared.PGVersion

flow/connectors/postgres/postgres_source.go

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log/slog"
77
"maps"
8+
"regexp"
89
"slices"
910
"strings"
1011
"sync/atomic"
@@ -35,12 +36,50 @@ type SlotCheckResult struct {
3536
PublicationExists bool
3637
}
3738

38-
func (c *PostgresConnector) CreateReplConn(ctx context.Context, env map[string]string) (*pgx.Conn, error) {
39+
type walSenderTimeout struct {
40+
str string
41+
duration time.Duration
42+
isSet bool
43+
}
44+
45+
var walSenderTimeoutNumbersOnly = regexp.MustCompile(`^\d+$`)
46+
47+
func walSenderTimeoutOverride(ctx context.Context, env map[string]string) (walSenderTimeout, error) {
48+
str, err := internal.PeerDBPostgresWalSenderTimeout(ctx, env)
49+
if err != nil {
50+
return walSenderTimeout{}, fmt.Errorf("failed to get wal_sender_timeout value: %w", err)
51+
}
52+
if strings.EqualFold(str, "NONE") {
53+
return walSenderTimeout{
54+
str: str,
55+
duration: 0,
56+
isSet: false,
57+
}, nil
58+
}
59+
parseStr := str
60+
if walSenderTimeoutNumbersOnly.MatchString(parseStr) {
61+
parseStr += "ms" // A bare integer is interpreted as milliseconds, per Postgres
62+
}
63+
duration, err := time.ParseDuration(parseStr)
64+
if err != nil {
65+
return walSenderTimeout{}, fmt.Errorf("failed to parse wal_sender_timeout value %q: %w", parseStr, err)
66+
}
67+
if duration < 0 {
68+
return walSenderTimeout{}, fmt.Errorf("invalid wal_sender_timeout value: %s", duration)
69+
}
70+
return walSenderTimeout{
71+
str: str,
72+
duration: duration,
73+
isSet: true,
74+
}, nil
75+
}
76+
77+
func (c *PostgresConnector) CreateReplConn(ctx context.Context, env map[string]string) (*pgx.Conn, walSenderTimeout, error) {
3978
// create a separate connection for non-replication queries as replication connections cannot
4079
// be used for extended query protocol, i.e. prepared statements
4180
replConfig, err := ParseConfig(c.connStr, c.Config)
4281
if err != nil {
43-
return nil, fmt.Errorf("failed to parse connection string: %w", err)
82+
return nil, walSenderTimeout{}, fmt.Errorf("failed to parse connection string: %w", err)
4483
}
4584

4685
replConfig.Config.RuntimeParams["timezone"] = "UTC"
@@ -55,32 +94,33 @@ func (c *PostgresConnector) CreateReplConn(ctx context.Context, env map[string]s
5594
replConfig.Config.RuntimeParams["standard_conforming_strings"] = "on"
5695
replConfig.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol
5796

58-
walSenderTimeout, err := internal.PeerDBPostgresWalSenderTimeout(ctx, env)
97+
wst, err := walSenderTimeoutOverride(ctx, env)
5998
if err != nil {
60-
return nil, fmt.Errorf("failed to get wal_sender_timeout value: %w", err)
99+
return nil, walSenderTimeout{}, err
61100
}
62-
if !strings.EqualFold(walSenderTimeout, "NONE") {
63-
c.logger.Info("set wal_sender_timeout", slog.String("wal_sender_timeout", walSenderTimeout))
64-
replConfig.Config.RuntimeParams["wal_sender_timeout"] = walSenderTimeout
101+
if wst.isSet {
102+
c.logger.Info("set wal_sender_timeout", slog.String("wal_sender_timeout", wst.str))
103+
replConfig.Config.RuntimeParams["wal_sender_timeout"] = wst.str
65104
} else {
66105
c.logger.Info("not setting wal_sender_timeout")
67106
}
68107

69108
conn, err := NewPostgresConnFromConfig(ctx, replConfig, c.Config.TlsHost, c.rdsAuth, c.ssh)
70109
if err != nil {
71110
internal.LoggerFromCtx(ctx).Error("failed to create replication connection", slog.Any("error", err))
72-
return nil, fmt.Errorf("failed to create replication connection: %w", err)
111+
return nil, walSenderTimeout{}, fmt.Errorf("failed to create replication connection: %w", err)
73112
}
74-
return conn, nil
113+
return conn, wst, nil
75114
}
76115

77116
func (c *PostgresConnector) SetupReplConn(ctx context.Context, env map[string]string) error {
78-
conn, err := c.CreateReplConn(ctx, env)
117+
conn, wst, err := c.CreateReplConn(ctx, env)
79118
if err != nil {
80119
return err
81120
}
82121
c.replConn = conn
83-
go c.runReplConnKeepalive(ctx, 15*time.Second, c.ReplPing)
122+
c.walSenderTimeout = wst
123+
go c.runReplConnKeepalive(ctx, 15*time.Second, c.replPing)
84124
return nil
85125
}
86126

@@ -89,7 +129,7 @@ func (c *PostgresConnector) SetupReplConn(ctx context.Context, env map[string]st
89129
// from the PullRecords stop: (1) between PullRecords calls (2) within
90130
// PullRecords when AddRecord blocks on a full record stream.
91131
//
92-
// While PullRecords holds replLock inside ReceiveMessage, ReplPing's TryLock
132+
// While PullRecords holds replLock inside ReceiveMessage, replPing's TryLock
93133
// makes it a no-op. This is safe because ReceiveMessage will always run its
94134
// own keepalive on timeout.
95135
func (c *PostgresConnector) runReplConnKeepalive(
@@ -111,8 +151,8 @@ func (c *PostgresConnector) runReplConnKeepalive(
111151
}
112152
}
113153

114-
// ReplPing sends a standby status update so Postgres doesn't terminate the walsender for inactivity.
115-
func (c *PostgresConnector) ReplPing(ctx context.Context) error {
154+
// replPing sends a standby status update so Postgres doesn't terminate the walsender for inactivity.
155+
func (c *PostgresConnector) replPing(ctx context.Context) error {
116156
if c.replLock.TryLock() {
117157
defer c.replLock.Unlock()
118158
if c.replState != nil {

flow/connectors/postgres/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (c *PostgresConnector) CheckReplicationPermissions(ctx context.Context, use
204204

205205
func (c *PostgresConnector) CheckReplicationConnectivity(ctx context.Context, env map[string]string) error {
206206
// Check if we can create a replication connection
207-
conn, err := c.CreateReplConn(ctx, env)
207+
conn, _, err := c.CreateReplConn(ctx, env)
208208
if err != nil {
209209
return fmt.Errorf("failed to create replication connection: %v", err)
210210
}

0 commit comments

Comments
 (0)