Skip to content

Commit b5c24ef

Browse files
ilidemijgao54
andauthored
Add a config to disable CDC store for ClickHouse (#4438)
Building on #4366 - Introduce `PEERDB_CLICKHOUSE_CDC_STORE_ENABLED` to allow separating handling of cdc store enablement for ClickHouse (since other destinations like PG-to-PG and PG-to-Snowflake requires this to be enabled). - Still honor existing services with CDC store disabled, to avoid OOM issues resurfacing - Currently `PEERDB_CLICKHOUSE_CDC_STORE_ENABLED` is default to `true`, can be updated to `false` once we are ready to flip the switch (PR is currently a no-op) --------- Co-authored-by: Joy Gao <17896160+jgao54@users.noreply.github.com>
1 parent fcbbf65 commit b5c24ef

5 files changed

Lines changed: 90 additions & 19 deletions

File tree

flow/activities/flowable.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,11 @@ func getInitialNormalizeBatchID(
7171
catalogPool shared.CatalogPool,
7272
env map[string]string,
7373
destinationName string,
74+
destinationType protos.DBType,
7475
flowName string,
7576
) (int64, error) {
76-
dstPeer, err := connectors.LoadPeer(ctx, catalogPool, destinationName)
77-
if err != nil {
78-
return 0, fmt.Errorf("failed to load destination peer for normalize state: %w", err)
79-
}
80-
8177
// Postgres keeps normalize progress in destination-local metadata.
82-
if dstPeer.Type == protos.DBType_POSTGRES {
78+
if destinationType == protos.DBType_POSTGRES {
8379
dstPgConn, dstClose, err := connectors.GetPostgresConnectorByName(ctx, env, catalogPool, destinationName)
8480
if err != nil {
8581
return 0, fmt.Errorf("failed to get postgres destination connector for normalize state: %w", err)
@@ -360,7 +356,14 @@ func (a *FlowableActivity) SyncFlow(
360356
ctx = internal.WithOperationContext(ctx, protos.FlowOperation_FLOW_OPERATION_SYNC)
361357
logger := internal.LoggerFromCtx(ctx)
362358

363-
srcConn, srcClose, err := connectors.GetByNameAs[connectors.CDCPullConnectorCore](ctx, config.Env, a.CatalogPool, config.SourceName)
359+
destinationType, err := connectors.LoadPeerType(ctx, a.CatalogPool, config.DestinationName)
360+
if err != nil {
361+
return a.Alerter.LogFlowError(ctx, config.FlowJobName, fmt.Errorf("failed to load destination peer type: %w", err))
362+
}
363+
364+
srcConn, srcClose, err := connectors.GetByNameWithCDCDestinationTypeAs[connectors.CDCPullConnectorCore](
365+
ctx, config.Env, a.CatalogPool, config.SourceName, destinationType,
366+
)
364367
if err != nil {
365368
return a.Alerter.LogFlowError(ctx, config.FlowJobName, err)
366369
}
@@ -383,7 +386,7 @@ func (a *FlowableActivity) SyncFlow(
383386
normResponses := concurrency.NewLastChan()
384387

385388
lastNormBatchID, err := getInitialNormalizeBatchID(
386-
ctx, logger, a.CatalogPool, config.Env, config.DestinationName, config.FlowJobName,
389+
ctx, logger, a.CatalogPool, config.Env, config.DestinationName, destinationType, config.FlowJobName,
387390
)
388391
if err != nil {
389392
return a.Alerter.LogFlowError(ctx, config.FlowJobName, err)
@@ -401,7 +404,7 @@ func (a *FlowableActivity) SyncFlow(
401404
// under normal steady operation where the batch hits idle timeout every time it will match the hours very closely
402405
// effective hours will be longer if pull is idling, or there are waits on big transactions,
403406
// or the sync interval is so small that start/stop overhead starts being visible
404-
// will be shorter if the batches hit the size limit rather rather than idle timeout
407+
// will be shorter if the batches hit the size limit rather than idle timeout
405408
normBufferSize := normBufferHours * 3600 / int64(idleTimeout.Seconds())
406409
// Normalize is always 1 batch behind, allow 2 to still run in parallel with pull-sync
407410
normBufferSize = max(normBufferSize, 2)

flow/connectors/core.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,13 @@ func LoadPeer(ctx context.Context, catalogPool shared.CatalogPool, peerName stri
546546
}
547547

548548
func GetConnector(ctx context.Context, env map[string]string, config *protos.Peer) (Connector, error) {
549+
return getConnector(ctx, env, config, protos.DBType_DBTYPE_UNKNOWN)
550+
}
551+
552+
func getConnector(ctx context.Context, env map[string]string, config *protos.Peer, cdcDestinationType protos.DBType) (Connector, error) {
549553
switch inner := config.Config.(type) {
550554
case *protos.Peer_PostgresConfig:
551-
return connpostgres.NewPostgresConnector(ctx, env, inner.PostgresConfig)
555+
return connpostgres.NewPostgresConnectorWithCDCDestination(ctx, env, inner.PostgresConfig, cdcDestinationType)
552556
case *protos.Peer_BigqueryConfig:
553557
return connbigquery.NewBigQueryConnector(ctx, inner.BigqueryConfig)
554558
case *protos.Peer_SnowflakeConfig:
@@ -579,7 +583,7 @@ var noopClose = func(context.Context) {}
579583
// Gets typed connector by config. Returns a close function to recruit the compiler into helping us avoid connection leaks.
580584
func GetAs[T Connector](ctx context.Context, env map[string]string, config *protos.Peer) (T, func(context.Context), error) {
581585
var none T
582-
conn, err := GetConnector(ctx, env, config)
586+
conn, err := getConnector(ctx, env, config, protos.DBType_DBTYPE_UNKNOWN)
583587
if err != nil {
584588
return none, noopClose, exceptions.NewPeerCreateError(err)
585589
}
@@ -621,6 +625,34 @@ func GetByNameAs[T Connector](
621625
return conn, connClose, err
622626
}
623627

628+
// Gets connector by name, aware of CDC destination connector type.
629+
// Returns a close function to recruit the compiler into helping us avoid connection leaks.
630+
func GetByNameWithCDCDestinationTypeAs[T Connector](
631+
ctx context.Context, env map[string]string, catalogPool shared.CatalogPool, name string, destinationType protos.DBType,
632+
) (T, func(context.Context), error) {
633+
var none T
634+
peer, err := LoadPeer(ctx, catalogPool, name)
635+
if err != nil {
636+
return none, noopClose, err
637+
}
638+
conn, err := getConnector(ctx, env, peer, destinationType)
639+
if err != nil {
640+
return none, noopClose, exceptions.NewPeerCreateError(err)
641+
}
642+
643+
if tconn, ok := conn.(T); ok {
644+
connClose := func(closeCtx context.Context) {
645+
if err := conn.Close(); err != nil {
646+
internal.LoggerFromCtx(closeCtx).Error("error closing connector", slog.Any("error", err))
647+
}
648+
}
649+
return tconn, connClose, nil
650+
} else {
651+
conn.Close()
652+
return none, noopClose, errors.ErrUnsupported
653+
}
654+
}
655+
624656
// Gets Postgres connector by name. Returns a close function to recruit the compiler into helping us avoid connection leaks.
625657
func GetPostgresConnectorByName(
626658
ctx context.Context,

flow/connectors/postgres/cdc.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -525,13 +525,9 @@ func PullCdcRecords[Items model.Items](
525525
}
526526
}
527527

528-
// Remove exceptions.PrimaryKeyModifiedError and its classification when cdc store is removed
529-
cdcStoreEnabled, err := internal.PeerDBCDCStoreEnabled(ctx, req.Env)
530-
if err != nil {
531-
return err
532-
}
533528
var cdcRecordsStorage *utils.CDCStore[Items]
534-
if cdcStoreEnabled {
529+
if p.cdcStoreEnabled {
530+
var err error
535531
cdcRecordsStorage, err = utils.NewCDCStore[Items](ctx, req.Env, p.flowJobName)
536532
if err != nil {
537533
return err

flow/connectors/postgres/postgres.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,25 @@ type PostgresConnector struct {
5151
replLock sync.Mutex
5252
closeLock sync.Mutex
5353
pgVersion shared.PGVersion
54+
55+
cdcStoreEnabled bool
56+
}
57+
58+
func NewPostgresConnectorWithCDCDestination(
59+
ctx context.Context, env map[string]string, pgConfig *protos.PostgresConfig, destinationType protos.DBType,
60+
) (*PostgresConnector, error) {
61+
return newPostgresConnector(ctx, env, pgConfig, destinationType)
5462
}
5563

56-
func NewPostgresConnector(ctx context.Context, env map[string]string, pgConfig *protos.PostgresConfig) (*PostgresConnector, error) {
64+
func NewPostgresConnector(
65+
ctx context.Context, env map[string]string, pgConfig *protos.PostgresConfig,
66+
) (*PostgresConnector, error) {
67+
return newPostgresConnector(ctx, env, pgConfig, protos.DBType_DBTYPE_UNKNOWN)
68+
}
69+
70+
func newPostgresConnector(
71+
ctx context.Context, env map[string]string, pgConfig *protos.PostgresConfig, destinationType protos.DBType,
72+
) (*PostgresConnector, error) {
5773
logger := internal.LoggerFromCtx(ctx)
5874
flowNameInApplicationName, err := internal.PeerDBApplicationNamePerMirrorName(ctx, nil)
5975
if err != nil {
@@ -114,6 +130,17 @@ func NewPostgresConnector(ctx context.Context, env map[string]string, pgConfig *
114130
metadataSchema = *pgConfig.MetadataSchema
115131
}
116132

133+
cdcStoreEnabled, err := internal.PeerDBCDCStoreEnabled(ctx, env)
134+
if err != nil {
135+
return nil, fmt.Errorf("failed to fetch CDC Store setting: %w", err)
136+
}
137+
if cdcStoreEnabled && destinationType == protos.DBType_CLICKHOUSE {
138+
cdcStoreEnabled, err = internal.PeerDBClickHouseCDCStoreEnabled(ctx, env)
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to fetch ClickHouse CDC Store setting: %w", err)
141+
}
142+
}
143+
117144
connector := &PostgresConnector{
118145
logger: logger,
119146
Config: pgConfig,
@@ -130,6 +157,7 @@ func NewPostgresConnector(ctx context.Context, env map[string]string, pgConfig *
130157
pgVersion: 0,
131158
typeMap: pgtype.NewMap(),
132159
rdsAuth: rdsAuth,
160+
cdcStoreEnabled: cdcStoreEnabled,
133161
}
134162

135163
tunnel.StartKeepalive(context.Background(), func() {

flow/internal/dynamicconf.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,17 @@ var DynamicSettings = [...]*protos.DynamicSetting{
6868
Description: "Controls whether to enable the store for recovering unchanged Postgres TOAST values within a CDC batch",
6969
DefaultValue: "true",
7070
ValueType: protos.DynconfValueType_BOOL,
71-
ApplyMode: protos.DynconfApplyMode_APPLY_MODE_IMMEDIATE,
71+
ApplyMode: protos.DynconfApplyMode_APPLY_MODE_AFTER_RESUME,
7272
TargetForSetting: protos.DynconfTarget_ALL,
7373
},
74+
{
75+
Name: "PEERDB_CLICKHOUSE_CDC_STORE_ENABLED",
76+
Description: "Override PEERDB_CDC_STORE_ENABLED when destination is ClickHouse",
77+
DefaultValue: "true",
78+
ValueType: protos.DynconfValueType_BOOL,
79+
ApplyMode: protos.DynconfApplyMode_APPLY_MODE_AFTER_RESUME,
80+
TargetForSetting: protos.DynconfTarget_CLICKHOUSE,
81+
},
7482
{
7583
Name: "PEERDB_CDC_DISK_SPILL_RECORDS_THRESHOLD",
7684
Description: "CDC: number of records beyond which records are written to disk instead",
@@ -649,6 +657,10 @@ func PeerDBCDCStoreEnabled(ctx context.Context, env map[string]string) (bool, er
649657
return dynamicConfBool(ctx, env, "PEERDB_CDC_STORE_ENABLED")
650658
}
651659

660+
func PeerDBClickHouseCDCStoreEnabled(ctx context.Context, env map[string]string) (bool, error) {
661+
return dynamicConfBool(ctx, env, "PEERDB_CLICKHOUSE_CDC_STORE_ENABLED")
662+
}
663+
652664
func PeerDBCDCDiskSpillRecordsThreshold(ctx context.Context, env map[string]string) (int64, error) {
653665
return dynamicConfSigned[int64](ctx, env, "PEERDB_CDC_DISK_SPILL_RECORDS_THRESHOLD")
654666
}

0 commit comments

Comments
 (0)