Skip to main content
The Snowflake Target Connector streams Change Data Capture (CDC) events into Snowflake with Snowpipe Streaming. Every change is appended to a changelog table that keeps the full history of the source table. On top of it, Popsink builds a dynamic table that holds the current state of the source table, one row per primary key, and keeps it up to date with an incremental MERGE.

Compatible versions

Dynamic tables require a running warehouse for their refresh; size it for your change volume. See Cost and warehouse.

Key Features

  • CDC Streaming: utilizes Snowpipe Streaming to deliver real-time changes from the source system.
  • Full change history: every change is written as an insert into the changelog table, so nothing is ever overwritten.
  • Current state in a dynamic table: a dynamic table merges the changelog into one row per key, with updates applied and deletes removed. See Dynamic Tables.
  • Per-subscription control: each subscription chooses whether it gets a dynamic table and how fresh it must be (its target lag).
  • Time Travel Support: retains historical data for time travel queries, facilitating data analysis and auditing.
  • Key-Pair Authentication: relies on Snowflake’s key-pair authentication mechanism — no passwords.
  • Schema evolution: new columns are added automatically as the source schema evolves, to the changelog and to the dynamic table.

Prerequisites

  • A Snowflake account where you wish to sync your data.
  • A warehouse for the dynamic table refreshes.
  • A role with the privileges listed under Role privileges below.
  • A key pair (no passphrase) for authentication. To set it up:
    1. Generate the key pair:
    2. Register the public key on the Snowflake user:
    3. Copy the private key without the header and footer lines:

Role privileges

Run these as a role that can grant on the target database — ACCOUNTADMIN, or the owner of the database and schema:
USAGE on the database and schema makes them visible to the role. USAGE on the warehouse is what lets it create the dynamic table and run that table’s refreshes — the only part of provisioning that needs a running warehouse; the rest is cloud-services DDL. CREATE TABLE covers the append-only changelog table the connector creates per subscription. CREATE PIPE covers the Snowpipe Streaming pipe it creates alongside each of those tables (<table>_STREAMING), which is the object streamed rows actually arrive through — without it, provisioning fails before the first row lands. CREATE DYNAMIC TABLE covers the current-state table built over the changelog. No INSERT, UPDATE or DELETE grant is needed: the connector creates the tables it writes to and therefore owns them, and rows reach them through the pipe rather than through SQL DML. If instead you point the connector at a schema whose tables already exist and are owned by another role, that owner has to grant write and schema-evolution rights on each one, or new source columns will fail to land:
The role owns the dynamic tables it creates, so no extra grant is needed to alter, resume or rebuild them. To let other roles read them, grant SELECT on the dynamic tables (or SELECT ON FUTURE DYNAMIC TABLES IN SCHEMA) as you would for any table.

Configuration

An empty append table suffix writes the changelog under the bare table name (ORDERS), which is how legacy targets were set up. The dynamic table would need that same name, so no dynamic table can be created on such a target.

How It Works

For each subscription writing table <T>, Popsink creates three objects in the connector’s database and schema: Changes are streamed into <T>_APPEND as inserts: an update or a delete adds a row, it never modifies one. The dynamic table <T> reads the new rows of <T>_APPEND and merges them, so queries on <T> see the source table as it is, while <T>_APPEND keeps everything that happened to it. Table names must be simple identifiers (letters, digits, _ and $, not starting with a digit). They are created unquoted, so Snowflake stores them in upper case.

Changelog columns

Besides the source columns, every row of the changelog carries: These columns are carried into the dynamic table too, except before_data: each row of the dynamic table tells you which change produced it and when. __partition and __offset only order the changes of a row; do not use them as a row identifier. A backfill row reads the source table directly rather than through Kafka, so its __offset is a counter derived from the range the loader read. On an IBM i source it can look like the record’s RRN, but it is not one: it drifts after deleted records, follows an index key on large files, and is replaced by a Kafka offset as soon as the row changes.

Dynamic Tables

A dynamic table is a Snowflake table whose content is defined by a query and kept up to date by Snowflake itself, on a schedule set by its target lag. Popsink uses one per subscription to turn the changelog into the current state of the source table.

How the merge works

On each refresh, Snowflake reads only the rows added to <T>_APPEND since the previous refresh, and merges them into <T>:
  1. Keep the latest change of each key. The new rows are grouped by the subscription’s primary key, and the latest one wins: the highest __source_ts_ms, then the highest __partition and __offset to break ties between two changes made in the same millisecond.
  2. Apply it only if it is newer than the row already in <T>, by the same ordering. A late or replayed event never overwrites a more recent state.
  3. Apply the change:
    • a delete removes the key from <T>;
    • any other change updates the row, or inserts it if the key is not there yet.
For example, the changelog of order 42: After the refresh, the dynamic table ORDERS holds one row, order 42 with status shipped. Order 43 was deleted, so it is not there. Both histories stay in ORDERS_APPEND. Deletes are hard deletes in the dynamic table. To find deleted rows, query the changelog for __deleted = TRUE. Because only the new rows are read, a refresh costs in proportion to the changes since the last one, not to the size of the table. Popsink creates the dynamic table with:
The COMMENT is how Popsink recognizes a dynamic table it manages: it records the key and the columns the table was built with. Do not change it, and do not alter the definition by hand: Popsink would recreate the table on its next check.

Configuring a subscription

In the subscription drawer, the dynamic table is set per subscription: The same options can be set on several subscriptions at once from the bulk configuration panel. Choosing the target lag. A shorter lag gives fresher data but runs the warehouse more often. The lag is a target, not a guarantee: a refresh that takes longer than the lag, or a busy warehouse, delays the next one.
With a target lag of 0 (DOWNSTREAM), Snowflake refreshes the table only when a dynamic table that reads from it refreshes. If nothing reads from it, the table is never refreshed, and since it is created with INITIALIZE = ON_SCHEDULE, it stays empty. Use DOWNSTREAM only when you build your own dynamic tables on top, or refresh it yourself with ALTER DYNAMIC TABLE <T> REFRESH.
Changing the options. A new target lag or warehouse is applied in place with ALTER DYNAMIC TABLE ... SET TARGET_LAG/WAREHOUSE: the table is not rebuilt. Turning the option off stops Popsink from managing the dynamic table, but does not drop it: drop it yourself if you no longer need it.
Several subscriptions can write the same table. If they disagree on whether to build the dynamic table or on its target lag, the table falls back to the defaults (on, 60 min) and a warning is logged.
The dynamic table is always created in the connector’s database and schema, next to its changelog.

First refresh

The dynamic table is created with INITIALIZE = ON_SCHEDULE: Popsink does not wait for it to be filled, and the first refresh runs on Snowflake’s schedule, within the target lag. Until then, querying it returns an error saying the table has not been initialized yet. The changelog <T>_APPEND can be queried right away. To fill it immediately, run:

Tables without a primary key

A dynamic table needs a key to merge on. When the subscription has no primary key, Popsink uses __hash, the hash of the row’s content, as the key, and logs a warning:
  • an update writes a delete for the old content and an insert for the new content, so the old row is removed;
  • two source rows with exactly the same content collapse into one row in the dynamic table.
If the source table does have a key, set it on the datamodel or flag it in the subscription’s mapping: the dynamic table is then rebuilt on that key.

Schema evolution

When a new column reaches the subscription, Popsink:
  1. adds it to <T>_APPEND;
  2. recreates the dynamic table with the new column.
To avoid replaying the whole history, the recreate starts from a clone of the current dynamic table taken at its last successful refresh, then applies the changes made since. If no refresh has succeeded yet, or the key changed, the dynamic table is rebuilt from the whole changelog. Rows that existed before the new column have NULL in it. Columns are never dropped or retyped.

Rebuilding a dynamic table

If a dynamic table is lost, dropped or out of sync, open the target, and in its subscription list choose Rebuild dynamic table in the subscription’s menu. Popsink recreates the dynamic table from its changelog. Only one rebuild runs at a time. A rebuild is also triggered automatically after a backfill created the changelog.

Cost and warehouse

Each refresh runs on the connector’s Warehouse and is billed as warehouse compute. The changelog itself is written by Snowpipe Streaming and does not use the warehouse.
  • Use a warehouse with AUTO_SUSPEND and AUTO_RESUME so it only runs during refreshes.
  • Many dynamic tables with a short target lag keep the warehouse busy. Raise the target lag on tables that do not need to be fresh before sizing up the warehouse.
  • A dedicated warehouse makes refresh costs easy to follow, and keeps them from competing with your queries.

Monitoring

Each dynamic table can be inspected in Snowsight, where its page shows its refresh history and graph, or with SQL:

Troubleshooting

Deleting a subscription

By default, deleting a subscription leaves its Snowflake objects in place. Check Also delete the objects created in the target in the delete dialog to drop them too: the dialog lists the dynamic table, the changelog and the pipe it will drop. Popsink only drops objects it can identify as its own, and lists the others as kept.