Skip to main content
idle · snowflake

Snowflake tables over 100 GB with no reads in 90 days

resource types
1
rule IDs covered
1
severity
medium

What does ZopNight detect here?

A Snowflake table with zero reads across 90 days of ACCESS_HISTORY and QUERY_HISTORY, sitting on more than 100 GB of active storage, is paying for data nobody queries. Archiving to an external stage and dropping the table reclaims that storage entirely.

Signal and threshold

How ZopNight evaluates Snowflake tables over 100 GB with no reads in 90 days.
Field Value
Rule IDssnowflake_table_cold_90d
Categoryidle
Severitymedium
Metricnone — pure configuration read
Sourcesignals/idle_compute/snowflake-table-archive.yaml

Two conditions, both hard

snowflake.table.access_count summed over 90 days must equal exactly zero, and snowflake.table.active_bytes must exceed 100 GB.

The zero is a hard equality rather than a threshold, and that is deliberate: the metric defaults to 0 when it is missing. A table whose access data has not been ingested looks identical to a table nobody touches, so the rule treats “no evidence of reads” and “zero reads” the same way, which makes the 90-day window and the size floor the real safeguards.

Why 100 GB

Below that, the storage saving does not justify the operational cost of archiving and the risk of needing the table back. Snowflake storage is inexpensive per terabyte; the finding only becomes material at scale.

Archive rather than delete

The remediation is to unload the table to an external stage (S3, GCS or Azure Blob) and then drop it. The data stays available at object-storage rates, which are a fraction of Snowflake active storage, and can be reloaded if it turns out somebody did need it.

That path matters because a dropped Snowflake table is recoverable only within Time Travel, and after that only from Fail-safe via support. Unloading first turns an irreversible action into a reversible one.

Joining TABLE_STORAGE_METRICS to ACCESS_HISTORY

Terminal window
SELECT t.table_catalog, t.table_schema, t.table_name,
t.active_bytes/POW(1024,3) AS active_gb,
MAX(a.query_start_time) AS last_access
FROM snowflake.account_usage.table_storage_metrics t
LEFT JOIN snowflake.account_usage.access_history a
ON a.objects_accessed[0]:objectName::string =
t.table_catalog||'.'||t.table_schema||'.'||t.table_name
WHERE t.active_bytes > 100*POW(1024,3)
GROUP BY 1,2,3,4
HAVING last_access IS NULL OR last_access < DATEADD(day,-90,CURRENT_TIMESTAMP());

ACCESS_HISTORY has a latency of up to a few hours and its own retention limit, so cross-check anything the query returns before dropping it.

See it fire on your bill.

Connect an account read-only. The first findings land in minutes.

417 rule families across 353 resource types on 22 platforms. Every threshold, metric, and IAM action is documented on these pages before you grant anything.

417 rule families documented
353 resource types covered
read-only default access level
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·