snowflake-pack
Claude Code skill pack for Snowflake (30 skills)
Installation
Open Claude Code and run this command:
/plugin install snowflake-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> 30 production-grade Claude Code skills for Snowflake data platform development — real snowflake-sdk, snowflake-connector-python, and Snowflake SQL patterns.
Skills (30)
'Apply advanced Snowflake debugging with query profiling, spill analysis,.
Snowflake Advanced Troubleshooting
Overview
Deep debugging techniques for complex Snowflake issues: query profile analysis, spill detection, lock contention, transaction conflicts, and metadata operation bottlenecks.
Instructions
Step 1: Query Profile Deep Dive
-- Get detailed execution stats for a specific query
SELECT *
FROM TABLE(GET_QUERY_OPERATOR_STATS('<query_id>'));
-- Key operators to look for:
-- TableScan: Check partitions_scanned vs partitions_total
-- Sort: Check spilling_to_local_storage, spilling_to_remote_storage
-- Join: Check type (broadcast vs hash), probe/build side sizes
-- Aggregate: Check if grouping cardinality causes spill
-- Identify queries with excessive spilling
SELECT query_id, query_text,
bytes_spilled_to_local_storage / 1e9 AS local_spill_gb,
bytes_spilled_to_remote_storage / 1e9 AS remote_spill_gb,
total_elapsed_time / 1000 AS seconds,
warehouse_size
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE (bytes_spilled_to_local_storage > 0 OR bytes_spilled_to_remote_storage > 0)
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
ORDER BY bytes_spilled_to_remote_storage DESC
LIMIT 20;
-- Remote spill = warehouse too small for the query
-- Fix: Scale up warehouse or optimize query to reduce intermediate data
Step 2: Lock and Transaction Contention
-- Find blocked/waiting queries
SELECT query_id, query_text, blocked_query_id,
DATEDIFF('second', start_time, CURRENT_TIMESTAMP()) AS wait_seconds,
user_name, warehouse_name
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE execution_status = 'BLOCKED'
AND start_time >= DATEADD(minutes, -30, CURRENT_TIMESTAMP());
-- Find long-running transactions (holding locks)
SELECT *
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE execution_status = 'RUNNING'
AND DATEDIFF('minute', start_time, CURRENT_TIMESTAMP()) > 30
ORDER BY start_time;
-- Kill a blocking query
SELECT SYSTEM$CANCEL_QUERY('<blocking_query_id>');
-- Common lock scenarios:
-- DDL on table blocks DML (ALTER TABLE blocks INSERT)
-- Concurrent MERGE on same table → serialization
-- Long COPY INTO blocks other COPY INTO on same table
Step 3: Metadata Operation Analysis
-- Cloud services credit spike (metadata-heavy operations)
SELECT DATE_TRUNC('hour', start_time) AS hour,
SUM(credits_used_cloud_services) AS cloud_credits,
COUNT(*) AS query_count,
SUM(credits_used_cloud_services) / NULLIF(COUNT(*), 0) AS credits_per_query
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
AND credits_used_cloud_services > 0
GROUP BY hour
ORDER BY cloud_credits DESC;
-- Excessive metadata queries (SHOW, DESCRIBE, INFORMATION_SCHEMA)
SELECT q'Choose and implement Snowflake architecture blueprints: data lakehouse,.
Snowflake Architecture Variants
Overview
Three validated architecture blueprints for Snowflake deployments: traditional data warehouse, lakehouse with Iceberg, and data mesh with data sharing.
Variant A: Traditional Data Warehouse
Best for: Single team, centralized analytics, < 50 users
┌──────────────────────────┐
│ Snowflake Account │
│ │
│ ┌────────┐ ┌────────┐ │
│ │ Bronze │→ │ Silver │→ Gold │
│ └────────┘ └────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Single ETL Warehouse │ │
│ └─────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ BI Tools │ │ Analysts │ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────┘
-- Simple single-account setup
CREATE DATABASE DW;
CREATE SCHEMA DW.RAW;
CREATE SCHEMA DW.CURATED;
CREATE SCHEMA DW.ANALYTICS;
CREATE WAREHOUSE ETL_WH WAREHOUSE_SIZE = 'MEDIUM' AUTO_SUSPEND = 120;
CREATE WAREHOUSE QUERY_WH WAREHOUSE_SIZE = 'SMALL' AUTO_SUSPEND = 60;
Variant B: Lakehouse with Iceberg Tables
Best for: Hybrid cloud/on-prem, existing data lake, open table format requirement
┌──────────────────────┐ ┌─────────────────────┐
│ External Storage │ │ Snowflake Account │
│ (S3/GCS/Azure) │ │ │
│ │ │ ┌────────────────┐ │
│ ┌─────────────┐ │←───→│ │ Iceberg Tables │ │
│ │ Parquet/ │ │ │ │ (managed) │ │
│ │ Iceberg │ │ │ └────────────────┘ │
│ │ files │ │ │ │
│ └─────────────┘ │ │ ┌────────────────┐ │
│ │ │ │ Native Tables │ │
│ ┌─────────────┐ │ │ │ (hot data) │ │
│ │ Spark/Flink │ │ │ └────────────────┘ │
│ │ (external) │ │ │ │
│ └─────────────┘ │ │ ┌────────────────┐ │
└──────────────────────┘ │ │ Dynamic Tables │ │
│ │ (transforms) │ │
│ └────────────────┘ │
└──────────────────────┘
-- Iceberg table backed by external storage
CREATE ICEBERG TABLE events_iceberg (
event_id STRING,
event_type STRING,
event_data VARIANT,
event_timestamp TIMESTAMP_NTZ
)
CATALOG = 'SNOWFLAKE'
EXTERNAL_VOLUME = 'my_s3_volume'
BASE_LOCATION = 'iceberg/events/';
-- External volume for S3
CREATE EXTERNAL VOLUME my_s3_volume
STORAGE_LOCATIONS = (
(NAME = 'primary'
STORAGE_BASE_URL = 's3://my-data-lake/'
STORAGE_PROVIDER = 'S3'
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789:role/snowflake-iceberg')
);
-- Dynamic Iceberg table f'Configure Snowflake CI/CD with GitHub Actions, SchemaChange, and Terraform.
Snowflake CI Integration
Overview
Set up CI/CD for Snowflake using SchemaChange for migrations, GitHub Actions for automation, and Terraform for infrastructure.
Prerequisites
- GitHub repository with Actions enabled
- Snowflake service account with key pair auth
- SchemaChange or Terraform installed
Instructions
Step 1: SchemaChange for Database Migrations
# Install SchemaChange
pip install schemachange
# Directory structure
migrations/
├── V1.0.0__initial_schema.sql # Versioned (run once, in order)
├── V1.1.0__add_orders_table.sql
├── V1.2.0__add_customer_segments.sql
├── R__views.sql # Repeatable (re-run on every change)
├── R__stored_procedures.sql
└── A__cleanup_temp_tables.sql # Always run
-- V1.0.0__initial_schema.sql
CREATE DATABASE IF NOT EXISTS {{database}};
CREATE SCHEMA IF NOT EXISTS {{database}}.{{schema}};
CREATE TABLE IF NOT EXISTS {{database}}.{{schema}}.users (
id INTEGER AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
created_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
-- V1.1.0__add_orders_table.sql
CREATE TABLE IF NOT EXISTS {{database}}.{{schema}}.orders (
order_id INTEGER AUTOINCREMENT,
user_id INTEGER REFERENCES {{database}}.{{schema}}.users(id),
amount DECIMAL(12,2),
order_date TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
# Run migrations locally
schemachange deploy \
--root-folder migrations \
--snowflake-account $SNOWFLAKE_ACCOUNT \
--snowflake-user $SNOWFLAKE_USER \
--snowflake-private-key-path ./rsa_key.p8 \
--snowflake-warehouse DEV_WH_XS \
--snowflake-database DEV_DB \
--snowflake-schema PUBLIC \
--change-history-table SCHEMACHANGE.CHANGE_HISTORY \
--create-change-history-table \
--vars '{"database": "DEV_DB", "schema": "PUBLIC"}'
Step 2: GitHub Actions Workflow
# .github/workflows/snowflake-deploy.yml
name: Snowflake Deploy
on:
push:
branches: [main]
paths: ['migrations/**']
pull_request:
branches: [main]
paths: ['migrations/**']
env:
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
jobs:
validate:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install schemachange
- name: Dry-run migrations against staging
env:
SNOWFLAKE_PRIVATE_KEY: ${{ secrets.SNOWFLAKE_PRIVATE_KEY }}
run: |
echo "$SNOWFLAKE_PRIVATE_KEY" > /tmp/rsa_key.p8
schemachange deploy \
--roo'Diagnose and fix common Snowflake errors and SQL compilation failures.
Snowflake Common Errors
Overview
Quick reference for the most common Snowflake error codes, SQL compilation errors, and driver issues with real solutions.
Error Reference
002003 (42S02): Object Does Not Exist
SQL compilation error: Object 'MY_DB.MY_SCHEMA.USERS' does not exist or not authorized.
Causes: Table doesn't exist, wrong database/schema context, or role lacks privileges.
Solutions:
-- Check current context
SELECT CURRENT_DATABASE(), CURRENT_SCHEMA(), CURRENT_ROLE();
-- Verify object exists
SHOW TABLES LIKE 'USERS' IN SCHEMA MY_DB.MY_SCHEMA;
-- Grant access if needed
GRANT SELECT ON TABLE MY_DB.MY_SCHEMA.USERS TO ROLE MY_ROLE;
-- Use fully-qualified names to avoid context issues
SELECT * FROM MY_DB.MY_SCHEMA.USERS;
000606: No Active Warehouse
SQL execution error: No active warehouse selected in the current session.
Solutions:
-- Set warehouse for session
USE WAREHOUSE COMPUTE_WH;
-- Or set in connection config
-- warehouse: 'COMPUTE_WH' in createConnection()
-- Check warehouse state
SHOW WAREHOUSES LIKE 'COMPUTE_WH';
-- If SUSPENDED, it auto-resumes if AUTO_RESUME = TRUE
390100: Incorrect Username or Password
Incorrect username or password was specified.
Solutions:
# Verify credentials are set
echo $SNOWFLAKE_ACCOUNT # Should be 'orgname-accountname'
echo $SNOWFLAKE_USER
# Test with SnowSQL
snowsql -a $SNOWFLAKE_ACCOUNT -u $SNOWFLAKE_USER
# Check account format — common mistake:
# Wrong: myaccount.us-east-1.snowflakecomputing.com
# Right: myorg-myaccount
390144: JWT Token Invalid (Key Pair Auth)
JWT token is invalid.
Solutions:
# Verify public key is assigned
# Run in Snowflake:
# DESC USER my_user;
# Check RSA_PUBLIC_KEY column
# Regenerate if needed
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
# Re-assign (remove headers/newlines from pub key first)
# ALTER USER my_user SET RSA_PUBLIC_KEY='MIIBIj...';
001003: SQL Compilation Error
SQL compilation error: syntax error line X at position Y unexpected 'TOKEN'.
Common causes:
-- Missing semicolons in multi-statement mode
-- Wrong: SELECT 1 SELECT 2
-- Right: SELECT 1; SELECT 2;
-- Reserved word used as identifier
-- Wrong: SELECT order FROM orders
-- Right: SELECT "order" FROM orders
-- Wrong function syntax
-- Wrong: DATEADD('day', 1, col)
-- Right: DATEADD(day, 1, col) -- no quo'Execute Snowflake primary workflow: data loading via stages and COPY.
Snowflake Core Workflow A — Data Loading
Overview
Primary data loading workflow: stages, file formats, COPY INTO, and Snowpipe for continuous ingestion.
Prerequisites
- Completed
snowflake-install-authsetup - Target table created in Snowflake
- Source data in S3, GCS, Azure Blob, or local files
- Role with
CREATE STAGEandUSAGEon warehouse
Instructions
Step 1: Create a File Format
-- CSV format
CREATE OR REPLACE FILE FORMAT my_csv_format
TYPE = 'CSV'
FIELD_DELIMITER = ','
SKIP_HEADER = 1
NULL_IF = ('NULL', 'null', '')
EMPTY_FIELD_AS_NULL = TRUE
FIELD_OPTIONALLY_ENCLOSED_BY = '"'
ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE;
-- JSON format (for semi-structured data)
CREATE OR REPLACE FILE FORMAT my_json_format
TYPE = 'JSON'
STRIP_OUTER_ARRAY = TRUE
IGNORE_UTF8_ERRORS = TRUE;
-- Parquet format
CREATE OR REPLACE FILE FORMAT my_parquet_format
TYPE = 'PARQUET'
SNAPPY_COMPRESSION = TRUE;
Step 2: Create a Stage
-- External stage (S3)
CREATE OR REPLACE STAGE my_s3_stage
STORAGE_INTEGRATION = my_s3_integration
URL = 's3://my-bucket/data/'
FILE_FORMAT = my_csv_format;
-- External stage (GCS)
CREATE OR REPLACE STAGE my_gcs_stage
STORAGE_INTEGRATION = my_gcs_integration
URL = 'gcs://my-bucket/data/'
FILE_FORMAT = my_csv_format;
-- Internal stage (Snowflake-managed storage)
CREATE OR REPLACE STAGE my_internal_stage
FILE_FORMAT = my_csv_format;
-- List files in stage
LIST @my_s3_stage;
Step 3: Upload Files to Internal Stage
# Using SnowSQL PUT command
snowsql -c prod -q "PUT @my_internal_stage AUTO_COMPRESS=TRUE"
# Using Python connector
cursor.execute("PUT @my_internal_stage AUTO_COMPRESS=TRUE")
Step 4: Load Data with COPY INTO
-- Basic COPY INTO from stage
COPY INTO my_db.my_schema.users
FROM @my_s3_stage/users/
FILE_FORMAT = my_csv_format
ON_ERROR = 'CONTINUE' -- Skip bad rows
PURGE = TRUE; -- Delete files after load
-- COPY with column mapping
COPY INTO my_db.my_schema.orders (order_id, customer_id, amount, order_date)
FROM (
SELECT $1, $2, $3::FLOAT, $4::TIMESTAMP_NTZ
FROM @my_s3_stage/orders/
)
FILE_FORMAT = my_csv_format;
-- Load JSON into VARIANT column
COPY INTO my_db.my_schema.raw_events
FROM @my_s3_stage/events/
FILE_FORMAT = my_json_format
MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE;
-- Check COPY history
SELECT * FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(
TABLE_NAME => 'USERS',
START_TIME => DATEADD(hours, -24, CURRENT_TIMESTAMP())
));
Step 5: Programmatic
'Execute Snowflake data transformation with streams, tasks, and dynamic.
Snowflake Core Workflow B — Data Transformation
Overview
Build ELT pipelines using streams (change data capture), tasks (scheduling), and dynamic tables (declarative transforms).
Prerequisites
- Data loaded into Snowflake (via
snowflake-core-workflow-a) - Understanding of ELT vs ETL patterns
- Role with
CREATE TASK,CREATE STREAMprivileges
Instructions
Step 1: Create a Stream for Change Data Capture
-- Track changes on the raw orders table
CREATE OR REPLACE STREAM orders_stream ON TABLE raw_orders
APPEND_ONLY = FALSE;
-- Append-only stream (lighter weight, inserts only)
CREATE OR REPLACE STREAM events_stream ON TABLE raw_events
APPEND_ONLY = TRUE;
-- Check what's changed since last consumption
SELECT * FROM orders_stream;
-- METADATA$ACTION = 'INSERT' | 'DELETE'
-- METADATA$ISUPDATE = TRUE if row is part of an UPDATE
-- METADATA$ROW_ID = unique row identifier
Step 2: Create a Task to Process Stream Data
-- Transform task runs when stream has data
CREATE OR REPLACE TASK transform_orders
WAREHOUSE = TRANSFORM_WH
SCHEDULE = '5 MINUTE'
WHEN SYSTEM$STREAM_HAS_DATA('orders_stream')
AS
MERGE INTO dim_orders AS target
USING (
SELECT
order_id,
customer_id,
amount::DECIMAL(12,2) AS amount,
order_date::TIMESTAMP_NTZ AS order_date,
CASE
WHEN amount >= 1000 THEN 'high_value'
WHEN amount >= 100 THEN 'medium_value'
ELSE 'standard'
END AS order_tier,
CURRENT_TIMESTAMP() AS processed_at
FROM orders_stream
WHERE METADATA$ACTION = 'INSERT'
) AS source
ON target.order_id = source.order_id
WHEN MATCHED THEN UPDATE SET
target.amount = source.amount,
target.order_tier = source.order_tier,
target.processed_at = source.processed_at
WHEN NOT MATCHED THEN INSERT
(order_id, customer_id, amount, order_date, order_tier, processed_at)
VALUES
(source.order_id, source.customer_id, source.amount,
source.order_date, source.order_tier, source.processed_at);
-- Enable the task
ALTER TASK transform_orders RESUME;
Step 3: Build a Task DAG (Directed Acyclic Graph)
-- Root task: aggregate daily metrics
CREATE OR REPLACE TASK daily_metrics_root
WAREHOUSE = TRANSFORM_WH
SCHEDULE = 'USING CRON 0 6 * * * America/New_York'
AS
INSERT INTO daily_order_metrics
SELECT
CURRENT_DATE() - 1 AS metric_date,
COUNT(*) AS total_orders,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value,
COUNT(DISTINCT customer_id) AS unique_customers
FROM dim_orders
WHERE order_date >= CURRENT_DATE() - 1
AND order_date < CURRENT_DATE();
-- Child task: runs after root completes
CREATE OR REPLACE TASK update_cust'Optimize Snowflake costs with resource monitors, warehouse auto-suspend,.
Snowflake Cost Tuning
Overview
Optimize Snowflake costs through resource monitors, warehouse right-sizing, auto-suspend tuning, and credit consumption analysis.
Snowflake Pricing Model
| Cost Component | What It Measures | Typical % of Bill |
|---|---|---|
| Compute (credits) | Warehouse running time | 60-80% |
| Storage | Data at rest (compressed) | 10-20% |
| Cloud services | Metadata ops, auth, compilation | 5-10% |
| Data transfer | Egress between regions/clouds | 0-5% |
| Serverless | Snowpipe, auto-clustering, MV refresh | Variable |
Credit rates by warehouse size:
| Size | Credits/Hour | Nodes |
|---|---|---|
| X-Small | 1 | 1 |
| Small | 2 | 2 |
| Medium | 4 | 4 |
| Large | 8 | 8 |
| X-Large | 16 | 16 |
| 2X-Large | 32 | 32 |
Instructions
Step 1: Analyze Current Credit Consumption
-- Credits by warehouse (last 30 days)
SELECT warehouse_name,
SUM(credits_used) AS total_credits,
SUM(credits_used_compute) AS compute_credits,
SUM(credits_used_cloud_services) AS cloud_credits,
ROUND(SUM(credits_used) * 3.0, 2) AS est_cost_usd -- ~$3/credit standard
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(days, -30, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY total_credits DESC;
-- Daily credit trend
SELECT DATE_TRUNC('day', start_time) AS day,
SUM(credits_used) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(days, -30, CURRENT_TIMESTAMP())
GROUP BY day
ORDER BY day;
-- Idle warehouse time (credits wasted while no queries running)
SELECT warehouse_name,
SUM(credits_used) AS total_credits,
COUNT(DISTINCT query_id) AS queries,
CASE WHEN COUNT(DISTINCT query_id) = 0 THEN SUM(credits_used)
ELSE 0 END AS idle_credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY w
LEFT JOIN SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY q
ON w.warehouse_name = q.warehouse_name
AND DATE_TRUNC('hour', w.start_time) = DATE_TRUNC('hour', q.start_time)
WHERE w.start_time >= DATEADD(days, -7, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY idle_credits DESC;
Step 2: Set Up Resource Monitors
-- Account-level resource monitor
CREATE OR REPLACE RESOURCE MONITOR account_monthly
WITH CREDIT_QUOTA = 5000
FREQUENCY = MONTHLY
START_TIMESTAMP = IMMEDIATELY
TRIGGERS
ON 50 PERCE'Implement Snowflake data governance with masking policies, row access.
Snowflake Data Handling
Overview
Implement data governance in Snowflake using column-level masking policies, row access policies, object tagging, and data classification for GDPR/CCPA compliance.
Prerequisites
- Enterprise Edition or higher (for masking and row access policies)
- SECURITYADMIN or ACCOUNTADMIN role
- Understanding of GDPR/CCPA data subject rights
Instructions
Step 1: Data Classification with Tags
-- Create tag taxonomy
CREATE TAG IF NOT EXISTS pii_type
ALLOWED_VALUES 'email', 'phone', 'ssn', 'name', 'address';
CREATE TAG IF NOT EXISTS data_sensitivity
ALLOWED_VALUES 'public', 'internal', 'confidential', 'restricted';
-- Apply tags to columns
ALTER TABLE users MODIFY COLUMN email SET TAG pii_type = 'email';
ALTER TABLE users MODIFY COLUMN phone SET TAG pii_type = 'phone';
ALTER TABLE users MODIFY COLUMN name SET TAG pii_type = 'name';
ALTER TABLE users MODIFY COLUMN email SET TAG data_sensitivity = 'confidential';
-- Find all tagged columns
SELECT * FROM TABLE(INFORMATION_SCHEMA.TAG_REFERENCES(
'users', 'TABLE'
));
-- Discover PII with Snowflake's automatic classification (Enterprise+)
SELECT *
FROM TABLE(
INFORMATION_SCHEMA.EXTRACT_SEMANTIC_CATEGORIES('users')
);
Step 2: Column-Level Masking Policies
-- Dynamic masking — shows real data to privileged roles, masked to others
CREATE OR REPLACE MASKING POLICY email_mask AS (val STRING)
RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() IN ('DATA_ENGINEER', 'SYSADMIN') THEN val
WHEN CURRENT_ROLE() = 'DATA_ANALYST' THEN
REGEXP_REPLACE(val, '.+@', '***@') -- Show domain only
ELSE '***MASKED***'
END;
CREATE OR REPLACE MASKING POLICY phone_mask AS (val STRING)
RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() IN ('DATA_ENGINEER', 'SYSADMIN') THEN val
ELSE CONCAT('***-***-', RIGHT(val, 4)) -- Show last 4 digits
END;
CREATE OR REPLACE MASKING POLICY ssn_mask AS (val STRING)
RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() IN ('SYSADMIN') THEN val
ELSE '***-**-' || RIGHT(val, 4)
END;
-- Apply masking policies to columns
ALTER TABLE users MODIFY COLUMN email SET MASKING POLICY email_mask;
ALTER TABLE users MODIFY COLUMN phone SET MASKING POLICY phone_mask;
-- Tag-based masking (apply policy to all columns with a tag)
ALTER TAG pii_type SET MASKING POLICY email_mask;
-- Now ALL columns tagged pii_type='email' are automatically masked
Step 3: Row Access Policies
-- Row-level security — users only see their own department's data
CREATE OR REPLACE ROW ACCESS POLICY department_access AS (department_col 'Collect Snowflake debug evidence for support tickets and troubleshooting.
Snowflake Debug Bundle
Overview
Collect diagnostic information from Snowflake's ACCOUNTUSAGE views, QUERYHISTORY, and driver logs for support tickets and troubleshooting.
Prerequisites
- Role with access to
SNOWFLAKE.ACCOUNT_USAGEschema (typically ACCOUNTADMIN) - Access to application logs
- Permission to collect environment info
Instructions
Step 1: Query-Level Diagnostics
-- Find the problematic query by ID
SELECT query_id, query_text, execution_status, error_code, error_message,
start_time, end_time, total_elapsed_time / 1000 AS elapsed_seconds,
bytes_scanned, rows_produced, compilation_time, execution_time,
warehouse_name, warehouse_size
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE query_id = '<paste-query-id-here>';
-- Recent failed queries
SELECT query_id, query_text, error_code, error_message,
start_time, user_name, role_name, warehouse_name
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE execution_status = 'FAIL'
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
ORDER BY start_time DESC
LIMIT 20;
-- Slow queries (> 60 seconds)
SELECT query_id, query_text, total_elapsed_time / 1000 AS seconds,
bytes_scanned / 1e9 AS gb_scanned, partitions_scanned, partitions_total,
warehouse_name, warehouse_size
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE total_elapsed_time > 60000
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
ORDER BY total_elapsed_time DESC
LIMIT 10;
Step 2: Connection and Session Diagnostics
-- Active sessions
SELECT session_id, user_name, created_on,
client_application_id, client_environment
FROM TABLE(INFORMATION_SCHEMA.SESSIONS())
ORDER BY created_on DESC;
-- Login history (auth failures)
SELECT event_timestamp, user_name, client_ip, reported_client_type,
error_code, error_message, is_success
FROM SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY
WHERE event_timestamp >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
AND is_success = 'NO'
ORDER BY event_timestamp DESC;
Step 3: Warehouse and Resource Diagnostics
-- Warehouse load (queued queries = undersized)
SELECT warehouse_name, start_time,
avg_running, avg_queued_load, avg_queued_provisioning, avg_blocked
FROM TABLE(INFORMATION_SCHEMA.WAREHOUSE_LOAD_HISTORY(
DATE_RANGE_START => DATEADD(hours, -4, CURRENT_TIMESTAMP())
))
ORDER BY start_time DESC;
-- Credit consumption by warehouse
SELECT warehouse_name, SUM(credits_used) AS credits,
SUM(credits_used_compute) AS compute_credits,
SUM(credits_used_cloud_services) AS cloud_credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(days, -7, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY credits DESC;
'Deploy Snowflake-powered applications with proper connection management.
Snowflake Deploy Integration
Overview
Deploy applications that connect to Snowflake on serverless platforms, containers, and VMs with proper connection lifecycle management.
Prerequisites
- Snowflake service account with key pair auth
- Platform CLI installed (gcloud, aws, docker)
- Application tested against staging Snowflake
Instructions
Step 1: Connection Management for Serverless
// src/snowflake/serverless-connection.ts
import snowflake from 'snowflake-sdk';
let cachedConnection: snowflake.Connection | null = null;
/**
* Reuse connection across Lambda/Cloud Function invocations.
* Serverless containers may be reused — avoid reconnecting every call.
*/
export async function getConnection(): Promise<snowflake.Connection> {
if (cachedConnection?.isUp()) {
return cachedConnection;
}
const conn = snowflake.createConnection({
account: process.env.SNOWFLAKE_ACCOUNT!,
username: process.env.SNOWFLAKE_USER!,
authenticator: 'SNOWFLAKE_JWT',
privateKey: process.env.SNOWFLAKE_PRIVATE_KEY!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA || 'PUBLIC',
clientSessionKeepAlive: true, // Keep session alive between invocations
});
await new Promise<void>((resolve, reject) => {
conn.connect((err) => (err ? reject(err) : resolve()));
});
cachedConnection = conn;
return conn;
}
Step 2: Google Cloud Run Deployment
# Dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
ENV NODE_ENV=production
CMD ["node", "dist/index.js"]
#!/bin/bash
# deploy-cloud-run.sh
PROJECT_ID="${GCP_PROJECT_ID}"
SERVICE_NAME="snowflake-api"
REGION="us-central1"
# Build and push
gcloud builds submit --tag gcr.io/$PROJECT_ID/$SERVICE_NAME
# Deploy with Snowflake credentials from Secret Manager
gcloud run deploy $SERVICE_NAME \
--image gcr.io/$PROJECT_ID/$SERVICE_NAME \
--region $REGION \
--platform managed \
--set-secrets="SNOWFLAKE_ACCOUNT=snowflake-account:latest,\
SNOWFLAKE_USER=snowflake-user:latest,\
SNOWFLAKE_PRIVATE_KEY=snowflake-private-key:latest" \
--set-env-vars="SNOWFLAKE_WAREHOUSE=PROD_ANALYTICS_WH,\
SNOWFLAKE_DATABASE=PROD_DB,\
SNOWFLAKE_SCHEMA=PUBLIC" \
--min-instances=1 \
--max-instances=10 \
--timeout=300
Step 3: AWS Lambda Deployment
// lambda/handler.ts
import { getConnection } from './snowflake/serverless-connection';
export async function handler(event: any) {
try {
const conn = await getConnection();
const rows = await new Promise<any[]>((resolve, rej'Configure Snowflake enterprise RBAC with system roles, custom role hierarchies,.
Snowflake Enterprise RBAC
Overview
Configure enterprise-grade access control using Snowflake's system-defined roles, custom role hierarchies, SSO via SAML/OIDC, and SCIM for automated user provisioning.
Snowflake System Roles
| Role | Purpose | Use For |
|---|---|---|
| ACCOUNTADMIN | Top-level admin | Billing, resource monitors, replication |
| SECURITYADMIN | Security management | Users, roles, grants, network policies |
| SYSADMIN | Object management | Databases, warehouses, schemas, tables |
| USERADMIN | User management | Create users and roles |
| PUBLIC | Default for all users | Minimal access, applied automatically |
Best Practice: Never use ACCOUNTADMIN as a default role. Create custom roles and grant them to SYSADMIN.
Instructions
Step 1: Design Custom Role Hierarchy
-- Functional roles (what people do)
CREATE ROLE DATA_ENGINEER;
CREATE ROLE DATA_ANALYST;
CREATE ROLE DATA_SCIENTIST;
CREATE ROLE BI_VIEWER;
CREATE ROLE APP_SERVICE; -- Service accounts
-- Access roles (what they can access)
CREATE ROLE RAW_DATA_READER;
CREATE ROLE CURATED_DATA_READER;
CREATE ROLE CURATED_DATA_WRITER;
CREATE ROLE GOLD_DATA_READER;
-- Role hierarchy (bottom-up)
-- BI_VIEWER → GOLD_DATA_READER
-- DATA_ANALYST → CURATED_DATA_READER + GOLD_DATA_READER
-- DATA_SCIENTIST → DATA_ANALYST + RAW_DATA_READER
-- DATA_ENGINEER → all access roles
-- All custom roles → SYSADMIN
GRANT ROLE GOLD_DATA_READER TO ROLE BI_VIEWER;
GRANT ROLE CURATED_DATA_READER TO ROLE DATA_ANALYST;
GRANT ROLE GOLD_DATA_READER TO ROLE DATA_ANALYST;
GRANT ROLE DATA_ANALYST TO ROLE DATA_SCIENTIST;
GRANT ROLE RAW_DATA_READER TO ROLE DATA_SCIENTIST;
GRANT ROLE RAW_DATA_READER TO ROLE DATA_ENGINEER;
GRANT ROLE CURATED_DATA_READER TO ROLE DATA_ENGINEER;
GRANT ROLE CURATED_DATA_WRITER TO ROLE DATA_ENGINEER;
GRANT ROLE GOLD_DATA_READER TO ROLE DATA_ENGINEER;
-- All custom roles under SYSADMIN
GRANT ROLE DATA_ENGINEER TO ROLE SYSADMIN;
GRANT ROLE DATA_ANALYST TO ROLE SYSADMIN;
GRANT ROLE DATA_SCIENTIST TO ROLE SYSADMIN;
GRANT ROLE BI_VIEWER TO ROLE SYSADMIN;
GRANT ROLE APP_SERVICE TO ROLE SYSADMIN;
Step 2: Grant Object Privileges
-- Access role: RAW_DATA_READER
GRANT USAGE ON DATABASE PROD_DW TO ROLE RAW_DATA_READER;
GRANT USAGE ON SCHEMA PROD_DW.BRONZE TO ROLE RAW_DATA_READER;
GRANT SELECT ON ALL TABLES IN SCHEMA PROD_DW.BRONZE TO ROLE RAW_DATA_READER;
GRANT SELECT ON FUTURE TABLES IN SCHEMA PROD_DW.BRONZE TO ROLE RAW_DATA_READER;
-- Access role: CURATED_DATA_READER
GRANT USAGE ON DATABASE PROD_DW TO ROLE CURATED_DATA_READER;
GRANT USAGE ON SCHEMA PROD_DW.SILVER TO ROLE CURATED_DATA_READER;
GRANT SELECT ON ALL TABLES IN 'Create a minimal working Snowflake example with real SQL queries.
Snowflake Hello World
Overview
Minimal working examples demonstrating core Snowflake operations: connect, query, create objects, load data.
Prerequisites
- Completed
snowflake-install-authsetup - Valid credentials configured in environment
- A warehouse available (e.g.,
COMPUTE_WH)
Instructions
Step 1: Connect and Query (Node.js)
// hello-snowflake.ts
import snowflake from 'snowflake-sdk';
const connection = snowflake.createConnection({
account: process.env.SNOWFLAKE_ACCOUNT!,
username: process.env.SNOWFLAKE_USER!,
password: process.env.SNOWFLAKE_PASSWORD!,
warehouse: 'COMPUTE_WH',
database: 'DEMO_DB',
schema: 'PUBLIC',
});
connection.connect((err) => {
if (err) {
console.error('Connection failed:', err.message);
process.exit(1);
}
console.log('Connected to Snowflake!');
// Run a simple query
connection.execute({
sqlText: `SELECT CURRENT_TIMESTAMP() AS now,
CURRENT_WAREHOUSE() AS warehouse,
CURRENT_DATABASE() AS database,
CURRENT_ROLE() AS role`,
complete: (err, stmt, rows) => {
if (err) {
console.error('Query failed:', err.message);
return;
}
console.log('Query result:', rows);
connection.destroy((err) => {
if (err) console.error('Disconnect error:', err.message);
});
},
});
});
Step 2: Connect and Query (Python)
# hello_snowflake.py
import snowflake.connector
import os
conn = snowflake.connector.connect(
account=os.environ['SNOWFLAKE_ACCOUNT'],
user=os.environ['SNOWFLAKE_USER'],
password=os.environ['SNOWFLAKE_PASSWORD'],
warehouse='COMPUTE_WH',
database='DEMO_DB',
schema='PUBLIC',
)
try:
cursor = conn.cursor()
cursor.execute("""
SELECT CURRENT_TIMESTAMP() AS now,
CURRENT_WAREHOUSE() AS warehouse,
CURRENT_DATABASE() AS database,
CURRENT_ROLE() AS role
""")
for row in cursor:
print(f"Time: {row[0]}, Warehouse: {row[1]}, DB: {row[2]}, Role: {row[3]}")
finally:
conn.close()
Step 3: Create Database Objects
-- Run via connection.execute() or snowflake worksheet
CREATE DATABASE IF NOT EXISTS DEMO_DB;
CREATE SCHEMA IF NOT EXISTS DEMO_DB.MY_SCHEMA;
CREATE OR REPLACE TABLE DEMO_DB.MY_SCHEMA.USERS (
id INTEGER AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(255),
created_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
INSERT INTO DEMO_DB.MY_SCHEMA.USERS (name, email)
VALUES ('Alice', 'alice@example.com'),
('Bob', 'bob@example.com');
'Execute Snowflake incident response with triage, rollback, and postmortem.
Snowflake Incident Runbook
Overview
Rapid incident response procedures for Snowflake infrastructure, pipeline failures, and query issues.
Severity Levels
| Level | Definition | Response Time | Examples |
|---|---|---|---|
| P1 | Complete outage | < 15 min | All queries failing, auth broken |
| P2 | Degraded service | < 1 hour | High latency, task failures |
| P3 | Minor impact | < 4 hours | Snowpipe delays, non-critical errors |
| P4 | No user impact | Next business day | Monitoring gaps, cost anomalies |
Quick Triage (First 5 Minutes)
Step 1: Is Snowflake Itself Down?
# Check Snowflake status page
curl -s https://status.snowflake.com/api/v2/summary.json | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"Status: {data['status']['description']}\")
for c in data['components']:
if c['status'] != 'operational':
print(f\" DEGRADED: {c['name']} - {c['status']}\")
"
Step 2: Can We Connect?
-- Quick connectivity test
SELECT CURRENT_TIMESTAMP(), CURRENT_ACCOUNT(), CURRENT_REGION();
-- If this fails, the issue is connectivity/auth, not query logic
Step 3: What's Failing?
-- Recent failures (last 30 minutes)
SELECT error_code, error_message, COUNT(*) AS occurrences,
MIN(start_time) AS first_seen, MAX(start_time) AS last_seen
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE execution_status = 'FAIL'
AND start_time >= DATEADD(minutes, -30, CURRENT_TIMESTAMP())
GROUP BY error_code, error_message
ORDER BY occurrences DESC;
-- Failed tasks
SELECT name, state, error_message, scheduled_time, completed_time
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(
SCHEDULED_TIME_RANGE_START => DATEADD(hours, -1, CURRENT_TIMESTAMP())
))
WHERE state = 'FAILED'
ORDER BY scheduled_time DESC;
-- Stale streams (data loss risk)
SHOW STREAMS;
-- Check STALE column — if TRUE, stream offset is beyond retention
Decision Tree
Query failures?
├─ Auth errors (390100, 390144)
│ → Check credentials, key pair, network policy
├─ Object not found (002003)
│ → Wrong context? Permissions? Object dropped?
├─ Warehouse issues (000606)
│ → Warehouse suspended? Resource monitor hit?
├─ Timeout (100038)
│ → Query too slow? Warehouse too small?
└─ Snowflake platform issue (5xx, connectivity)
→ Check status.snowflake.com → enable fallback
Pipeline failures?
├─ Task failed
│ → Check TASK_HISTORY error_message
│ → Is source stream stale?
├─ Snowpipe not loading
│ → SYSTEM$PIPE_STATUS('pipe_name')
│ → Check 'Install and configure Snowflake driver authentication for Node.
Snowflake Install & Auth
Overview
Set up Snowflake drivers and configure authentication for Node.js (snowflake-sdk) and Python (snowflake-connector-python).
Prerequisites
- Node.js 18+ or Python 3.9+
- Snowflake account (format:
or legacy- name> )locator>. - User with appropriate role granted
Instructions
Step 1: Install the Driver
# Node.js — official driver from snowflakedb
npm install snowflake-sdk
# Python — official connector
pip install snowflake-connector-python
# Python with pandas support
pip install "snowflake-connector-python[pandas]"
Step 2: Choose an Authentication Method
| Method | Use Case | Env Vars Needed |
|---|---|---|
| Password | Quick dev setup | SNOWFLAKEACCOUNT, SNOWFLAKEUSER, SNOWFLAKE_PASSWORD |
| Key Pair | CI/CD, service accounts | SNOWFLAKEACCOUNT, SNOWFLAKEUSER, SNOWFLAKEPRIVATEKEY_PATH |
| External Browser SSO | Interactive dev | SNOWFLAKEACCOUNT, SNOWFLAKEUSER |
| OAuth | Enterprise SSO integration | SNOWFLAKEACCOUNT, SNOWFLAKEOAUTH_TOKEN |
Step 3a: Password Authentication
// src/snowflake/client.ts
import snowflake from 'snowflake-sdk';
const connection = snowflake.createConnection({
account: process.env.SNOWFLAKE_ACCOUNT!, // e.g. 'myorg-myaccount'
username: process.env.SNOWFLAKE_USER!,
password: process.env.SNOWFLAKE_PASSWORD!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE || 'COMPUTE_WH',
database: process.env.SNOWFLAKE_DATABASE,
schema: process.env.SNOWFLAKE_SCHEMA || 'PUBLIC',
role: process.env.SNOWFLAKE_ROLE || 'PUBLIC',
});
connection.connect((err, conn) => {
if (err) {
console.error('Unable to connect:', err.message);
return;
}
console.log('Connected as id:', conn.getId());
});
# src/snowflake_client.py
import snowflake.connector
import os
conn = snowflake.connector.connect(
account=os.environ['SNOWFLAKE_ACCOUNT'],
user=os.environ['SNOWFLAKE_USER'],
password=os.environ['SNOWFLAKE_PASSWORD'],
warehouse=os.environ.get('SNOWFLAKE_WAREHOUSE', 'COMPUTE_WH'),
database=os.environ.get('SNOWFLAKE_DATABASE'),
schema=os.environ.get('SNOWFLAKE_SCHEMA', 'PUBLIC'),
role=os.environ.get('SNOWFLAKE_ROLE', 'PUBLIC'),
)
print(f"Connected: {conn.get_que'Identify and avoid Snowflake anti-patterns and common mistakes in SQL,.
Snowflake Known Pitfalls
Overview
Common mistakes and anti-patterns when using Snowflake, with real SQL examples and fixes.
Pitfall #1: Leaving Warehouses Running (Cost Killer)
Anti-Pattern:
-- Warehouse with auto_suspend = 0 (never suspends)
CREATE WAREHOUSE ALWAYS_ON_WH
WAREHOUSE_SIZE = 'XLARGE'
AUTO_SUSPEND = 0;
-- 16 credits/hour = ~$1,152/day at $3/credit
Fix:
ALTER WAREHOUSE ALWAYS_ON_WH SET
AUTO_SUSPEND = 120, -- Suspend after 2 min idle
AUTO_RESUME = TRUE; -- Resume on next query
-- Audit all warehouses for high auto_suspend
SELECT name, size, auto_suspend, state
FROM INFORMATION_SCHEMA.WAREHOUSES
WHERE auto_suspend > 600 OR auto_suspend = 0;
Pitfall #2: Using ACCOUNTADMIN for Everything
Anti-Pattern:
-- Human users with ACCOUNTADMIN default role
ALTER USER analyst SET DEFAULT_ROLE = 'ACCOUNTADMIN';
-- One bad query can drop production databases
Fix:
-- Use least-privilege roles
ALTER USER analyst SET DEFAULT_ROLE = 'DATA_ANALYST';
-- Audit ACCOUNTADMIN usage
SELECT grantee_name, role
FROM SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_USERS
WHERE role = 'ACCOUNTADMIN' AND deleted_on IS NULL;
-- Should be < 3 users, all named admins
Pitfall #3: SELECT * on Wide Tables
Anti-Pattern:
-- Scans ALL columns (Snowflake stores columnar — unused cols waste I/O)
SELECT * FROM events; -- 200 columns, only need 3
Fix:
-- Select only needed columns — dramatically reduces bytes scanned
SELECT event_id, event_type, event_timestamp FROM events;
-- Check column pruning impact
SELECT bytes_scanned FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY_BY_SESSION())
ORDER BY start_time DESC LIMIT 1;
Pitfall #4: Clustering Keys on Small Tables
Anti-Pattern:
-- Clustering key on a 10,000 row table
ALTER TABLE config_settings CLUSTER BY (category);
-- Costs credits for reclustering with zero performance benefit
Fix:
-- Only cluster tables > 1TB with frequent filter queries
-- Check table size before clustering
SELECT table_name, row_count, bytes / 1e9 AS gb
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name = 'CONFIG_SETTINGS';
-- If < 1 GB, clustering is waste
-- Remove unnecessary clustering
ALTER TABLE config_settings DROP CLUSTERING KEY;
Pitfall #5: Not Using MERGE for Idempotent Loads
Anti-Pattern:
-- INSERT creates duplicates on retry
INSERT INTO dim_or'Implement Snowflake load testing, warehouse scaling, and capacity planning.
Snowflake Load & Scale
Overview
Load testing, scaling strategies, and capacity planning for Snowflake workloads using warehouse sizing, multi-cluster configuration, and concurrent query simulation.
Scaling Model
| Dimension | How to Scale | When |
|---|---|---|
| Single query speed | Scale UP (bigger warehouse) | Complex queries, large scans |
| Concurrent queries | Scale OUT (multi-cluster) | Many users, dashboard refresh |
| Data volume | Scale UP + clustering | Tables > 1TB |
| Mixed workloads | Separate warehouses | ETL + analytics on same data |
Instructions
Step 1: Benchmark Current Performance
-- Baseline metrics for critical queries
-- Run each query 3 times and record results
-- Disable result cache for accurate benchmarking
ALTER SESSION SET USE_CACHED_RESULT = FALSE;
-- Test query 1: Point lookup
SELECT * FROM orders WHERE order_id = 12345;
-- Test query 2: Aggregation
SELECT DATE_TRUNC('month', order_date) AS month,
COUNT(*) AS orders, SUM(amount) AS revenue
FROM orders
WHERE order_date >= '2025-01-01'
GROUP BY month ORDER BY month;
-- Test query 3: Join + filter
SELECT c.name, SUM(o.amount) AS total_spend
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE o.order_date >= DATEADD(days, -90, CURRENT_DATE())
GROUP BY c.name
ORDER BY total_spend DESC
LIMIT 100;
-- Record results
SELECT query_id, query_text, warehouse_name, warehouse_size,
total_elapsed_time / 1000 AS seconds,
bytes_scanned / 1e9 AS gb_scanned,
rows_produced, partitions_scanned, partitions_total,
bytes_spilled_to_local_storage, bytes_spilled_to_remote_storage
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY_BY_SESSION())
ORDER BY start_time DESC
LIMIT 10;
-- Re-enable cache
ALTER SESSION SET USE_CACHED_RESULT = TRUE;
Step 2: Test Warehouse Size Impact
-- Run same query on different warehouse sizes to find optimal
-- XS → S → M → L → XL
ALTER WAREHOUSE BENCHMARK_WH SET WAREHOUSE_SIZE = 'XSMALL';
ALTER SESSION SET USE_CACHED_RESULT = FALSE;
-- Run your benchmark query
SELECT /* BENCHMARK_XS */ ...;
ALTER WAREHOUSE BENCHMARK_WH SET WAREHOUSE_SIZE = 'SMALL';
SELECT /* BENCHMARK_S */ ...;
ALTER WAREHOUSE BENCHMARK_WH SET WAREHOUSE_SIZE = 'MEDIUM';
SELECT /* BENCHMARK_M */ ...;
-- Compare results
SELECT warehouse_size, query_id,
total_elapsed_time / 1000 AS seconds,
bytes_scanned / 1e9 AS gb_scanned
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY_BY_SESSION())
WHERE query_text LIKE '%BENCHMARK_%'
ORDER BY start_time DESC;
-- Typical scaling: doubling size halves runtime for scan-heavy queries
-- Diminishing returns for small/simple queries
'Configure Snowflake local development with testing, mocking, and fast.
Snowflake Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for Snowflake with separate dev warehouses, mocked tests, and SnowSQL for rapid iteration.
Prerequisites
- Completed
snowflake-install-authsetup - Node.js 18+ or Python 3.9+
- A dedicated dev warehouse (e.g.,
DEVWHXS) with auto-suspend
Instructions
Step 1: Create Dev-Specific Snowflake Objects
-- Run once to set up isolated dev environment
CREATE WAREHOUSE IF NOT EXISTS DEV_WH_XS
WAREHOUSE_SIZE = 'XSMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
CREATE DATABASE IF NOT EXISTS DEV_DB;
CREATE SCHEMA IF NOT EXISTS DEV_DB.SANDBOX;
-- Grant to dev role
GRANT USAGE ON WAREHOUSE DEV_WH_XS TO ROLE DEV_ROLE;
GRANT ALL ON DATABASE DEV_DB TO ROLE DEV_ROLE;
Step 2: Project Structure
my-snowflake-project/
├── src/
│ ├── snowflake/
│ │ ├── connection.ts # Connection wrapper with connectAsync
│ │ ├── queries.ts # Typed query functions
│ │ └── types.ts # Row type definitions
│ └── index.ts
├── tests/
│ ├── unit/
│ │ └── queries.test.ts # Mocked — no Snowflake needed
│ └── integration/
│ └── snowflake.test.ts # Requires SNOWFLAKE_* env vars
├── sql/
│ ├── migrations/ # Versioned DDL scripts
│ │ ├── V001__create_users.sql
│ │ └── V002__add_orders.sql
│ └── seeds/
│ └── dev-data.sql # Sample data for dev
├── .env.local # Local secrets (git-ignored)
├── .env.example # Template for team
└── package.json
Step 3: Connection Wrapper with Async/Await
// src/snowflake/connection.ts
import snowflake from 'snowflake-sdk';
// Enable promise-based API
snowflake.configure({ logLevel: 'WARN' });
export function createSnowflakeConnection() {
return snowflake.createConnection({
account: process.env.SNOWFLAKE_ACCOUNT!,
username: process.env.SNOWFLAKE_USER!,
password: process.env.SNOWFLAKE_PASSWORD!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE || 'DEV_WH_XS',
database: process.env.SNOWFLAKE_DATABASE || 'DEV_DB',
schema: process.env.SNOWFLAKE_SCHEMA || 'SANDBOX',
role: process.env.SNOWFLAKE_ROLE || 'DEV_ROLE',
});
}
// Promise wrapper for connection.execute
export function executeQuery(
conn: snowflake.Connection,
sqlText: string,
binds?: any[]
): Promise<any[]> {
return new Promise((resolve, reject) => {
conn.execute({
sqlText,
binds,
complete: (err, stmt, rows) => {
if (err) reject(err);
else resolve(rows || []);
},
});
});
}
// Promise wrapper for connect
export function connectAsync(
conn: snowflake.Connection
): Promise<snowflake.Connection> {
'Execute migration to Snowflake from Redshift, BigQuery, or on-prem databases.
Snowflake Migration Deep Dive
Overview
Comprehensive guide for migrating to Snowflake from Redshift, BigQuery, on-prem databases, or other data warehouses.
Migration Types
| Source | Complexity | Duration | Key Challenge |
|---|---|---|---|
| Amazon Redshift | Medium | 2-6 weeks | SQL dialect differences |
| Google BigQuery | Medium | 2-6 weeks | Nested/repeated fields |
| On-prem (Oracle/SQL Server) | High | 1-3 months | Data transfer bandwidth |
| Another Snowflake account | Low | Days | Replication or data sharing |
Instructions
Step 1: Schema Conversion
-- Common SQL differences from Redshift/BigQuery
-- Redshift DISTKEY/SORTKEY → Snowflake clustering (optional, for large tables)
-- Redshift: CREATE TABLE orders (id INT) DISTSTYLE KEY DISTKEY(customer_id) SORTKEY(order_date);
-- Snowflake:
CREATE TABLE orders (
id INTEGER AUTOINCREMENT,
customer_id INTEGER,
order_date TIMESTAMP_NTZ
);
ALTER TABLE orders CLUSTER BY (order_date); -- Only for tables > 1TB
-- Redshift IDENTITY → Snowflake AUTOINCREMENT
-- Redshift: id INT IDENTITY(1,1)
-- Snowflake: id INTEGER AUTOINCREMENT START 1 INCREMENT 1
-- BigQuery STRUCT/ARRAY → Snowflake VARIANT/ARRAY
-- BigQuery: address STRUCT<street STRING, city STRING>
-- Snowflake:
CREATE TABLE customers (
id INTEGER,
address VARIANT -- Store as JSON: {"street": "...", "city": "..."}
);
-- Access: SELECT address:street::VARCHAR FROM customers
-- BigQuery REPEATED fields → Snowflake ARRAY
-- BigQuery: tags ARRAY<STRING>
-- Snowflake: tags ARRAY
-- Data types mapping
-- Redshift VARCHAR(MAX) → Snowflake VARCHAR (16MB max)
-- Redshift TIMESTAMPTZ → Snowflake TIMESTAMP_TZ
-- BigQuery INT64 → Snowflake NUMBER(38,0)
-- BigQuery FLOAT64 → Snowflake FLOAT
-- BigQuery BYTES → Snowflake BINARY
-- Oracle CLOB → Snowflake VARCHAR
-- SQL Server DATETIME2 → Snowflake TIMESTAMP_NTZ
Step 2: Data Transfer Methods
# Method 1: Through cloud storage (recommended for large datasets)
# From Redshift → S3 → Snowflake
# Step A: Unload from Redshift to S3
psql -h redshift-cluster.xxx.region.redshift.amazonaws.com -d mydb -c "
UNLOAD ('SELECT * FROM orders')
TO 's3://migration-bucket/redshift/orders/'
IAM_ROLE 'arn:aws:iam::123456789:role/RedshiftUnload'
FORMAT PARQUET;
"
# Step B: Load from S3 to Snowflake
snowsql -c prod -q "
CREATE STAGE migration_stage
STORAGE_INTEGRATION = s3_integration
URL = 's3://migration-bucket/redshift/';
COPY INTO orders
FROM @migration_stage/orders/
FILE_FORMAT = (TYPE = 'PARQUET')
MATCH_BY_COLUMN_NAME = CASE_INSEN'Configure Snowflake across dev, staging, and production with account-level.
Snowflake Multi-Environment Setup
Overview
Configure dev/staging/production environments using Snowflake's zero-copy cloning, separate databases, and environment-specific roles and warehouses.
Environment Strategy
| Environment | Approach | Data | Warehouse | Cost |
|---|---|---|---|---|
| Development | Cloned DB, XSMALL WH | Zero-copy clone (refreshed weekly) | DEVWHXS | Minimal |
| Staging | Cloned DB, same-size WH | Zero-copy clone (refreshed daily) | STAGING_WH | Moderate |
| Production | Source of truth | Real data | PROD_WH (multi-cluster) | Full |
Instructions
Step 1: Create Environment Databases with Zero-Copy Cloning
-- Zero-copy clone creates instant copy with no additional storage cost
-- Storage cost only accrues when cloned data diverges from source
-- Clone production to staging (point-in-time)
CREATE DATABASE STAGING_DW CLONE PROD_DW;
-- Clone to dev
CREATE DATABASE DEV_DW CLONE PROD_DW;
-- Clone from a specific point in time (Time Travel)
CREATE DATABASE STAGING_DW CLONE PROD_DW
AT (TIMESTAMP => '2026-03-21 06:00:00'::TIMESTAMP_NTZ);
-- Refresh clone (drop and re-clone)
-- Schedule this as a task:
CREATE OR REPLACE TASK refresh_staging_clone
WAREHOUSE = ADMIN_WH
SCHEDULE = 'USING CRON 0 4 * * * America/New_York' -- 4 AM ET daily
AS
BEGIN
DROP DATABASE IF EXISTS STAGING_DW;
CREATE DATABASE STAGING_DW CLONE PROD_DW;
-- Re-grant permissions after clone
GRANT USAGE ON DATABASE STAGING_DW TO ROLE STAGING_ROLE;
GRANT USAGE ON ALL SCHEMAS IN DATABASE STAGING_DW TO ROLE STAGING_ROLE;
GRANT SELECT ON ALL TABLES IN DATABASE STAGING_DW TO ROLE STAGING_ROLE;
END;
ALTER TASK refresh_staging_clone RESUME;
Step 2: Environment-Specific Warehouses
-- Development: minimal size, aggressive auto-suspend
CREATE WAREHOUSE DEV_WH
WAREHOUSE_SIZE = 'XSMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE
RESOURCE_MONITOR = dev_monitor;
-- Staging: mirrors production size for realistic testing
CREATE WAREHOUSE STAGING_WH
WAREHOUSE_SIZE = 'MEDIUM'
AUTO_SUSPEND = 120
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE
RESOURCE_MONITOR = staging_monitor;
-- Production: multi-cluster for concurrency
CREATE WAREHOUSE PROD_WH
WAREHOUSE_SIZE = 'MEDIUM'
MIN_CLUSTER_COUNT = 1
MAX_CLUSTER_COUNT = 4
SCALING_POLICY = 'STANDARD'
AUTO_SUSPEND = 300
AUTO_RESUME = TRUE
RESOURCE_MONITOR = prod_monitor;
-- Resource monitors per environment
CREATE RESOURCE MONITOR dev_monitor
WITH CREDIT_QUOTA = 50 FREQUENCY = MONTHLY START_TIMESTAMP = IMMEDIATELY
TRIGGERS ON 100 PERCENT DO SUSPEND;
CREATE RESOURCE MO'Set up Snowflake observability using ACCOUNT_USAGE views, alerts, and.
Snowflake Observability
Overview
Set up comprehensive observability for Snowflake using built-in ACCOUNT_USAGE views, Snowflake Alerts, and integration with external monitoring systems.
Prerequisites
- Role with access to
SNOWFLAKE.ACCOUNT_USAGE(ACCOUNTADMIN or granted) - Notification integration configured for alerts
- Optional: Prometheus/Grafana or Datadog for external dashboards
Instructions
Step 1: Key Monitoring Queries
-- === QUERY PERFORMANCE ===
-- Average query time by warehouse (last 7 days)
SELECT warehouse_name,
COUNT(*) AS query_count,
ROUND(AVG(total_elapsed_time) / 1000, 1) AS avg_seconds,
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY total_elapsed_time) / 1000, 1) AS p95_seconds,
ROUND(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY total_elapsed_time) / 1000, 1) AS p99_seconds
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(days, -7, CURRENT_TIMESTAMP())
AND execution_status = 'SUCCESS'
AND query_type = 'SELECT'
GROUP BY warehouse_name
ORDER BY avg_seconds DESC;
-- === ERROR RATE ===
-- Error rate by hour
SELECT DATE_TRUNC('hour', start_time) AS hour,
COUNT_IF(execution_status = 'SUCCESS') AS success,
COUNT_IF(execution_status = 'FAIL') AS failures,
ROUND(COUNT_IF(execution_status = 'FAIL') * 100.0 /
NULLIF(COUNT(*), 0), 2) AS error_rate_pct
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
GROUP BY hour
ORDER BY hour;
-- === CREDIT CONSUMPTION ===
-- Hourly credit usage
SELECT DATE_TRUNC('hour', start_time) AS hour,
warehouse_name,
SUM(credits_used) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
GROUP BY hour, warehouse_name
ORDER BY hour DESC, credits DESC;
-- === STORAGE GROWTH ===
-- Daily storage trend
SELECT usage_date,
ROUND(storage_bytes / 1e12, 3) AS storage_tb,
ROUND(stage_bytes / 1e12, 3) AS stage_tb,
ROUND(failsafe_bytes / 1e12, 3) AS failsafe_tb
FROM SNOWFLAKE.ACCOUNT_USAGE.STORAGE_USAGE
WHERE usage_date >= DATEADD(days, -30, CURRENT_DATE())
ORDER BY usage_date;
Step 2: Built-in Snowflake Alerts
-- Alert: High error rate
CREATE OR REPLACE ALERT high_error_rate_alert
WAREHOUSE = ANALYTICS_WH
SCHEDULE = '15 MINUTE'
IF (EXISTS (
SELECT 1
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(minutes, -15, CURRENT_TIMESTAMP())
GROUP BY ALL
HAVING COUNT_IF(execution_status = 'FAIL') * 100.0 / COUNT(*) > 5
))
THEN
CALL SYSTEM$SEND_EMAIL(
'ops_notifications',
'oncall@company.com',
'Snowflake: Error rate > 5%''Optimize Snowflake query performance with clustering, materialized views,.
Snowflake Performance Tuning
Overview
Optimize Snowflake query performance using clustering keys, materialized views, result caching, query profiling, and warehouse tuning.
Prerequisites
- Access to
SNOWFLAKE.ACCOUNTUSAGE.QUERYHISTORY - Understanding of micro-partitions and pruning
- Role with
MONITORprivilege on warehouses
Instructions
Step 1: Identify Slow Queries
-- Top 20 slowest queries in last 24 hours
SELECT query_id, query_text, total_elapsed_time / 1000 AS seconds,
bytes_scanned / 1e9 AS gb_scanned,
partitions_scanned, partitions_total,
ROUND(partitions_scanned / NULLIF(partitions_total, 0) * 100, 1) AS pct_scanned,
warehouse_name, warehouse_size
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE execution_status = 'SUCCESS'
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
AND query_type = 'SELECT'
ORDER BY total_elapsed_time DESC
LIMIT 20;
-- Queries scanning too many partitions (poor pruning)
SELECT query_id, query_text,
partitions_scanned, partitions_total,
bytes_scanned / 1e9 AS gb_scanned
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE partitions_scanned > partitions_total * 0.5
AND partitions_total > 100
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
ORDER BY partitions_scanned DESC
LIMIT 10;
Step 2: Add Clustering Keys
-- Clustering improves pruning for large tables (> 1TB)
-- Choose columns used in WHERE and JOIN clauses
-- Cluster by date (most common filter)
ALTER TABLE orders CLUSTER BY (order_date);
-- Multi-column clustering
ALTER TABLE events CLUSTER BY (event_date, event_type);
-- Check clustering depth (lower = better)
SELECT SYSTEM$CLUSTERING_INFORMATION('orders', '(order_date)');
-- Monitor automatic reclustering
SELECT table_name, num_rows, bytes,
SYSTEM$CLUSTERING_DEPTH('orders') AS clustering_depth
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name = 'ORDERS';
Step 3: Use Materialized Views
-- Pre-compute expensive aggregations
CREATE OR REPLACE MATERIALIZED VIEW daily_revenue_mv
CLUSTER BY (metric_date)
AS
SELECT
DATE_TRUNC('day', order_date) AS metric_date,
COUNT(*) AS order_count,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value
FROM orders
GROUP BY DATE_TRUNC('day', order_date);
-- Query the MV instead of base table — automatic rewrite may also apply
SELECT * FROM daily_revenue_mv
WHERE metric_date >= DATEADD(days, -30, CURRENT_DATE());
-- Check MV freshness
SELECT name, is_secure, text, refresh_on
FROM INFORMATION_SCHEMA.MATERIALIZED_VIEWS
WHERE name = 'DAILY_REVENUE_MV';
Step 4: Leverage Result Caching
-- Result'Implement Snowflake governance guardrails with network rules, session.
Snowflake Policy & Guardrails
Overview
Automated policy enforcement and governance guardrails using Snowflake-native features: network rules, authentication policies, session policies, and object-level governance.
Instructions
Step 1: Network Rules and Policies
-- Network rules (more granular than legacy network policies)
CREATE OR REPLACE NETWORK RULE corp_vpn_rule
TYPE = IPV4
MODE = INGRESS
VALUE_LIST = ('203.0.113.0/24', '198.51.100.0/24');
CREATE OR REPLACE NETWORK RULE cloud_services_rule
TYPE = HOST_PORT
MODE = EGRESS
VALUE_LIST = ('api.company.com:443', 'events.company.com:443');
-- Create network policy using rules
CREATE OR REPLACE NETWORK POLICY prod_network_policy
ALLOWED_NETWORK_RULE_LIST = (corp_vpn_rule)
BLOCKED_NETWORK_RULE_LIST = ();
-- Apply at account level
ALTER ACCOUNT SET NETWORK_POLICY = prod_network_policy;
-- Or per-user (service accounts can have different rules)
ALTER USER svc_etl SET NETWORK_POLICY = prod_network_policy;
Step 2: Authentication Policies
-- Require MFA for interactive users
CREATE OR REPLACE AUTHENTICATION POLICY interactive_auth
MFA_AUTHENTICATION_METHODS = ('TOTP')
CLIENT_TYPES = ('SNOWFLAKE_UI', 'SNOWSQL')
SECURITY_INTEGRATIONS = ('saml_sso');
-- Service accounts: key pair only, no password
CREATE OR REPLACE AUTHENTICATION POLICY service_auth
AUTHENTICATION_METHODS = ('KEYPAIR')
CLIENT_TYPES = ('SNOWFLAKE_DRIVER')
MFA_AUTHENTICATION_METHODS = ();
-- Apply policies
ALTER USER analyst_user SET AUTHENTICATION POLICY = interactive_auth;
ALTER USER svc_etl SET AUTHENTICATION POLICY = service_auth;
Step 3: Session Policies
-- Enforce session timeout and idle limits
CREATE OR REPLACE SESSION POLICY prod_session_policy
SESSION_IDLE_TIMEOUT_MINS = 30
SESSION_UI_IDLE_TIMEOUT_MINS = 15;
-- Apply to account
ALTER ACCOUNT SET SESSION POLICY = prod_session_policy;
Step 4: Statement-Level Guardrails
-- Prevent runaway queries
ALTER WAREHOUSE PROD_WH SET
STATEMENT_TIMEOUT_IN_SECONDS = 3600, -- 1 hour max
STATEMENT_QUEUED_TIMEOUT_IN_SECONDS = 600; -- 10 min max queue
-- Prevent accidental full table operations
-- Use row access policies + stored procedures instead of raw access
-- Example: Safe delete procedure with audit
CREATE OR REPLACE PROCEDURE safe_delete(
table_name VARCHAR, where_clause VARCHAR, max_rows INTEGER DEFAULT 10000
)
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
BEGIN
-- Count affected rows first
LET count_sql VARCHAR := 'SELECT COUNT(*) FROM ' || :table_name || ' WHERE ' || :where_clause;
LET affected_rows INTEGER;
EXECUTE IMMEDIATE :count_sql INTO :affected_rows;
IF (:affected_rows > :max_rows) THEN
RETURN 'BLO'Execute Snowflake production readiness checklist with monitoring and.
Snowflake Production Checklist
Overview
Complete checklist for deploying Snowflake data pipelines and integrations to production.
Prerequisites
- Staging environment validated
- Production Snowflake account configured
- Resource monitors in place
- Monitoring infrastructure ready
Pre-Deployment Checklist
Authentication & Secrets
- [ ] Service accounts use key pair auth (not password)
- [ ] Private keys stored in secret manager (not files/env vars)
- [ ] Key rotation procedure documented and tested
- [ ] Network policy applied to production account
- [ ] Connection parameters use production account identifier
Warehouse Configuration
- [ ] Production warehouses created with appropriate sizing
- [ ] Auto-suspend configured (60-300s based on workload)
- [ ] Auto-resume enabled
- [ ] Resource monitors with credit quotas and alerts
- [ ] Separate warehouses for ETL, analytics, and dashboard workloads
-- Production warehouse setup
CREATE WAREHOUSE IF NOT EXISTS PROD_ETL_WH
WAREHOUSE_SIZE = 'LARGE'
AUTO_SUSPEND = 120
AUTO_RESUME = TRUE;
CREATE WAREHOUSE IF NOT EXISTS PROD_ANALYTICS_WH
WAREHOUSE_SIZE = 'MEDIUM'
MIN_CLUSTER_COUNT = 1
MAX_CLUSTER_COUNT = 3
SCALING_POLICY = 'STANDARD'
AUTO_SUSPEND = 300
AUTO_RESUME = TRUE;
-- Resource monitor with alerts
CREATE OR REPLACE RESOURCE MONITOR prod_monitor
WITH CREDIT_QUOTA = 1000
FREQUENCY = MONTHLY
START_TIMESTAMP = IMMEDIATELY
TRIGGERS
ON 75 PERCENT DO NOTIFY
ON 90 PERCENT DO NOTIFY
ON 100 PERCENT DO SUSPEND
ON 110 PERCENT DO SUSPEND_IMMEDIATE;
ALTER WAREHOUSE PROD_ETL_WH SET RESOURCE_MONITOR = prod_monitor;
ALTER WAREHOUSE PROD_ANALYTICS_WH SET RESOURCE_MONITOR = prod_monitor;
Data Pipeline Readiness
- [ ] All tasks resumed and running on schedule
- [ ] Streams not stale (check with
SHOW STREAMS) - [ ] Snowpipe notifications configured and verified
- [ ] COPY INTO error handling set (
ONERROR = 'CONTINUE'or'SKIPFILE') - [ ] Data retention set appropriately (
DATARETENTIONTIMEINDAYS)
Query & Performance
- [ ] Critical queries tested at production data volume
- [ ] Clustering keys set on large tables (>1TB)
- [ ] Statement timeout configured per warehouse
- [ ] Result caching enabled (
USECACHEDRESULT = TRUE)
-- Set statement timeout for production
ALTER WAREHOUSE PROD_ETL_WH SET STATEMENT_TIMEOUT_IN_SECONDS = 3600;
ALTER WAREHOUSE PROD_ANALYTICS_WH SET STATEMENT_TIMEOUT_IN_SECONDS = 600;
-- Enable query result caching (default is ON)
ALTER ACCOUNT SET USE_CACHED_RESULT = TRUE;
<'Handle Snowflake concurrency limits, warehouse queuing, and query throttling.
Snowflake Rate Limits & Concurrency
Overview
Snowflake doesn't use traditional API rate limits. Instead, concurrency is governed by warehouse size, multi-cluster configuration, and per-session/account limits.
Key Limits
| Resource | Limit | Notes |
|---|---|---|
| Concurrent queries per warehouse | 8 (XS) to 64+ (4XL) | Depends on warehouse size |
| Queued queries per warehouse | Unlimited (queued, not rejected) | But users experience latency |
| SQL API requests | 10 concurrent per user | Via REST /api/v2/statements |
| Snowpipe file notifications | 10,000/sec per pipe | Per-pipe limit |
| Login rate | Throttled per account | Avoid rapid connect/disconnect |
| COPY INTO files per command | 1,000 files recommended | Performance degrades beyond |
Instructions
Step 1: Detect Queuing Issues
-- Check warehouse load — avg_queued_load > 0 means queries are waiting
SELECT warehouse_name, start_time,
avg_running, avg_queued_load, avg_blocked
FROM TABLE(INFORMATION_SCHEMA.WAREHOUSE_LOAD_HISTORY(
DATE_RANGE_START => DATEADD(hours, -4, CURRENT_TIMESTAMP())
))
WHERE avg_queued_load > 0
ORDER BY start_time DESC;
-- Find queries that waited in queue
SELECT query_id, query_text, queued_overload_time / 1000 AS queue_seconds,
total_elapsed_time / 1000 AS total_seconds, warehouse_name
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE queued_overload_time > 0
AND start_time >= DATEADD(hours, -24, CURRENT_TIMESTAMP())
ORDER BY queued_overload_time DESC
LIMIT 20;
Step 2: Right-Size Your Warehouse
-- Size recommendations based on workload type
-- XSMALL: Simple queries, dev/test, low concurrency
-- SMALL/MEDIUM: Standard analytics, dashboards
-- LARGE/XLARGE: Complex joins, large scans
-- 2XL+: Heavy ELT, ML training
-- Create right-sized warehouses per workload
CREATE WAREHOUSE IF NOT EXISTS ETL_WH
WAREHOUSE_SIZE = 'LARGE'
AUTO_SUSPEND = 120
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
CREATE WAREHOUSE IF NOT EXISTS ANALYTICS_WH
WAREHOUSE_SIZE = 'MEDIUM'
AUTO_SUSPEND = 300
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
CREATE WAREHOUSE IF NOT EXISTS DASHBOARD_WH
WAREHOUSE_SIZE = 'SMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
Step 3: Multi-Cluster Warehouse for High Concurrency
-- Auto-scale from 1 to 5 clusters based on demand
CREATE OR REPLACE WAREHOUSE HIGH_CONCURRENCY_WH
WAREHOUSE_SIZE = 'MEDIUM'
MIN_CLUSTER_COUNT = 1
MAX_CLUSTER_COUNT = 5
SCALING_POLICY = 'STANDARD' -- Start new cluster when queri'Implement Snowflake reference architecture with medallion pattern and.
Snowflake Reference Architecture
Overview
Production-ready Snowflake architecture using the medallion pattern (bronze/silver/gold), role-based access, and workload-isolated warehouses.
Architecture Overview
┌──────────────────────┐
│ Data Sources │
│ (S3, APIs, DBs, SaaS) │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ BRONZE (Raw) │
│ Snowpipe / COPY │
│ VARIANT columns │
└──────────┬───────────┘
│ Streams + Tasks
┌──────────▼───────────┐
│ SILVER (Cleansed) │
│ Typed columns │
│ Deduped, validated │
└──────────┬───────────┘
│ Dynamic Tables
┌──────────▼───────────┐
│ GOLD (Business) │
│ Aggregated │
│ Analytics-ready │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Consumers │
│ BI tools, APIs, │
│ Data Sharing │
└──────────────────────┘
Database Layout
-- One database per environment, schemas per layer
CREATE DATABASE PROD_DW;
-- Bronze: Raw ingested data (append-only, VARIANT columns)
CREATE SCHEMA PROD_DW.BRONZE;
-- Silver: Cleansed, typed, deduplicated
CREATE SCHEMA PROD_DW.SILVER;
-- Gold: Business-level aggregations and dimensions
CREATE SCHEMA PROD_DW.GOLD;
-- Staging: Temporary tables for ETL processing
CREATE SCHEMA PROD_DW.STAGING;
-- Utility: Stored procedures, UDFs, file formats
CREATE SCHEMA PROD_DW.UTILITY;
Instructions
Step 1: Bronze Layer (Raw Ingestion)
-- Store raw data as VARIANT for schema-on-read
CREATE TABLE PROD_DW.BRONZE.RAW_EVENTS (
ingestion_time TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),
source_file VARCHAR(500),
raw_data VARIANT
);
-- File format for JSON ingestion
CREATE FILE FORMAT PROD_DW.UTILITY.JSON_INGEST
TYPE = 'JSON' STRIP_OUTER_ARRAY = TRUE;
-- Stage for S3 source
CREATE STAGE PROD_DW.UTILITY.S3_EVENTS_STAGE
STORAGE_INTEGRATION = s3_integration
URL = 's3://data-lake/events/'
FILE_FORMAT = PROD_DW.UTILITY.JSON_INGEST;
-- Snowpipe for continuous ingestion
CREATE PIPE PROD_DW.BRONZE.EVENTS_PIPE
AUTO_INGEST = TRUE
AS
COPY INTO PROD_DW.BRONZE.RAW_EVENTS (source_file, raw_data)
FROM (SELECT METADATA$FILENAME, $1 FROM @PROD_DW.UTILITY.S3_EVENTS_STAGE);
Step 2: Silver Layer (Cleansing)
'Implement Snowflake reliability patterns: replication, failover, Time.
Snowflake Reliability Patterns
Overview
Production-grade reliability patterns for Snowflake: database replication, account failover, Time Travel recovery, and application-level circuit breakers.
Instructions
Step 1: Time Travel for Point-in-Time Recovery
-- Query historical data (up to 90 days on Enterprise Edition)
SELECT * FROM orders
AT (TIMESTAMP => '2026-03-21 14:00:00'::TIMESTAMP_NTZ);
-- Restore a table to a previous state
CREATE OR REPLACE TABLE orders
CLONE orders AT (TIMESTAMP => '2026-03-21 14:00:00'::TIMESTAMP_NTZ);
-- Restore a dropped table
UNDROP TABLE orders;
UNDROP SCHEMA my_schema;
UNDROP DATABASE my_database;
-- Query by offset (5 minutes ago)
SELECT * FROM orders AT (OFFSET => -300);
-- Query by statement ID (before a specific query ran)
SELECT * FROM orders BEFORE (STATEMENT => '<query_id_of_bad_update>');
-- Set retention period per table
ALTER TABLE critical_data SET DATA_RETENTION_TIME_IN_DAYS = 90;
ALTER TABLE temp_staging SET DATA_RETENTION_TIME_IN_DAYS = 0; -- No Time Travel
Step 2: Database Replication Across Regions
-- Enable replication on source account (primary)
ALTER DATABASE PROD_DW ENABLE REPLICATION TO ACCOUNTS
myorg.us_east_account,
myorg.eu_west_account;
-- On target account: create replica database
CREATE DATABASE PROD_DW_REPLICA
AS REPLICA OF myorg.us_west_account.PROD_DW;
-- Refresh replica (manual or scheduled)
ALTER DATABASE PROD_DW_REPLICA REFRESH;
-- Check replication status
SELECT * FROM TABLE(INFORMATION_SCHEMA.DATABASE_REPLICATION_USAGE_HISTORY(
DATE_RANGE_START => DATEADD(hours, -24, CURRENT_TIMESTAMP())
));
-- Check replication lag
SELECT database_name, primary_snowflake_region,
replication_allowed, is_primary,
DATEDIFF('minute', snowflake_region_last_refresh_time, CURRENT_TIMESTAMP()) AS lag_minutes
FROM TABLE(INFORMATION_SCHEMA.REPLICATION_DATABASES())
WHERE database_name = 'PROD_DW_REPLICA';
Step 3: Account Failover Groups
-- Create failover group (replicates databases, warehouses, roles, etc.)
-- On primary account:
CREATE FAILOVER GROUP prod_failover
OBJECT_TYPES = DATABASES, WAREHOUSES, ROLES, USERS, INTEGRATIONS
ALLOWED_DATABASES = PROD_DW
ALLOWED_ACCOUNTS = myorg.us_east_account
REPLICATION_SCHEDULE = '10 MINUTE';
-- On secondary account: create as replica
CREATE FAILOVER GROUP prod_failover
AS REPLICA OF myorg.us_west_account.prod_failover;
-- Promote secondary to primary (during outage)
ALTER FAILOVER GROUP prod_failover PRIMARY;
-- After recovery, switch back
-- On original primary:
ALTER FAILOVER GROUP prod_failover PRIMARY;
Step 4: Application-Level Connection Failover
// src/snowflake/resilient-connection.ts
import snowflake from 'snowflak'Apply production-ready Snowflake SDK patterns for snowflake-sdk and.
Snowflake SDK Patterns
Overview
Production-ready patterns for snowflake-sdk (Node.js) and snowflake-connector-python using real driver APIs.
Prerequisites
- Completed
snowflake-install-authsetup - Understanding of callback-to-promise conversion patterns
- Familiarity with Snowflake's callback-based Node.js API
Instructions
Step 1: Connection Pool (Node.js)
// src/snowflake/pool.ts
import snowflake from 'snowflake-sdk';
interface PoolConfig {
max: number;
idleTimeoutMs: number;
}
class SnowflakePool {
private pool: snowflake.Connection[] = [];
private available: snowflake.Connection[] = [];
private waiting: ((conn: snowflake.Connection) => void)[] = [];
private config: PoolConfig;
constructor(
private connConfig: snowflake.ConnectionOptions,
config: Partial<PoolConfig> = {}
) {
this.config = { max: 10, idleTimeoutMs: 60000, ...config };
}
async acquire(): Promise<snowflake.Connection> {
// Return available connection
if (this.available.length > 0) {
return this.available.pop()!;
}
// Create new if under limit
if (this.pool.length < this.config.max) {
const conn = snowflake.createConnection(this.connConfig);
await new Promise<void>((resolve, reject) => {
conn.connect((err) => (err ? reject(err) : resolve()));
});
this.pool.push(conn);
return conn;
}
// Wait for one to become available
return new Promise((resolve) => {
this.waiting.push(resolve);
});
}
release(conn: snowflake.Connection): void {
if (this.waiting.length > 0) {
const next = this.waiting.shift()!;
next(conn);
} else {
this.available.push(conn);
}
}
async withConnection<T>(fn: (conn: snowflake.Connection) => Promise<T>): Promise<T> {
const conn = await this.acquire();
try {
return await fn(conn);
} finally {
this.release(conn);
}
}
}
// Singleton pool
export const pool = new SnowflakePool({
account: process.env.SNOWFLAKE_ACCOUNT!,
username: process.env.SNOWFLAKE_USER!,
password: process.env.SNOWFLAKE_PASSWORD!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE || 'COMPUTE_WH',
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA || 'PUBLIC',
});
Step 2: Promise-Based Query Helper
// src/snowflake/query.ts
import snowflake from 'snowflake-sdk';
interface QueryResult<T = Record<string, any>> {
rows: T[];
statement: snowflake.Statement;
sqlText: string;
}
export function query<T = Record<string, any>>(
conn: snowflake.Connection,
sqlText: string,
binds?: snowflake.Binds
): Promise<QueryResult<T>> {
return new Promi'Apply Snowflake security best practices: network policies, key rotation,.
Snowflake Security Basics
Overview
Security best practices for Snowflake: network policies, key pair rotation, MFA, secret management, and least-privilege roles.
Prerequisites
- SECURITYADMIN or ACCOUNTADMIN role access
- Understanding of network CIDR notation
- Secret management solution (Vault, AWS Secrets Manager, etc.)
Instructions
Step 1: Create Network Policies
-- Restrict access to known IP ranges
CREATE OR REPLACE NETWORK POLICY corporate_policy
ALLOWED_IP_LIST = (
'203.0.113.0/24', -- Corporate office
'198.51.100.0/24', -- VPN range
'10.0.0.0/8' -- Internal network
)
BLOCKED_IP_LIST = (
'203.0.113.99' -- Block specific IP
);
-- Apply to entire account
ALTER ACCOUNT SET NETWORK_POLICY = corporate_policy;
-- Or apply to specific user (service account)
ALTER USER svc_etl SET NETWORK_POLICY = corporate_policy;
-- Verify current policy
SELECT * FROM TABLE(INFORMATION_SCHEMA.POLICY_REFERENCES(POLICY_NAME => 'corporate_policy'));
Step 2: Configure Key Pair Rotation
#!/bin/bash
# rotate-snowflake-keys.sh
# Generate new key pair
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key_new.p8 -nocrypt
openssl rsa -in rsa_key_new.p8 -pubout -out rsa_key_new.pub
# Extract public key (remove headers and newlines)
PUB_KEY=$(grep -v "BEGIN\|END" rsa_key_new.pub | tr -d '\n')
echo "Run in Snowflake:"
echo "ALTER USER svc_etl SET RSA_PUBLIC_KEY_2 = '${PUB_KEY}';"
echo ""
echo "After verifying new key works:"
echo "ALTER USER svc_etl UNSET RSA_PUBLIC_KEY;"
echo "ALTER USER svc_etl SET RSA_PUBLIC_KEY = '${PUB_KEY}';"
echo "ALTER USER svc_etl UNSET RSA_PUBLIC_KEY_2;"
-- Snowflake supports two active public keys for zero-downtime rotation
-- Step 1: Set new key as RSA_PUBLIC_KEY_2
ALTER USER svc_etl SET RSA_PUBLIC_KEY_2 = 'MIIBIj...new_key...';
-- Step 2: Update application to use new private key
-- Step 3: After verification, promote and clean up
ALTER USER svc_etl SET RSA_PUBLIC_KEY = 'MIIBIj...new_key...';
ALTER USER svc_etl UNSET RSA_PUBLIC_KEY_2;
Step 3: Enable MFA
-- Enforce MFA via authentication policy
CREATE OR REPLACE AUTHENTICATION POLICY require_mfa
MFA_AUTHENTICATION_METHODS = ('TOTP')
CLIENT_TYPES = ('SNOWFLAKE_UI', 'SNOWSQL')
SECURITY_INTEGRATIONS = ();
-- Apply to human users (not service accounts)
ALTER USER analyst_user SET AUTHENTICATION POLICY = require_mfa;
-- Check MFA enrollment status
SELECT name, has_mfa, login_name, disabled
FROM SNOWFLAKE.ACCOUNT_USAGE.USERS
WHERE has_mfa = 'false' AND disabled = 'fal'Upgrade Snowflake drivers, handle breaking changes, and migrate between.
Snowflake Upgrade & Migration
Overview
Guide for upgrading Snowflake driver versions, handling Snowflake behavior change releases, and migrating between editions.
Prerequisites
- Current driver version identified
- Test suite available
- Staging Snowflake account
- Git for version control
Instructions
Step 1: Check Current Versions
# Node.js driver
npm list snowflake-sdk
npm view snowflake-sdk version # Latest available
# Python connector
pip show snowflake-connector-python
pip index versions snowflake-connector-python 2>/dev/null | head -5
# Snowflake platform version (run in SQL)
# SELECT CURRENT_VERSION();
Step 2: Review Snowflake Release Notes
# Check Node.js driver changelog
open https://github.com/snowflakedb/snowflake-connector-nodejs/blob/master/CHANGELOG.md
# Check Python connector changelog
open https://docs.snowflake.com/en/release-notes/clients-drivers/python-connector-2025
# Check Snowflake BCR (Behavior Change Releases)
open
Step 3: Upgrade on a Branch
# Node.js
git checkout -b chore/upgrade-snowflake-sdk
npm install snowflake-sdk@latest
npm test
# Python
git checkout -b chore/upgrade-snowflake-connector
pip install --upgrade snowflake-connector-python
pytest
Step 4: Handle Common Breaking Changes
Node.js Driver Changes (1.x to 2.x+):
// Old: Synchronous configure
// snowflake.configure({ logLevel: 'DEBUG' });
// New: Same API but check for removed options
import snowflake from 'snowflake-sdk';
snowflake.configure({
logLevel: process.env.NODE_ENV === 'development' ? 'DEBUG' : 'WARN',
// insecureConnect removed in newer versions — use proper certs
});
// connectAsync added in later versions
const conn = snowflake.createConnection({ /* ... */ });
await conn.connectAsync(); // Promise-based (if available)
// Fallback for older versions:
await new Promise((resolve, reject) => {
conn.connect((err, c) => err ? reject(err) : resolve(c));
});
Python Connector Changes:
# v3.x: fetch_pandas_all() requires pandas extra
# pip install "snowflake-connector-python[pandas]"
# v3.x: write_pandas() moved to snowflake.connector.pandas_tools
from snowflake.connector.pandas_tools import write_pandas
# v2.x to v3.x: DictCursor import changed
# Old: from snowflake.connector import DictCursor
# New:
cursor = conn.cursor(snowflake.connector.DictCursor)
# Arrow result format (default in newer versions)
conn = snowflake.connector.connect(
# ...
arrow_number_to_decimal=True, # New in 3.x
)
Snowflake Platform BCR (Behavior Change Releases):
--'Implement Snowflake event-driven patterns with alerts, notifications,.
Snowflake Webhooks & Events
Overview
Snowflake uses alerts, email notifications, external functions, and notification integrations for event-driven patterns (not traditional webhooks).
Prerequisites
- ACCOUNTADMIN or role with
CREATE ALERTprivilege - Email notification integration configured
- For external functions: API Gateway (AWS/GCP/Azure) configured
- For S3/GCS event notifications: Snowpipe configured
Instructions
Step 1: Snowflake Alerts (Built-in Event System)
-- Alert when daily revenue drops below threshold
CREATE OR REPLACE ALERT revenue_drop_alert
WAREHOUSE = ANALYTICS_WH
SCHEDULE = '60 MINUTE'
IF (EXISTS (
SELECT 1
FROM daily_order_metrics
WHERE metric_date = CURRENT_DATE()
AND total_revenue < (
SELECT AVG(total_revenue) * 0.5
FROM daily_order_metrics
WHERE metric_date BETWEEN DATEADD(days, -30, CURRENT_DATE())
AND DATEADD(days, -1, CURRENT_DATE())
)
))
THEN
CALL SYSTEM$SEND_EMAIL(
'revenue_notifications',
'oncall@company.com',
'Revenue Alert: Below 50% of 30-day average',
'Daily revenue has dropped significantly. Check dashboard.'
);
ALTER ALERT revenue_drop_alert RESUME;
-- Alert when warehouse credits exceed daily budget
CREATE OR REPLACE ALERT credit_usage_alert
WAREHOUSE = ANALYTICS_WH
SCHEDULE = '30 MINUTE'
IF (EXISTS (
SELECT 1
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= CURRENT_DATE()
GROUP BY ALL
HAVING SUM(credits_used) > 100 -- Daily budget: 100 credits
))
THEN
CALL SYSTEM$SEND_EMAIL(
'ops_notifications',
'ops@company.com',
'Snowflake Credit Alert',
'Daily credit usage has exceeded 100 credits.'
);
-- Monitor alert history
SELECT *
FROM TABLE(INFORMATION_SCHEMA.ALERT_HISTORY(
SCHEDULED_TIME_RANGE_START => DATEADD(hours, -24, CURRENT_TIMESTAMP())
))
ORDER BY scheduled_time DESC;
Step 2: Email Notification Integration
-- Set up email notification integration
CREATE OR REPLACE NOTIFICATION INTEGRATION email_notifications
TYPE = EMAIL
ENABLED = TRUE
ALLOWED_RECIPIENTS = (
'oncall@company.com',
'ops@company.com',
'data-team@company.com'
);
-- Send email from stored procedure
CREATE OR REPLACE PROCEDURE send_data_quality_report()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
BEGIN
LET row_count INTEGER;
SELECT COUNT(*) INTO :row_count
FROM orders WHERE order_date = CURRENT_DATE() AND amount IS NULL;
IF (row_count > 0) THEN
CALL SYSTEM$SEND_EMAIL(
'email_notifications',
'data-team@company.com',
'Data Quality Issue: NULL amounts detected',
row_count || ' ordeReady to use snowflake-pack?
Related Plugins
supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
/plugin install supabase-pack@claude-code-plugins-plus
vercel-pack
Complete Vercel integration skill pack with 30 skills covering deployments, edge functions, preview environments, performance optimization, and production operations. Flagship+ tier vendor pack.
/plugin install vercel-pack@claude-code-plugins-plus
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
/plugin install clay-pack@claude-code-plugins-plus
cursor-pack
Complete Cursor integration skill pack with 30 skills covering AI code editing, composer workflows, codebase indexing, and productivity features. Flagship+ tier vendor pack.
/plugin install cursor-pack@claude-code-plugins-plus
exa-pack
Complete Exa integration skill pack with 30 skills covering neural search, semantic retrieval, web search API, and AI-powered discovery. Flagship+ tier vendor pack.
/plugin install exa-pack@claude-code-plugins-plus
firecrawl-pack
Complete Firecrawl integration skill pack with 30 skills covering web scraping, crawling, markdown conversion, and LLM-ready data extraction. Flagship+ tier vendor pack.
/plugin install firecrawl-pack@claude-code-plugins-plus