← Back to DevBytes

Scaling Redshift: From Prototype to Production

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:

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

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:

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:

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

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

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:

-- 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:

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.

— Ad —

Google AdSense will appear here after approval

← Back to all articles