Skip to main content
Filament replicates PostgreSQL to Snowflake with change data capture. It reads the pgoutput logical replication stream from a slot it creates for the pipeline, snapshots each table through the same consistent point, and merges inserts, updates, and deletes into typed Snowflake tables. Data is staged as Parquet in the Snowflake user’s internal stage. Each run is a bounded catch-up from the last durable position to the current WAL position, so you schedule runs as often as you need fresh data.
The PostgreSQL source is beta and the Snowflake sink is alpha.

How it works

  1. Snapshot. The first run creates a replication slot with an exported snapshot and reads every selected table through it.
  2. Catch up. Later runs replay changes from the slot up to the current WAL position and stop. A run with no changes still saves the position.
  3. Merge. merge writes upsert inserts and updates by primary key and delete removed rows, so Snowflake mirrors PostgreSQL.
In a test on a 1.1M-row database, a catch-up run after 1,000 updates, 200 deletes, and 301 inserts moved exactly 1,501 records, and the next run with no changes moved 0.

Prerequisites

  • PostgreSQL with wal_level=logical, and a primary key on every replicated table
  • A Snowflake account where you can create a role, warehouse, database, and user
  • A Filament deployment backed by the PostgreSQL datastore, with the CLI pointed at it through filament context add and filament context use
CDC needs a datastore that can durably track replication streams. The local CLI context stores state in SQLite and rejects CDC pipelines with cannot durably admit replication streams. Deploy Filament on Kubernetes, Railway, or Render, or run the PostgreSQL-backed stack from Local development to try it on a laptop.

1. Prepare PostgreSQL

Enable logical replication and restart PostgreSQL. On RDS, Cloud SQL, or Azure, set the equivalent parameter on the instance instead.
Create a replication user and grant read access:
Create the publication as a table owner or superuser:
Filament can create and maintain the publication itself, but only when its user has permission to create publications on those tables. A plain replication user does not, and the first run fails with create publication "filament": ERROR: permission denied. Creating the publication yourself and setting manage_publication to false (step 3) avoids granting Filament ownership of your tables. For tables with large text, jsonb, or bytea values that change without being rewritten, set full replica identity:

2. Prepare Snowflake

Generate a key pair for the service user:
Then, as an account administrator, create the role, warehouse, database, and user, and assign the public key printed above:
Filament creates the destination schema, so FILAMENT_ROLE owns it and can create and evolve tables there. The full grant reference is on the Snowflake sink page.

3. Create the source and sink

--sink-account takes the organization-account identifier. On a deployed server, secret values such as the DSN and private key are stored by the deployment’s secret provider.

4. Create the pipeline and run the snapshot

Pass --sync-mode cdc explicitly. Without it the CLI fills in the default read mode, and a CDC source rejects it with pipeline graph is invalid: CDC connections do not accept a read mode. The first run snapshots every selected table. In the test database, that was 1,100,500 rows across customers and orders.

5. Catch up on changes

Make changes in PostgreSQL:
Run the pipeline again:
The run reads only the 1,501 changes. Updated rows are replaced, the 200 deleted orders are removed, and the 300 new orders are inserted.

6. Verify

Each replicated row carries Filament metadata columns: _filament_run_id, _filament_run_started_at, _filament_operation, _filament_source_position, and _filament_sequence. On the source, check replication slot lag:

7. Schedule it

Add a cron schedule to the pipeline in the web app or with the Pipelines API. Every 5 to 15 minutes is common. The Snowflake warehouse resumes for each load and suspends after AUTO_SUSPEND, so the schedule drives most of the Snowflake compute cost.

Going to production

  • Slot lag: an unread slot keeps WAL on the source disk. Keep the schedule running, and drop slots for pipelines you delete. Filament stores each generated slot name in the control database’s replication_streams.consumer_name column.
  • Start without a snapshot: --source-snapshot-mode none on the pipeline streams from the current position, and the destination fills in as rows change.
  • History instead of a mirror: --write-mode append keeps every change as a row, with deletes as events.
  • Schema changes: new columns are added to Snowflake tables. Renames, type changes, and dropped columns are not applied automatically.

Troubleshooting

create publication "filament": ERROR: permission denied

The replication user cannot create publications. Create the publication as a table owner and set --source-manage-publication=false.

CDC connections do not accept a read mode

Add --sync-mode cdc to filament pipeline create.

261004 (08004): failed to auth for unknown reason. HTTP: 404

The Snowflake account identifier is wrong. Use the organization-account form shown in Snowsight under your account details.

slot is active

Another client holds the pipeline’s replication slot. Stop that consumer or wait for the previous run to finish. More errors are in Troubleshooting.

FAQ

Does Filament replicate deletes from PostgreSQL to Snowflake?

Yes. CDC captures deletes from the WAL, and merge removes those rows in Snowflake.

Is the replication continuous?

Each run catches up to the WAL position at the moment it starts and then stops. Scheduling runs every few minutes gives near-continuous replication without a warehouse that stays running.

Can I replicate tables without a primary key?

Not with CDC. Copy keyless tables with a separate full replace pipeline.

Does Filament need superuser access to PostgreSQL?

No. It needs a role with REPLICATION and SELECT on the tables, plus a publication created by a table owner.