postgres_cdc: Fix behaviour of narrowing of publication from ALL TABLES to named table and vice versa - #4726
postgres_cdc: Fix behaviour of narrowing of publication from ALL TABLES to named table and vice versa #4726josephwoodward wants to merge 17 commits into
Conversation
fb1ba2b to
a52645f
Compare
a52645f to
c308c37
Compare
c308c37 to
765cad3
Compare
765cad3 to
b140624
Compare
11a61ad to
5531f3f
Compare
c181f3c to
8b8c56b
Compare
8b8c56b to
0f96069
Compare
ae5cf05 to
426cfa1
Compare
This fix attempts to drop and recreate the publication in a single transaction. Returning an error is permissions issues arise.
db8d5f9 to
09a1f87
Compare
|
This one isn't an issue as it'll only be for new tables, so it's expected behaviour to start streaming from the point of adding a new table.
The only data the user will see in this case is the point up into the WAL drops and recreates the publication.
It started up because the behaviour was incorrect. I've updated the docs to highlight that this particular use case (going from named tables to all tables) requires superuser permission. See commit 0d0cf87.
Good idea, output plugin validation moved above destructive behaviour. Addressed in commit a305a5d. |
| // Validate slot's output plugin before we perform anything destructive | ||
| if len(connExecResult) > 0 && len(connExecResult[0].Rows) > 0 { | ||
| outputPlugin := string(connExecResult[0].Rows[0][1]) | ||
| // handling a case when replication slot already exists but with different output plugin created manually | ||
| if outputPlugin != decodingPlugin { | ||
| return nil, fmt.Errorf("replication slot %s already exists with different output plugin: %s", config.ReplicationSlotName, outputPlugin) | ||
| } | ||
| } | ||
|
|
||
| // Create/amend publication (for instances where tables config has been widened or narrowed) | ||
| pubName := "pglog_stream_" + config.ReplicationSlotName | ||
| stream.logger.Infof("Creating publication %s for tables: %s", pubName, tablesForPublication) | ||
| if err = CreatePublication(ctx, stream.pgConn, stream.logger, pubName, tablesForPublication); err != nil { | ||
| return nil, err | ||
| } |
There was a problem hiding this comment.
This reordering — validating the slot's output plugin before CreatePublication can drop and recreate the publication — is a real behaviour change (it's the whole point of commit a305a5d), but it lands with no test. Nothing in the repo currently exercises the "slot exists with a different output plugin" branch either (it's only referenced at logical_stream.go:205-207), so the guarantee "we don't destroy the user's publication when the slot is unusable" is unverified and is easy to silently regress by moving these blocks again.
An integration test in pglogicalstream could create a slot with a non-pgoutput plugin (e.g. test_decoding, available in the stock postgres:16 image already used by createDockerInstance) plus a named-table publication, call NewPgStream with an empty tables list, and assert both that the error mentions the plugin mismatch and that puballtables is still false.
Per CONTRIBUTING.md §1.3.2/§1.3.3 — tests should cover end-to-end functionality and integration tests verify core workflows.
|
Thanks for this — the underlying fix looks right to me (checking
None of this undermines the direction — the shape of the fix seems right to me, and 3–5 look like cheap wins. Happy to dig further into any of them in-thread if useful, and totally fine if some of these land as follow-ups. |
Currently there are some behavioural bugs in the PostgreSQL connector that we should address in isolation before merging #4720. These exist when a user modifies their configuration to go from a list of tables, to no tables in either direction.
All tables to named-tables
When creating a publication with any of the following
tablesconfigurations, the publication is created as aFOR ALL TABLES, which tracks all tables in the schema.If a user then modifies their configuration to narrow one or more tables:
Instead of narrowing the publication to the
table_to_tracktable, the publication still remains as tracking all tables (FOR ALL TABLES).The consequence is there's no data loss, however the connector is still publishing change events for all tables in the schema (including new ones created later, and anything containing PII the user thought they'd excluded by narrowing)
How does this happen?
The
GetPublicationTablesattempts to find the tables being tracked before deciding whether to narrow it (if one or more tables are supplied) or expand it toFOR ALL TABLES(if no tables are supplied), however it's checkingpg_publication_tableswhich is a view, not the authoritative membership catalog (it should usepg_publication.puballtablesinstead).Named-tables to All Tables
Conversely, when modifying the configuration to go from named tables to
FOR ALL TABLES, the table(s) are removed from the publication but the publication is left empty, meaning no tables would be tracked no change events streamed.Breaking Behavioural Change
By fixing the incorrect aforementioned behaviour, the connector now attempts to modify the publication. However PostgreSQL does not altering a publication and instead requires it to be dropped and recreated. This impacts the user in the following ways:
DROP PUBLICATION+CREATE PUBLICATION ... FOR TABLE ...DROP PUBLICATION+CREATE PUBLICATION ... FOR ALL TABLESCREATEprivilege on the database + ownership of the tables being publishedProof of Work
Along with the automated tests in this change, the following screenshot demonstrates:
tables: []creates aFOR ALL TABLESpublication.users) recreates the publication with named tables.userstousers,productsandcart.users,productsandcart) toproductsandusers.Switching from initial creation of 3 named tables to
FOR ALL TABLES: