Introduction to Scaling Redshift
Amazon Redshift is a fully managed, petabyte-scale data warehouse service that makes it simple and cost-effective to analyze all your data using standard SQL. While getting a Redshift cluster up and running for a prototype is straightforward, scaling it to production requires careful planning around cluster design, data distribution, query optimization, workload management, and monitoring. This tutorial walks you through the journey of taking a Redshift prototype and hardening it for production workloads.
Why Scaling Redshift Matters
A prototype Redshift cluster often works fine with a few gigabytes of data and a handful of concurrent users. However, as you move to production, several challenges emerge:
- Data volume growth: Tables that started at megabytes can grow to terabytes, making poor table design painfully obvious.
- Concurrency: Production systems often serve dozens or hundreds of simultaneous queries, which can bottleneck a poorly configured cluster.
- Cost efficiency: Over-provisioning wastes money, while under-provisioning hurts performance. Scaling correctly balances both.
- Reliability: Production workloads demand high availability, automated backups, and disaster recovery strategies.
- Query performance: As data grows, unoptimized queries that ran in seconds during prototyping can take minutes or hours.
Understanding Redshift Architecture
Before diving into scaling strategies, it is important to understand how Redshift works under the hood. Redshift uses a massively parallel processing (MPP) architecture. A cluster consists of a leader node and one or more compute nodes. The leader node parses queries and develops execution plans, while compute nodes execute the actual work in parallel.
Each compute node is divided into slices, and each slice is allocated a portion of the node's memory and disk space. Data is distributed across slices using a distribution style. Understanding this distribution is critical because it directly impacts query performance and scaling behavior.
Choosing the Right Node Type and Cluster Size
Redshift offers several node types, each suited for different workloads. The RA3 family is the current generation and separates compute from storage, allowing you to scale them independently.
Node Type Comparison
- RA3: Recommended for most workloads. Uses managed storage, so you only pay for the storage you use and can scale compute independently.
- DC2: Best for workloads under 1 TB where local storage with high performance is needed.
- DS2: Previous generation, not recommended for new clusters.
Here is an example of creating a production-ready RA3 cluster using the AWS CLI:
aws redshift create-cluster \
--cluster-name production-redshift \
--node-type ra3.4xlarge \
--number-of-nodes 4 \
--master-username admin \
--master-user-password 'MySecurePassword123!' \
--db-name analytics \
--cluster-parameter-group-name production-params \
--cluster-subnet-group-name production-subnet-group \
--vpc-security-group-ids sg-12345678 \
--publicly-accessible false \
--encrypted \
--automated-snapshot-retention-period 7 \
--tags Key=Environment,Value=Production
When choosing the number of nodes, start with your expected data volume and query concurrency. A general rule of thumb is that RA3 nodes can handle roughly 2-4 concurrent queries per node for complex analytical workloads, though this varies significantly based on query complexity.
Designing Tables for Scale
Table design is arguably the most important factor in scaling Redshift. The three key decisions for every table are distribution style, sort key, and encoding.
Distribution Styles
Redshift offers three distribution styles:
- AUTO: Redshift decides the distribution. Good for prototyping but not ideal for production.
- EVEN: Data is distributed evenly across slices in a round-robin fashion. Good when no clear distribution key exists.
- KEY: Data is distributed based on a specific column. Ideal for tables that are frequently joined on a common column.
- ALL: The entire table is copied to every node. Useful for small dimension tables (under 2-3 million rows).
Here is an example of creating well-designed production tables:
-- Large fact table distributed by customer_id
CREATE TABLE sales (
sale_id BIGINT ENCODE az64,
customer_id BIGINT ENCODE az64,
product_id BIGINT ENCODE az64,
store_id INTEGER ENCODE az64,
sale_date DATE ENCODE az64,
quantity INTEGER ENCODE az64,
unit_price DECIMAL(10,2) ENCODE az64,
total_amount DECIMAL(12,2) ENCODE az64,
created_at TIMESTAMP ENCODE az64
)
DISTSTYLE KEY
DISTKEY (customer_id)
SORTKEY (sale_date, customer_id);
-- Small dimension table using ALL distribution
CREATE TABLE products (
product_id BIGINT ENCODE az64,
product_name VARCHAR(200) ENCODE lzo,
category VARCHAR(100) ENCODE lzo,
unit_cost DECIMAL(10,2) ENCODE az64,
is_active BOOLEAN ENCODE raw
)
DISTSTYLE ALL
SORTKEY (product_id);
-- Medium dimension table distributed by key
CREATE TABLE customers (
customer_id BIGINT ENCODE az64,
email VARCHAR(255) ENCODE lzo,
first_name VARCHAR(100) ENCODE lzo,
last_name VARCHAR(100) ENCODE lzo,
signup_date DATE ENCODE az64,
country_code CHAR(2) ENCODE dict32,
lifetime_value DECIMAL(12,2) ENCODE az64
)
DISTSTYLE KEY
DISTKEY (customer_id)
SORTKEY (customer_id, signup_date);
Choosing Sort Keys
Sort keys determine the physical order of data on disk. Choosing the right sort key can dramatically reduce the amount of data scanned during queries. Best practices include:
- Use columns frequently used in range filters or equality filters as sort keys.
- Place the most selective column first in a compound sort key.
- Consider using interleaved sort keys for tables queried with diverse filter patterns, but be aware they have higher maintenance overhead.
- Time-series data should almost always use the timestamp column as the first sort key column.
Loading Data Efficiently
How you load data into Redshift significantly impacts performance at scale. The COPY command is the most efficient way to load data, and using it correctly is essential for production workloads.
Best Practices for Data Loading
- Load data in bulk batches rather than individual INSERT statements.
- Use compressed files (GZIP, LZOP, or BZIP2) to reduce transfer time.
- Split large files into multiple smaller files (at least one per slice) to enable parallel loading.
- Use manifest files for precise control over which files are loaded.
- Load from S3 rather than from remote hosts for best performance.
Here is an example of an efficient COPY command:
COPY sales
FROM 's3://my-data-bucket/sales/2024/01/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftCopyRole'
FORMAT AS CSV
DELIMITER ','
QUOTE '"'
IGNOREHEADER 1
GZIP
COMPUPDATE ON
STATUPDATE ON
MAXERROR 0
REGION 'us-east-1';
For continuous data loading, consider using Amazon Redshift Spectrum to query data directly from S3 without loading it into the cluster, or use the COPY command with a scheduled pipeline:
-- Example stored procedure for incremental loading
CREATE OR REPLACE PROCEDURE load_incremental_sales(p_date VARCHAR)
AS $$
BEGIN
-- Load new data into a staging table
CREATE TEMP TABLE staging_sales (LIKE sales);
COPY staging_sales
FROM 's3://my-data-bucket/sales/' || p_date || '/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftCopyRole'
FORMAT AS CSV GZIP;
-- Upsert into the main table
DELETE FROM sales
USING staging_sales
WHERE sales.sale_id = staging_sales.sale_id;
INSERT INTO sales
SELECT * FROM staging_sales;
-- Update statistics
ANALYZE sales;
END;
$$ LANGUAGE plpgsql;
Workload Management (WLM)
As concurrency increases, you need workload management to ensure that critical queries get the resources they need while preventing runaway queries from degrading overall performance. Redshift offers two WLM options: automatic WLM and manual WLM.
Automatic WLM
Automatic WLM is the recommended approach for most production workloads. It dynamically manages query concurrency and memory allocation based on workload patterns. You define query queues with different priorities, and Redshift handles the rest.
-- Enable automatic WLM with query priority
SET wlm_query_slot_count TO 1;
-- Create a parameter group with automatic WLM configuration
-- This is typically done via AWS CLI or Console
/*
Queue 1:
- Priority: Highest
- Query groups: 'critical_reports'
- Concurrency: Auto
Queue 2:
- Priority: Normal
- Query groups: 'default'
- Concurrency: Auto
Queue 3:
- Priority: Low
- Query groups: 'batch_loads'
- Concurrency: Auto
*/
Assigning Queries to Queues
You can route queries to specific WLM queues using query groups or user groups:
-- Assign a query to the critical reports queue
SET query_group TO 'critical_reports';
SELECT
c.country_code,
SUM(s.total_amount) AS revenue
FROM sales s
JOIN customers c ON s.customer_id = c.customer_id
WHERE s.sale_date >= '2024-01-01'
GROUP BY c.country_code
ORDER BY revenue DESC;
RESET query_group;
Query Monitoring Rules
Query monitoring rules allow you to automatically take action when queries exceed certain thresholds. This is essential for protecting production performance:
-- Example query monitoring rules (configured in parameter group)
/*
Rule 1: Long-running query
- Metric: query_execution_time
- Condition: > 3600 (1 hour)
- Action: Log and abort
Rule 2: High CPU query
- Metric: cpu_usage
- Condition: > 90%
- Rule: query_execution_time > 300
- Action: Log
Rule 3: Excessive return rows
- Metric: return_rows
- Condition: > 1000000
- Action: Log and hop
*/
Query Optimization at Scale
As data grows, query optimization becomes critical. Here are the key strategies for keeping queries fast in production.
Analyze and Vacuum Regularly
After significant data loads or deletes, you must update table statistics and reclaim space:
-- Update statistics for the query planner
ANALYZE sales;
ANALYZE customers;
ANALYZE products;
-- Reclaim space from deleted rows and re-sort data
VACUUM sales TO 100 PERCENT;
VACUUM customers TO 100 PERCENT;
-- For very large tables, consider vacuuming during low-traffic periods
VACUUM REINDEX sales;
Use Late Materialization
Redshift supports late materialization, which delays joining and projecting columns until after filtering. You can encourage this behavior by structuring queries to filter early:
-- Inefficient: joins everything before filtering
SELECT c.customer_id, c.email, s.total_amount
FROM customers c
JOIN sales s ON c.customer_id = s.customer_id
WHERE s.sale_date >= '2024-01-01'
AND s.total_amount > 1000;
-- More efficient: use a CTE to filter first
WITH high_value_sales AS (
SELECT customer_id, total_amount
FROM sales
WHERE sale_date >= '2024-01-01'
AND total_amount > 1000
)
SELECT c.customer_id, c.email, hvs.total_amount
FROM customers c
JOIN high_value_sales hvs ON c.customer_id = hvs.customer_id;
Leverage Materialized Views
Materialized views pre-compute and store results, dramatically speeding up common aggregation queries:
-- Create a materialized view for daily revenue by country
CREATE MATERIALIZED VIEW mv_daily_revenue_by_country
AUTO REFRESH YES
AS
SELECT
s.sale_date,
c.country_code,
COUNT(*) AS transaction_count,
SUM(s.total_amount) AS total_revenue,
AVG(s.total_amount) AS avg_transaction
FROM sales s
JOIN customers c ON s.customer_id = c.customer_id
GROUP BY s.sale_date, c.country_code;
-- Query the materialized view for instant results
SELECT * FROM mv_daily_revenue_by_country
WHERE sale_date >= '2024-01-01'
ORDER BY total_revenue DESC;
-- Manually refresh if needed
REFRESH MATERIALIZED VIEW mv_daily_revenue_by_country;
Use Redshift Spectrum for Cold Data
For data that is rarely accessed, keep it in S3 and query it with Redshift Spectrum instead of storing it in the cluster. This reduces storage costs and cluster size:
-- Create an external schema pointing to S3 data
CREATE EXTERNAL SCHEMA spectrum
FROM DATA CATALOG
DATABASE 'analytics_glue_db'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftSpectrumRole'
CREATE EXTERNAL DATABASE IF NOT EXISTS;
-- Query historical data in S3 alongside current data in Redshift
SELECT
'current' AS data_source,
sale_date,
SUM(total_amount) AS revenue
FROM sales
WHERE sale_date >= '2024-01-01'
GROUP BY sale_date
UNION ALL
SELECT
'archive' AS data_source,
sale_date,
SUM(total_amount) AS revenue
FROM spectrum.sales_archive
WHERE sale_date < '2024-01-01'
GROUP BY sale_date
ORDER BY sale_date;
Monitoring and Alerting
Production Redshift clusters require comprehensive monitoring. Redshift provides several system tables and views for this purpose.
Key Metrics to Monitor
- CPU utilization: Sustained high CPU may indicate the need for more nodes or query optimization.
- Disk usage: Monitor to avoid running out of space, especially on DC2 nodes.
- Query queue wait times: High wait times indicate WLM tuning is needed.
- Database connections: Track to ensure connection pooling is working.
- WLM queue length: Long queues suggest concurrency issues.
Useful Monitoring Queries
-- Top 10 longest running queries in the last hour
SELECT
query,
userid,
querytxt,
datediff(seconds, starttime, endtime) AS duration_seconds,
starttime
FROM stl_query
WHERE starttime >= dateadd(hour, -1, getdate())
AND userid > 1
ORDER BY duration_seconds DESC
LIMIT 10;
-- Queries currently running
SELECT
pid,
userid,
query,
starttime,
elapsed,
substring(querytxt, 1, 100) AS query_text
FROM stv_recents
WHERE status = 'Running'
ORDER BY starttime;
-- Table storage and skew
SELECT
TRIM(name) AS table_name,
size AS size_mb,
tbl_rows,
skew_sortkey1,
skew_rows
FROM svv_table_info
WHERE schema = 'public'
ORDER BY size_mb DESC;
-- WLM queue state
SELECT
service_class,
num_executing_queries,
num_queued_queries,
num_executing_queries + num_queued_queries AS total_in_queue
FROM stv_wlm_service_class_state
WHERE service_class >= 6;
Setting Up CloudWatch Alarms
-- Create CloudWatch alarms for critical metrics via AWS CLI
aws cloudwatch put-metric-alarm \
--alarm-name "Redshift-HighCPU" \
--alarm-description "Redshift CPU above 85% for 10 minutes" \
--metric-name CPUUtilization \
--namespace AWS/Redshift \
--statistic Average \
--period 300 \
--threshold 85 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=ClusterIdentifier,Value=production-redshift \
--evaluation-periods 2 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:RedshiftAlerts
aws cloudwatch put-metric-alarm \
--alarm-name "Redshift-DiskSpaceLow" \
--alarm-description "Redshift disk usage above 80%" \
--metric-name PercentageDiskSpaceUsed \
--namespace AWS/Redshift \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=ClusterIdentifier,Value=production-redshift \
--evaluation-periods 2 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:RedshiftAlerts
High Availability and Disaster Recovery
Production Redshift clusters need robust high availability and disaster recovery strategies.
Elastic Resize
Elastic resize allows you to add or remove nodes with minimal downtime. This is the preferred method for scaling compute capacity:
-- Scale up from 4 to 8 nodes using elastic resize
aws redshift resize-cluster \
--cluster-identifier production-redshift \
--cluster-type multi-node \
--number-of-nodes 8 \
--node-type ra3.4xlarge \
--classic false
Snapshots and Automated Backups
Configure automated snapshots and create manual snapshots before major changes:
-- Create a manual snapshot before a major data load
aws redshift create-cluster-snapshot \
--cluster-identifier production-redshift \
--snapshot-identifier pre-migration-20240115
-- Restore from a snapshot if needed
aws redshift restore-from-cluster-snapshot \
--cluster-identifier production-redshift-restored \
--snapshot-identifier pre-migration-20240115
-- Configure snapshot copy to another region for DR
aws redshift enable-snapshot-copy \
--cluster-identifier production-redshift \
--destination-region us-west-2 \
--retention-period 7
Multi-AZ Deployment
For mission-critical workloads, enable Multi-AZ deployment for automatic failover:
-- Modify cluster to enable Multi-AZ
aws redshift modify-cluster \
--cluster-identifier production-redshift \
--availability-zone-relocation-status enabled \
--multi-az-enabled
Security Best Practices
Security is non-negotiable in production. Follow these practices:
- Use IAM authentication instead of database passwords where possible.
- Encrypt all clusters using AWS KMS.
- Place clusters in private subnets with no public access.
- Use security groups to restrict access to known IP ranges only.
- Implement row-level security for multi-tenant data.
- Audit all database activity using Redshift audit logging.
-- Enable audit logging
aws redshift enable-logging \
--cluster-identifier production-redshift \
--bucket-name redshift-audit-logs \
--s3-key-prefix production/
-- Create an IAM role for temporary credentials
aws iam create-role \
--role-name RedshiftDBUser \
--assume-role-policy-document file://trust-policy.json
-- Grant database permissions using IAM
GRANT USAGE ON SCHEMA analytics TO GROUP reporting_users;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO GROUP reporting_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
GRANT SELECT ON TABLES TO GROUP reporting_users;
Cost Optimization
Scaling Redshift efficiently also means managing costs. Here are key strategies:
Reserved Nodes
For steady-state production workloads, purchase reserved nodes to save up to 75% compared to on-demand pricing:
-- Purchase a reserved node offering
aws redshift purchase-reserved-node-offering \
--reserved-node-offering-id 12345678-90ab-cdef-EXAMPLE \
--node-count 4
Concurrency Scaling
Concurrency Scaling automatically adds transient cluster capacity when needed, which can be more cost-effective than over-provisioning:
-- Enable concurrency scaling on specific queues
-- This is configured in the WLM parameter group
/*
For each queue that needs burst capacity:
- Set Concurrency Scaling mode to "Auto"
- Monitor usage via CloudWatch metrics
- ConcurrencyScalingActiveClusters metric shows how many
scaling clusters are active
*/
Data Lifecycle Management
-- Example: Move old partitions to S3 and delete from Redshift
BEGIN;
-- Unload old data to S3
UNLOAD ('SELECT * FROM sales WHERE sale_date < ''2023-01-01''')
TO 's3://my-archive-bucket/sales/pre-2023/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftUnloadRole'
FORMAT AS PARQUET
PARTITION BY (sale_date);
-- Delete old data from Redshift
DELETE FROM sales WHERE sale_date < '2023-01-01';
-- Vacuum to reclaim space
VACUUM sales TO 100 PERCENT;
COMMIT;
Putting It All Together: A Production Checklist
Before promoting your Redshift cluster to production, verify the following:
- Cluster uses RA3 nodes with appropriate sizing for your workload.
- All tables have explicit distribution styles and sort keys.
- Column encodings are applied to reduce storage and improve scan performance.
- WLM is configured with automatic mode and appropriate query queues.
- Query monitoring rules are in place to catch runaway queries.
- Automated snapshots are configured with appropriate retention.
- Manual snapshot workflow is documented for pre-deployment backups.
- CloudWatch alarms are set for CPU, disk space, and queue wait times.
- Cluster is encrypted and placed in a private subnet.
- Audit logging is enabled and logs are being archived.
- Data loading uses the COPY command with compressed, split files.
- Materialized views are created for common aggregation queries.
- Vacuum and analyze jobs are scheduled during low-traffic periods.
- Reserved nodes are purchased for baseline capacity.
- Disaster recovery procedures are documented and tested.
Conclusion
Scaling Redshift from a prototype to a production system requires attention to architecture, table design, workload management, query optimization, monitoring, security, and cost management. By carefully choosing distribution styles and sort keys, implementing robust data loading pipelines, configuring WLM for your concurrency needs, and establishing comprehensive monitoring and disaster recovery procedures, you can build a Redshift environment that performs reliably at scale. The key is to treat each of these areas as an ongoing practice rather than a one-time setup — continuously monitor performance, refine table designs as query patterns evolve, and adjust cluster sizing as your data and user base grow. With the strategies and examples covered in this tutorial, you are well-equipped to take your Redshift prototype and transform it into a robust, production-grade data warehouse.