Every Snowflake account already contains a full set of disposable, production-sized development environments. Most teams never use them, and instead keep a hand-loaded DEV database that drifted away from production eighteen months ago. Zero-copy cloning and Time Travel are the two features that fix this, and they are the cheapest features in the platform: a clone costs nothing at creation, and Time Travel is storage you are mostly paying for already.
This tutorial covers the mechanics, the three patterns we deploy on nearly every engagement (environment refresh, CI fixtures, and "oops" recovery), and the storage-cost traps that make finance ask questions.
How cloning actually works
Snowflake stores table data in immutable micro-partitions. A clone does not copy those files; it copies metadata — a new object that points at the same partitions. Storage only grows when you write, and then only for the partitions you change.
CREATE DATABASE dev_analytics CLONE prod_analytics;
That statement is near-instant on a 40 TB database and adds roughly zero bytes of billable storage on day one.
What you can clone: databases, schemas, tables, streams, stages (external stages only), tasks, and pipes. What comes along for the ride: child objects, table data, and most table-level properties. What does not come along:
- Grants on the cloned object itself. Privileges on child objects inside a cloned database are copied; the grants on the top-level object you cloned are not. Plan a re-grant step.
- Internal named stage contents. External stage definitions clone; files in internal stages do not.
- Load history. A cloned table has no
COPYload history, so re-running aCOPY INTOagainst the clone will reload files. - Snowpipe pipes in a running state. Cloned pipes are paused; resume deliberately.
Clones are independent from the moment they exist. Dropping the source does not harm the clone, and Snowflake keeps the underlying partitions alive as long as any clone or Time Travel window references them — which is the root of the cost trap covered below.
Time Travel: the other half
Time Travel lets you query, clone, or restore an object as it existed at a point in the retention window.
-- what did this table look like before the bad load?
SELECT * FROM orders AT (OFFSET => -60*30); -- 30 minutes ago
SELECT * FROM orders BEFORE (STATEMENT => '01b2c3d4-0000-abcd-0000-000000000001');
SELECT * FROM orders AT (TIMESTAMP => '2026-02-11 08:00:00'::timestamp_ltz);
Retention is controlled by DATA_RETENTION_TIME_IN_DAYS: 0–1 day on Standard Edition, 0–90 days on Enterprise and above. It is inherited account → database → schema → table, and it can be overridden at any level.
ALTER ACCOUNT SET DATA_RETENTION_TIME_IN_DAYS = 1;
ALTER DATABASE prod_analytics SET DATA_RETENTION_TIME_IN_DAYS = 7; -- curated layer
ALTER SCHEMA prod_analytics.staging SET DATA_RETENTION_TIME_IN_DAYS = 0; -- rebuildable
Beyond Time Travel sits Fail-safe: a non-configurable 7-day period on permanent tables during which only Snowflake Support can recover data. You cannot query it, you cannot disable it, and you pay for the storage. Transient and temporary tables have no Fail-safe — which is exactly why staging layers should be transient.
Pattern 1: environment refresh that takes a minute, not a weekend
Stop maintaining dev data. Rebuild it from production on a schedule.
CREATE OR REPLACE PROCEDURE ops.refresh_dev()
RETURNS STRING
LANGUAGE SQL
AS
$$
BEGIN
CREATE OR REPLACE DATABASE dev_analytics CLONE prod_analytics;
-- re-apply environment-specific grants
GRANT USAGE ON DATABASE dev_analytics TO ROLE developer;
GRANT USAGE ON ALL SCHEMAS IN DATABASE dev_analytics TO ROLE developer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN DATABASE dev_analytics TO ROLE developer;
-- suspend cloned tasks so dev does not double-run production pipelines
ALTER TASK IF EXISTS dev_analytics.pipelines.load_orders SUSPEND;
RETURN 'dev refreshed';
END;
$$;
CREATE OR REPLACE TASK ops.refresh_dev_weekly
WAREHOUSE = ops_wh
SCHEDULE = 'USING CRON 0 5 * * SUN America/Denver'
AS CALL ops.refresh_dev();
Two cautions. First, cloned tasks and pipes: verify their state after every clone, or your dev environment will start writing to shared external locations. Second, masking and row access policies are cloned by reference; if the policy lives in a production database that developers cannot see, the clone's columns may error or fully mask. Keep governance policies in a dedicated, widely readable governance database so clones behave.
If production contains regulated data, clone and then rewrite the sensitive columns in the clone, or apply a dev-only masking policy immediately after the clone, before granting access.
Pattern 2: CI fixtures and blue/green deployments
Cloning is what makes real pre-deployment testing affordable. A pipeline run looks like this:
-- 1. branch the data
CREATE DATABASE ci_pr_482 CLONE prod_analytics;
-- 2. deploy the branch's DDL into the clone (CREATE OR ALTER, dbt, schemachange...)
-- run the transformation suite and data tests against real volumes
-- 3. tear it down
DROP DATABASE ci_pr_482;
Because nothing was copied, a hundred pull requests a week cost essentially nothing beyond the compute they use and the partitions they rewrite. This pairs directly with Git-integrated deployments: the clone is your test target, and the same script that runs against ci_pr_482 runs against production on merge.
The blue/green variant for a risky migration:
CREATE DATABASE analytics_green CLONE analytics; -- blue stays live
-- migrate, validate, reconcile row counts and key aggregates in green
ALTER DATABASE analytics RENAME TO analytics_blue;
ALTER DATABASE analytics_green RENAME TO analytics; -- cutover
-- keep blue for a week as an instant rollback, then drop it
Pattern 3: recovery without a ticket
The three recovery moves worth memorising:
-- undo a dropped object (within the retention window)
UNDROP TABLE orders;
UNDROP SCHEMA staging;
UNDROP DATABASE prod_analytics;
-- restore a table to a prior state
CREATE OR REPLACE TABLE orders_restored CLONE orders BEFORE (STATEMENT => '<bad_statement_id>');
-- surgical repair: put back only the rows the bad MERGE destroyed
MERGE INTO orders t
USING (SELECT * FROM orders BEFORE (OFFSET => -60*45)) s
ON t.order_id = s.order_id
WHEN MATCHED AND t.status <> s.status THEN UPDATE SET t.status = s.status
WHEN NOT MATCHED THEN INSERT VALUES (s.order_id, s.customer_id, s.status, s.amount, s.updated_at);
Find the offending statement id in SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY (or INFORMATION_SCHEMA.QUERY_HISTORY for the last 7 days), filtering on the table and the window in question. Note that CREATE OR REPLACE TABLE and TRUNCATE are both recoverable this way — CREATE OR REPLACE starts a new object's history but the previous version is still addressable via UNDROP after dropping the new one. Practise this on a scratch table before the day you need it.
The storage trap
Here is the bill people do not expect. Deleted partitions stay billable while any Time Travel window or clone references them. A 20 TB production database with 90-day retention, plus six long-lived clones whose tables have been fully rewritten by nightly CREATE OR REPLACE, can quietly multiply storage.
Diagnose it:
SELECT table_catalog, table_schema, table_name,
active_bytes/POWER(1024,4) AS active_tb,
time_travel_bytes/POWER(1024,4) AS tt_tb,
failsafe_bytes/POWER(1024,4) AS fs_tb,
retained_for_clone_bytes/POWER(1024,4) AS clone_tb
FROM snowflake.account_usage.table_storage_metrics
WHERE deleted = FALSE
ORDER BY tt_tb + fs_tb + clone_tb DESC
LIMIT 50;
Four rules that keep it sane:
- Retention by layer, not by account. 0–1 day for raw/staging, 7 days for curated marts, 30–90 days only for the handful of tables where a silent corruption could go unnoticed for weeks.
- Transient for anything rebuildable. No Fail-safe, no 7-day tail. Staging, sandbox, and most clone-based dev databases should be transient (
CREATE TRANSIENT DATABASE dev_analytics CLONE ...— note you cannot clone a permanent table into a transient one in the other direction). - Expire clones. Tag long-lived clones with an owner and a drop date, and run a weekly task that drops anything past it. Abandoned clones are the single most common source of mystery storage growth.
- Prefer incremental writes over full
CREATE OR REPLACEin clone-heavy environments; replacing a table detaches every partition into Time Travel at once.
Where to start
Pick one thing this week: set retention per layer using the table above, then replace your hand-built dev database with a cloned one and a weekly refresh task. Those two changes usually pay for themselves in the first month, and they turn "can we test this against real data?" from a project into a CREATE DATABASE statement.
If you want a second pair of eyes on environment strategy, clone hygiene, or a blue/green migration plan, our Snowflake consultants do this work daily — get in touch with your scope and timeline.