Mirror procedures reference

Reference for the mirror management procedures in the snowflake.postgres schema, the SQL objects a mirror creates on the source and target, and the column layout of every $changes change log table.

Object layout

Each mirror creates several objects across the source and target:

ObjectLocationName patternPurpose
PublicationSource PG<mirror_name>Drives logical replication
Replication slotSource PG<mirror_name>Durable decoding cursor
Change log tableSource PG (Iceberg via pg_lake)snowflake_cdc_logs.snowflake_cdc_<...>Per-table CDC feed
Metalog tableSource PG (Iceberg via pg_lake)snowflake_cdc_logs.<mirror_name>Schema-change and batch queue
Target databaseSnowflake<target_database>Holds materialized target tables
Target tableSnowflake<target_db>.<schema>.<table>Materialized copy of source
$changes tableSnowflake<target_db>.<schema>.<table>$changesChange log exposed via auto-refresh
$live viewSnowflake<target_db>.<schema>.<table>$liveTarget plus pending change log

Identifiers are folded to uppercase on Snowflake so that schema, table, and column names are case-insensitive in SQL.

Mirror management procedures

All procedures live in Snowflake in the snowflake.postgres schema and require the postgres_mirror_admin application role.

ProcedureDescription
CREATE_MIRROR(...)Create a new mirror. Sets up the target database, apply task, and publication on the source.
ALTER_MIRROR(...)Change the refresh interval, warehouse, or add/remove tables and schemas.
DROP_MIRROR('<name>'[, drop_target_database])Remove a mirror and clean up all associated infrastructure. By default the target database is dropped with the mirror. Pass drop_target_database => FALSE to keep it.
DESCRIBE_MIRROR('<name>')Return configuration and runtime state for a single mirror, including apply lag, last applied commit, and recent apply run durations.
REFRESH_MIRROR('<name>')Apply pending changes to a mirror immediately and clear last_apply_time.
SUSPEND_MIRROR('<name>')Stop scheduling new apply runs for a mirror. An existing, in-flight run finishes naturally.
RESUME_MIRROR('<name>')Resume a suspended mirror in place. Fails with RESTART_REQUIRED if unapplied operations have aged out of the source metalog.
RESTART_MIRROR('<name>'[, truncate_feed])Suspend the task, wait for any in-flight execution to finish, tell the source Postgres to RESTART the publication, then resume the task. Both modes re-snapshot all mirrored tables and rebuild $changes from a fresh baseline. Default (soft restart) preserves queued operations in the source-side metalog; pass truncate_feed => TRUE (hard restart) to truncate the metalog first.
LIST_MIRRORS([<instance>])Return one row per mirror on the given Postgres instance. Called without arguments, returns all mirrors visible to the current role.
LIST_MIRRORED_TABLES('<name>')Return one row per source table with its replication state and per-table snapshot progress.
QUERY_ADMIN_LOG(...)Query mirror-internal events with optional filters by mirror name, level (INFO, WARN, ERROR, DEBUG), and timestamp.
REFRESH_CHANGES_TABLE(<mirror>, <table>)Refresh the Iceberg metadata for one mirrored table’s $changes table to reduce $live view lag.

CREATE_MIRROR parameters

ParameterRequiredDescription
mirror_nameYesIdentifier for the mirror, also used as the publication name. Must be an unquoted identifier of 50 characters or fewer. Must be unique across all Postgres instances in the account.
postgres_instanceYesName of the Postgres instance (case-sensitive).
postgres_databaseYesSource database on the Postgres instance.
target_databaseYesSnowflake database to create.
postgres_tablesAt least one of postgres_tables or postgres_schemas; both are allowedFully qualified source table names (schema.table).
postgres_schemasAt least one of postgres_tables or postgres_schemas; both are allowedSource schema names. All current and future tables in the schema are included.
refresh_intervalNoMerge cadence. Accepted values: '30 seconds', '1 minute', '10 minutes', '1 hour', '1 day'. Maximum value is '1 day'. Default: '10 minutes'.
target_table_typeNoPhysical format of the target tables. SNOWFLAKE (default) creates standard Snowflake tables. ICEBERG creates Snowflake-managed Iceberg tables, on Snowflake-managed storage unless external_volume is set.
warehouseNoRun the apply task on this user-managed warehouse instead of serverless compute. The apply task runs as the snowflake application, so grant USAGE on the warehouse to APPLICATION snowflake first.
external_volumeNoExternal volume for Iceberg target tables. Valid only with target_table_type => 'ICEBERG'. Default NULL uses Snowflake-managed storage. Grant USAGE on the volume to APPLICATION snowflake first. The volume is fixed after create; switching volumes means dropping and recreating the mirror.

ALTER_MIRROR parameters

At least one of refresh_interval, warehouse, add_tables/add_schemas, or remove_tables/remove_schemas is required.

ParameterRequiredDescription
mirror_nameYesName of an existing mirror.
refresh_intervalOne ofNew merge cadence. Same accepted values as create_mirror.
add_tablesOne ofFully qualified source table names to add.
remove_tablesOne ofFully qualified source table names to remove.
add_schemasOne ofSource schema names to add. All current and future tables in the schema are included.
remove_schemasOne ofSource schema names to remove.
warehouseOne ofWarehouse for the apply task. A warehouse name converts a serverless apply task to user-managed. NULL converts the task back to serverless. Omit the parameter to leave compute unchanged. NULL does not mean “leave this alone.” Grant USAGE on the warehouse to APPLICATION snowflake first.

DROP_MIRROR parameters

ParameterRequiredDescription
mirror_nameYesName of an existing mirror.
drop_target_databaseNoWhen TRUE (the default), the target database is dropped with the mirror. Pass FALSE to keep the database and its target tables.

DESCRIBE_MIRROR result columns

In addition to configuration and health columns (status, error_message, last_operation_time, recent_query_durations, postgres_status, and others), describe_mirror returns:

ColumnTypeDescription
feed_onlyBOOLEANWhen true, changes are ingested but not applied to target tables.
apply_lag_secondsNUMBERApply-hop lag in seconds (flushed on Postgres to applied on the target). Never NULL. 0 when the target has applied every flushed operation. Add postgres_status.cdc_flush_lag_seconds for an end-to-end time lag.
apply_lag_operationsNUMBERApply-hop lag in operations. 0 when caught up. NULL when the source is unreachable or running snowflake_cdc older than 1.4.
apply_lag_bytesNUMBERApply-hop lag in source WAL bytes. 0 when caught up. NULL when either watermark is unknown.
last_applied_commit_lsnNUMBERSource WAL position of the newest commit applied to the target. Use this for read-your-writes checks. NULL until the first change batch is applied.
last_applied_commit_timeTIMESTAMP_NTZWhen the commit behind last_applied_commit_lsn happened. Stays put on an idle mirror.
external_volumeSTRINGExternal volume holding Iceberg target tables, or NULL for Snowflake-managed storage.

LIST_MIRRORS result columns

Includes the usual per-mirror health fields plus:

ColumnTypeDescription
initial_snapshot_tables_remainingNUMBERHow many tracked tables are still completing their initial snapshot. 0 once every table has finished its first copy. Does not count a later re-snapshot; use list_mirrored_tables for that. Cheap to read on a fleet listing.

LIST_MIRRORED_TABLES result columns

state is always present. The snapshot-detail columns are read from the source at call time; they are NULL if the source is unreachable or is running snowflake_cdc older than 1.5.

ColumnTypeDescription
source_schema_nameSTRINGSchema of the source table in Postgres.
source_table_nameSTRINGName of the source table in Postgres.
stateSTRINGCoarse target-side state: SNAPSHOTTING during initial copy, REPLICATING once caught up.
snapshot_stateSTRINGFiner source-side state: SNAPSHOTTING, ERROR, PENDING, or REPLICATING.
snapshot_percentageFLOATProgress of the running copy, 0–100. NULL when no copy is running.
snapshot_rows_processedNUMBERRows copied so far by the running snapshot.
snapshot_total_rowsNUMBEREstimated total rows for the running snapshot.
snapshot_reasonSTRINGWhy the table is snapshotting, or last did: initial_publication, table_restart, or ddl_resync.
pending_sinceTIMESTAMP_NTZWhen the current snapshot was queued.
last_snapshot_errorSTRINGMost recent snapshot error for this table, kept after recovery.
last_snapshot_error_atTIMESTAMP_NTZWhen that error occurred.

Change log schema

Every $changes table begins with system columns followed by the source table’s data columns:

ColumnTypeDescription
_commit_lsnbigintLSN of the commit that produced the row. Shared by every row in the same transaction.
_lsnbigintLSN of the row itself. Unique per change within a transaction.
_xidbigintSource transaction ID.
_commit_timetimestamptzCommit timestamp of the source transaction.
_change_typeVARCHARS for initial snapshot, I for insert, D for delete. An UPDATE is emitted as a D/I pair.
_is_updatebooleanTrue on the I half of an UPDATE; false on pure INSERT rows and on the D half of an UPDATE.
_data_versionintIncrements when the table’s data generation changes (TRUNCATE, add/drop primary key).

You can query the change log from the Snowflake side:

-- On Snowflake (auto-refreshed copy in the target database):
SELECT _commit_lsn, _change_type, _is_update, id, name
FROM orders_db.public.orders$changes
ORDER BY _commit_lsn DESC, _lsn DESC
LIMIT 20;