Postgres Mirroring in Snowflake: What It Really Costs (and How to Keep It Cheap)

· Medium ·

9 min read Original article ↗

A practical guide to the billing, the refresh_interval decision, and the six numbers your team should be watching

Yaniv Leven

Snowflake made Postgres data mirroring generally available this week. Point a mirror at a Snowflake Postgres instance and every insert, update, delete and schema change shows up in Snowflake tables, without a CDC connector, a batch job, or a third-party tool in the middle. It’s available in every region where Snowflake Postgres runs.

The launch post calls it the end of the “pipeline tax.” For many teams, that will be true. But “no pipeline” isn’t “no bill.” I went through Snowflake’s documentation to pin down what you pay for, which settings move the number, and what to monitor. Where something isn’t documented, I’ll say so.

What you’re actually billed for

According to Snowflake’s mirroring docs, mirror costs have two main components:

  1. Compute for the apply task. Each mirror gets a serverless task named APPLY_MIRROR_<mirror_name> that merges accumulated changes into the target tables. It's billed at the standard Snowflake serverless task rate.
  2. Storage on both ends. The $changes change-log tables and the metalog are Iceberg tables that live on the source Postgres instance through pg_lake. The materialized target tables live in your Snowflake target database. Both are billed at standard Snowflake storage rates.

There are three more costs that the mirroring page doesn’t list as “mirror costs,” but that I’d count anyway:

  • Your Postgres instance does real work. Capture runs on the source instance. A walsender decodes the write-ahead log and a CDC worker batches changes roughly every 15 seconds. Both use the instance’s CPU and memory. Snowflake Postgres compute is billed in credits per hour based on the instance’s compute family, so if mirroring pushes you to a bigger instance, that’s a mirroring cost.
  • Reads. Queries against target tables and $live views run on your warehouses like any other query.
  • Data transfer. Snowflake’s Postgres cost page says standard transfer charges apply to data moving in and out of Postgres instances. Mirrors can’t cross accounts, regions or clouds, but the docs don’t spell out how transfer applies to mirroring, so I wouldn’t assume it’s zero.

There’s no per-row or per-GB mirroring price. Cost depends on how often the apply task runs, how much change data it processes, and how many tables are involved, priced at the serverless task rates in Snowflake’s Service Consumption Table.Where a mirror costs you: capture on Postgres, change logs, the apply task, and storage on both sides

Press enter or click to view image in full size

Where a mirror costs you: capture on Postgres, change logs, the apply task, and storage on both sides

refresh_interval is a pricing dial

This is the most important setting for cost, and Snowflake is refreshingly direct about it. Each apply run merges everything that’s accumulated since the last one. A shorter interval means more frequent, smaller runs and fresher target tables. A longer interval packs more changes into each run, which lowers cost.

A few specifics from the docs:

  • The default is 10 minutes. You can set anything from 30 seconds up to 1 day. Snowflake's own launch example uses 1 minute.
  • The $live view breaks the usual tradeoff. Every mirrored table gets a $live view that combines the target table with pending, not-yet-merged changes, with roughly 30 seconds of lag no matter what refresh interval you choose. Snowflake explicitly recommends a longer interval plus $live for teams that need recent data but want low apply costs.
  • Schema changes don’t wait. DDL, renames and snapshots are applied promptly regardless of the interval. The apply task checks for work at least every two minutes.

So the practical question is “which consumers really need sub-minute data, and can they read from $live?" Most dashboards and dbt models are fine with a target table 15 or 60 minutes behind.

One thing I couldn’t find documented: how querying $live compares in cost to the plain target table. It reads $changes on top of the target, so measure it before pointing a high-frequency dashboard at it.

When mirroring beats a batch pipeline, and when it might not

Snowflake says ongoing apply uses delete-then-append instead of MERGE, so insert-only batches skip the target-table scan that MERGE-based CDC pays on every run. There’s no external service or polling loop, and you get a seven-day $changes feed for free. It's likely a clear win if you already pay for a CDC connector and the time to babysit it, your tables are insert-heavy, and consumers need minutes-old data.

Be more careful if:

  • You have hundreds of busy tables. Snowflake calls table count “the most significant factor” in pipeline performance, because the apply task processes schema and merge logic for each table with changes.
  • Your instance is already near capacity. By default each mirror’s walsender runs at elevated CPU priority, and the docs warn that decoding “wins CPU contests against client backends.” On a saturated instance, that can slow your application’s writes.
  • You run huge single transactions. Bulk loads that change millions of rows in one statement use more memory and disk while mirroring holds them until commit.
  • Daily data is genuinely enough. Then a 1 day interval may be all you need. Decide that on purpose.

Six things to check before and after you turn it on

1. Size the source instance for app plus mirroring. Snowflake suggests STANDARD_XL as a good starting point, at least one core per mirror, and STANDARD_L or larger with extra disk (for example, more than 250 GB) for sustained high write throughput. The BURSTABLE tier isn’t supported.

2. Protect against re-snapshots. This is the hidden cost I’d watch most closely. max_slot_wal_keep_size defaults to one-tenth of allocated disk. If apply lag causes the WAL backlog to exceed it, Postgres invalidates the replication slot and the mirror re-snapshots all tables after 10 minutes. A full re-snapshot of a large schema is real compute and real load on your source.

3. Keep staging tables out. Tables that are constantly dropped and recreated add CDC work without useful output.

4. Prefer table-level mirrors for busy schemas. Schema-level mirrors pick up every new table automatically, including ones you didn’t mean to replicate. Table-level mirrors keep the scope, and the cost, reviewable. Snowflake also recommends spreading tables across several mirrors rather than one big one, and limiting the number of frequently changed tables in each.

5. Watch lag in two places. CALL SNOWFLAKE.POSTGRES.DESCRIBE_MIRROR('my_mirror') returns push lag (cdc_flush_lag_seconds, slot_lag_bytes) and apply lag (apply_lag_seconds). Add them together for end-to-end lag. Lag that trends upward is your early warning for a re-snapshot.

6. Track spend per mirror. Because each mirror has its own task, attribution is easy:

SELECT task_name, DATE_TRUNC('day', start_time) AS day,
SUM(TO_NUMBER(credits_used, 38, 9)) AS credits
FROM snowflake.account_usage.serverless_task_history
WHERE task_name LIKE 'APPLY_MIRROR_%'
GROUP BY 1, 2 ORDER BY 2 DESC, 3 DESC;

The view can lag by up to three hours. For storage, check POSTGRES_STORAGE_USAGE_HISTORY (change logs and metalog) and DATABASE_STORAGE_USAGE_HISTORY for the target database.

Press enter or click to view image in full size

Six numbers worth watching once mirroring is on

Where Seemore fits in

Mirroring is a pattern we see a lot at Seemore: one visible cost, like a connector invoice, gets replaced by several smaller ones across serverless tasks, storage and warehouse queries. Seemore pulls those together and ties them to the pipelines, data products and teams that use the mirrored data, with lineage showing who actually reads which tables downstream. That makes it easier to see whether a one-minute refresh interval is paying for itself, or whether nobody downstream reads the data more than once an hour.

Press enter or click to view image in full size

The bottom line

Postgres mirroring removes much of the pain of moving operational data into Snowflake, and its design is efficient. But you still pay for serverless apply compute, storage on both sides, and Postgres capacity. Treat refresh_interval as a pricing decision, use $live for the readers who need it, size your instance for the extra work, and monitor lag so a WAL spike doesn't turn into a full re-snapshot.

Ever since this went live we’ve gotten some repeated questions about this feature…you can see them below:

How is Snowflake Postgres data mirroring billed?

Mirroring has two main cost components: serverless compute for each mirror’s apply task, billed at the standard serverless task rate, and storage for the $changes and metalog tables on the Postgres instance plus the target tables in Snowflake, billed at standard storage rates. Change capture also uses CPU and memory on your Postgres instance, which is billed separately.

Does a longer refresh_interval reduce mirroring cost?

Yes. Each apply run merges all accumulated changes, so a longer interval combines more changes into fewer runs and lowers apply cost. The default is 10 minutes, and values range from 30 seconds to 1 day. The $live view still shows changes within about 30 seconds.

What is the $live view in Snowflake mirroring?

Every mirrored table has a $live view that combines the target table with pending, not-yet-merged changes from the change log, giving roughly 30-second freshness regardless of the refresh interval.

How do I monitor the cost of a mirror?

Each mirror runs as a serverless task named APPLY_MIRROR_<mirror_name>. Query SNOWFLAKE.ACCOUNT_USAGE.SERVERLESS_TASK_HISTORY filtered on that name, or filter Snowsight's Consumption page to serverless tasks. Use POSTGRES_STORAGE_USAGE_HISTORY and DATABASE_STORAGE_USAGE_HISTORY for storage.

What can cause a mirror to re-snapshot all tables?

If apply lag lets the WAL backlog exceed max_slot_wal_keep_size (by default one-tenth of the instance's disk), Postgres invalidates the replication slot, and the mirror re-snapshots every table after 10 minutes. Adequate disk and lag monitoring help prevent this.

Where is Postgres mirroring available?

On AWS and Microsoft Azure, in all regions where Snowflake Postgres is available. Mirrors can’t be created across accounts, regions or cloud providers.

Sources referenced

Want to see what your mirrors, pipelines and warehouses really cost, and who uses them? Learn more at Seemore Data.