Skip to content

fix: handle drop column on clickhouse and remove from schema in clickpipes - #4609

Open
masterashu wants to merge 4 commits into
mainfrom
dbi-501-allow-users-to-drop-columns-on-ch
Open

fix: handle drop column on clickhouse and remove from schema in clickpipes#4609
masterashu wants to merge 4 commits into
mainfrom
dbi-501-allow-users-to-drop-columns-on-ch

Conversation

@masterashu

@masterashu masterashu commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

When customer removes a column from table in Clickhouse, Normalize fails due to SQL query error mentioning unknown column.

In order to deal with this, we had to change the source schema for the table to remove that column.

This PR aims to make this easier by identifying if the column is deleted and retrying and updating the schema.

We check the errors in Normalization step and match if its occurring from a missing column, if it is we refresh the schema and retry normalization and also update the schema.

@github-actions

Copy link
Copy Markdown
Contributor

❌ Test Failure

Analysis: The new test Test_Column_Added_Back_After_Destination_Column_Drop timed out because the ClickHouse destination never received the re-added record (stuck at 2 vs expected 3 for the full 200s) in two suite variants, indicating a real bug in this PR's column drop/re-add handling rather than a flaky failure.
Confidence: 0.9

⚠️ This appears to be a real bug - manual intervention needed

View workflow run

@github-actions

Copy link
Copy Markdown
Contributor

❌ Test Failure

Analysis: The PR's own column-drop feature test (Test_Column_Added_Back_After_Destination_Column_Drop) fails deterministically on both ClickHouse and ClickHouse_Cluster variants with a persistent, non-converging record mismatch (source=3 vs destination=2) that never normalizes — a real regression in the PR's column-drop handling, not a timing flake.
Confidence: 0.82

⚠️ This appears to be a real bug - manual intervention needed

View workflow run

@masterashu
masterashu marked this pull request as ready for review July 23, 2026 15:56
@masterashu
masterashu requested a review from a team as a code owner July 23, 2026 15:56
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@jgao54

jgao54 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@masterashu could you add a description for the expected behavior for this PR? Thanks!

Comment thread flow/connectors/clickhouse/normalize.go Outdated
actualColSet[col.Name] = struct{}{}
}

currentSchema := gen.tableNameSchemaMapping[gen.TableName]

@jgao54 jgao54 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gen.tableNameSchemaMapping does not represent the freshest schema stored in the catalog, but instead represent the schema that was fetched from catalog earlier at the start of the startNormalize. This introduces a race condition between Sync and Normalize flow:

  1. normalize fetch schemas from catalog
  2. sync applies an add column DDL and updated the catalog
  3. normalize take this schemas from 1, removes the deleted column, and saves it back to catalog

The catalog is now stuck with a stale schema with a missing new column

Since we only care about column removal in the error handling, instead of passing back the updated schemas, pass back the removed column instead for the NormalizeResponse. Then in the readModifyLoop you can focus on just removing the deleted column from the fresh schema fetched from catalog, instead of replacing the entire schema today.

Comment thread flow/connectors/clickhouse/normalize.go
Comment thread flow/connectors/clickhouse/normalize.go Outdated
Comment thread flow/connectors/clickhouse/normalize.go Outdated
Comment on lines +737 to +739
c.logger.Warn("[clickhouse] auto-removing destination-dropped columns from schema",
slog.String("table", gen.TableName),
slog.Any("removedColumns", removedCols))

@jgao54 jgao54 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc, since we don't check against source db schema, if the user drops the column from ClickHouse today, but does not drop it from source, we will gracefully recover in this scenario as well and continue to replicate without the dropped column; and this column essentially becomes an excluded column, unless the table gets resynced which it would reappear again.

I think this is more of a product decision on whether we should fail loudly here, vs. we should continue. My only concern here with continue is if customer accidentally dropped a column, then previously it would fail the pipe, they would have manually added it back and the pipe would recover. vs with this change manually adding back the column would do nothing, and would require a resync.

If this is intended behavior we should have an user-facing log for it

cc @morsapaes on thoughts here.

@jgao54 jgao54 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized that if user dropped the column and then add it back, the historical data would be lost anyways. so maybe continuing the pipe is the more graceful option here (and user will just have to resync if it was a mistake)

Comment thread flow/activities/flowable_core.go Outdated
Comment thread flow/connectors/clickhouse/normalize.go Outdated
Comment thread flow/activities/flowable_core.go Outdated
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🔄 Flaky Test Detected

Analysis: The flow/e2e package hit its 20m go test deadline (panic: test timed out after 20m0s) on one matrix leg, cascading 202 in-flight/queued parallel subtests into failures with zero actual assertion errors, while the two sibling legs on the same commit finished the identical package in 956s and 994s against the 1200s budget.
Confidence: 0.92

✅ Automatically retrying the workflow

View workflow run

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🔄 Flaky Test Detected

Analysis: Test_MariaDB_PartialRowEvent failed during container setup with "readInitialHandshake ... err EOF: connection was bad" — a MariaDB testcontainer readiness race unrelated to this ClickHouse column-drop PR, and the same test passed in the other two matrix jobs.
Confidence: 0.93

✅ Automatically retrying the workflow

View workflow run

@masterashu
masterashu requested a review from jgao54 August 7, 2026 08:17
Comment on lines +797 to +798
logger.Error("failed to persist auto-removed destination columns to catalog",
slog.Any("error", updateErr))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should fail loudly and get retried. while it's also reasonable to just let the next iteration pick up the missing column if there is a transient error, if there's any internal errors we wouldn't be notified and it would always fail to persist to catalog.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also means can remove the updateErr == nil check below

Comment on lines +806 to +809
a.Alerter.LogFlowWarning(ctx, flowJobName,
fmt.Errorf("destination column(s) were dropped from table %s and automatically removed "+
"from the mirror schema; future syncs will omit those columns", name))
}

@jgao54 jgao54 Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently the warning message doesn't specify columns that are dropped, let's make the log more informative about that, since we now have this information

@jgao54

jgao54 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Codex identified a race condition worth mentioning:

Today the NormalizeRecords method will not only insert the rows into the destination table in CH, it will also update the offset to catalog on the table level (see SetLastNormalizedBatchIDForTable that updates metadata_last_sync_state table). So if a batch fails but one or more tables already succeeded normalize in the table, on retry those succeeded table will be skipped in the batch, so far this is an intended optimization.

The edge case that can cause correctness issue is:

  1. A user manually drops a column in CH.
  2. Normalize recovers for that table, as intended by this PR.
  3. Another table in the same normalize batch fails and stuck in retry loop.
  4. Correction to the catalog never happened because of 3.
  5. Postgres drops and re-adds that same column during this time.

Given catalog never removed the column, pg's ADD COLUMN ddl got ignored since no schema delta was detected, so ClickHouse never re-added that column. In this case, once the normalize error finally got unblocked, subsequent normalize will silently perform the "schema correction" introduced in this PR, and then permanently skip replicating the column that was added back.

The odds of this happening is pretty small, but not zero. Here's a potential best-effort workaround: #4678. It's still not perfect because if normalize fails and catalog correction fails, catalog correction doesn't get retried. A more bulletproof solution would be to update schema and advance offset together when there are column removals, but I am not sure if this additional code complexity is worthwhile given the unlikely scenario.

I am also okay with not trying to handle this edge case at all, if we feel that this is getting too much into the weeds. If user delete a column in CH accidentally and added it back, is probably warrants a resync anyways. if user delete a column in PG and added it back immediately, it's also very unlikely they also manually removed the column from ClickHouse in the same batch.

@jgao54

jgao54 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

discussed separately in slack, would be safer to add a source-check to make sure columns are also removed from source table before updating the catalog; or keep erroring otherwise.

This also allow us to keep the invariant of replicated columns = all columns - excluded columns, while allowing user to manually drop columns in clickhouse that have already been deleted from source, if they desire.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants