šŸŽ‰ 75% of content is free forever — Unlock Premium from $10/mo →
CW
šŸ’¼ Servicesā„¹ļø Aboutāœ‰ļø ContactView Pricing Plansfrom $10

Redshift Serverless for Data Engineers

AWS Data EngineeringRedshift Serverless Architecture⭐ Premium

Advertisement

Redshift Serverless

Master auto-scaling analytics, data sharing, and serverless data warehousing on AWS.

22 min readIntermediate

What is Redshift Serverless?

Redshift Serverless is a fully managed, serverless data warehousing solution from AWS that allows you to run analytics workloads without managing any infrastructure. It automatically provisions and scales compute and storage resources based on your workload demands.

Core Concepts

ConceptDescription
RPUs (Redshift Processing Units)Compute capacity measured in RPUs that scale automatically
NamespaceA collection of database objects, users, and roles
WorkgroupA collection of compute resources and configuration
Base CapacityMinimum RPU capacity your serverless endpoint can use
Data SharedCross-account or cross-region data sharing capability

Redshift Serverless Architecture

Redshift Serverless ArchitectureSQL ClientBI Tools, ScriptsODBC/JDBCConnection DriversServerless EndpointWorkgroup | NamespaceData SourcesS3, RDS, DynamoDBCompute Layer (Auto-Scaling 128-512 RPUs)Head NodeQuery PlanningCompute Node 1Columnar EngineCompute Node 2Massively ParallelCompute Node NAuto-ScaledManaged Storage LayerAuto-scaling | Columnar compressed | Encrypted | Cross-AZ replicatedRA3 nodes | Managed storage | $0.024/GB/month | Backup includedCross-Account Data SharingLive data access without copyingServerless FeaturesPay-per-query | No cluster mgmtCloudWatch IntegrationRPU usage, query metrics, alerts

How Auto-scaling Works

  1. Query Submission: User submits a SQL query to the serverless endpoint
  2. Capacity Analysis: Redshift analyzes query complexity and data volume
  3. RPU Allocation: Automatically allocates appropriate RPUs (128-512)
  4. Query Execution: Query runs on allocated compute resources
  5. Scale Down: Resources scale back when workload decreases

Real-World Project Structure

Architecture Diagram
redshift-serverless-production/
ā”œā”€ā”€ namespaces/
│   ā”œā”€ā”€ analytics-namespace/
│   │   ā”œā”€ā”€ workgroup-config.json
│   │   ā”œā”€ā”€ iam-roles/
│   │   │   ā”œā”€ā”€ rs-admin-role.json
│   │   │   ā”œā”€ā”€ rs-etl-role.json
│   │   │   └── rs-analyst-role.json
│   │   └── database-config/
│   │       ā”œā”€ā”€ users.sql
│   │       └── grants.sql
│   └── data-sharing-namespace/
│       └── workgroup-config.json
ā”œā”€ā”€ schemas/
│   ā”œā”€ā”€ raw/
│   │   ā”œā”€ā”€ orders.sql
│   │   ā”œā”€ā”€ customers.sql
│   │   └── products.sql
│   ā”œā”€ā”€ silver/
│   │   ā”œā”€ā”€ dim_customers.sql
│   │   ā”œā”€ā”€ dim_products.sql
│   │   └── fact_orders.sql
│   └── gold/
│       ā”œā”€ā”€ revenue_summary.sql
│       └── customer_segments.sql
ā”œā”€ā”€ etl-pipeline/
│   ā”œā”€ā”€ glue-jobs/
│   │   ā”œā”€ā”€ s3-to-redshift-load.py
│   │   └── redshift-to-s3-export.py
│   ā”œā”€ā”€ lambda-functions/
│   │   └── trigger-etl.py
│   └── step-functions/
│       └── daily-pipeline.json
ā”œā”€ā”€ monitoring/
│   ā”œā”€ā”€ cloudwatch-alarms.yaml
│   ā”œā”€ā”€ dashboards/
│   │   ā”œā”€ā”€ query-performance.json
│   │   └── cost-tracking.json
│   └── billing-alerts.yaml
└── security/
    ā”œā”€ā”€ kms-encryption/
    │   └── encryption-key.yaml
    ā”œā”€ā”€ vpc-config/
    │   └── subnet-group.json
    └── resource-policies/
        └── cross-account-sharing.json

Production Python Code

import boto3
import json
import logging
import time
from datetime import datetime
from typing import Dict, List, Optional
import psycopg2
from psycopg2.extras import RealDictCursor

logger = logging.getLogger(__name__)

class RedshiftServerlessManager:
    """Production-grade Redshift Serverless manager."""

    def __init__(
        self,
        endpoint: str,
        database: str,
        user: str,
        password: str,
        port: int = 5439
    ):
        self.endpoint = endpoint
        self.database = database
        self.user = user
        self.password = password
        self.port = port

    def get_connection(self):
        """Create a database connection."""
        return psycopg2.connect(
            host=self.endpoint,
            database=self.database,
            user=self.user,
            password=self.password,
            port=self.port,
            sslmode='require',
            connect_timeout=10,
            options='-c statement_timeout=300000'
        )

    def execute_query(
        self,
        sql: str,
        params: Optional[tuple] = None,
        fetch_results: bool = True
    ) -> List[Dict]:
        """Execute a query with error handling and retry logic."""
        max_retries = 3
        for attempt in range(max_retries):
            conn = None
            try:
                conn = self.get_connection()
                with conn.cursor(cursor_factory=RealDictCursor) as cur:
                    cur.execute(sql, params)
                    if fetch_results and cur.description:
                        results = cur.fetchall()
                        conn.commit()
                        return [dict(row) for row in results]
                    conn.commit()
                    return []
            except psycopg2.OperationalError as e:
                logger.warning(
                    f"Query attempt {attempt + 1} failed: {e}"
                )
                if attempt == max_retries - 1:
                    raise
                time.sleep(2 ** attempt)
            except Exception as e:
                logger.error(f"Query failed: {e}")
                if conn:
                    conn.rollback()
                raise
            finally:
                if conn:
                    conn.close()

    def load_from_s3(
        self,
        table_name: str,
        s3_path: str,
        iam_role: str,
        format: str = 'parquet',
        compression: str = 'gzip',
        manifest_file: Optional[str] = None
    ) -> Dict:
        """Load data from S3 into Redshift Serverless using COPY."""
        copy_sql = f"""
        COPY {table_name}
        FROM '{s3_path}'
        IAM_ROLE '{iam_role}'
        FORMAT AS {format}
        """

        if compression:
            copy_sql += f"\nGZIP" if compression == 'gzip' else f"\n{compression.upper()}"

        if manifest_file:
            copy_sql += f"\nMANIFEST"

        copy_sql += """
        STATUPDATE ON
        COMPUPDATE OFF
        REGION 'us-east-1';
        """

        start_time = time.time()
        try:
            self.execute_query(copy_sql)
            elapsed = time.time() - start_time
            logger.info(f"COPY into {table_name} completed in {elapsed:.2f}s")
            return {'status': 'success', 'duration': elapsed}
        except Exception as e:
            logger.error(f"COPY failed: {e}")
            raise

    def create_materialized_view(
        self,
        view_name: str,
        definition: str,
        auto_refresh: bool = False
    ) -> None:
        """Create or replace a materialized view."""
        refresh_clause = ""
        if auto_refresh:
            refresh_clause = " AUTO REFRESH YES"

        sql = f"""
        CREATE MATERIALIZED VIEW {view_name}{refresh_clause} AS
        {definition};
        """
        self.execute_query(sql)
        logger.info(f"Created materialized view: {view_name}")

    def refresh_materialized_view(
        self,
        view_name: str
    ) -> None:
        """Refresh a materialized view."""
        sql = f"REFRESH MATERIALIZED VIEW {view_name};"
        start_time = time.time()
        self.execute_query(sql)
        elapsed = time.time() - start_time
        logger.info(
            f"Refreshed {view_name} in {elapsed:.2f}s"
        )

    def get_query_performance(self) -> List[Dict]:
        """Get recent query performance metrics."""
        sql = """
        SELECT
            query,
            userid,
            querytxt,
            starttime,
            endtime,
            elapsed / 1000000 AS elapsed_seconds,
            rows AS rows_returned,
            bytes / 1024 / 1024 AS mb_scanned
        FROM stl_query_metrics
        WHERE starttime > DATEADD(hour, -24, GETDATE())
        ORDER BY elapsed DESC
        LIMIT 20;
        """
        return self.execute_query(sql)


class RedshiftServerlessProvisioner:
    """Provision and manage Redshift Serverless resources."""

    def __init__(self, region: str = 'us-east-1'):
        self.client = boto3.client('redshift-serverless', region_name=region)

    def create_namespace(
        self,
        namespace_name: str,
        admin_username: str,
        admin_password: str,
        db_name: str = 'dev',
        iam_roles: Optional[List[str]] = None,
        security_group_ids: Optional[List[str]] = None
    ) -> Dict:
        """Create a Redshift Serverless namespace."""
        try:
            kwargs = {
                'namespaceName': namespace_name,
                'adminUsername': admin_username,
                'adminUserPassword': admin_password,
                'dbName': db_name,
                'tags': [
                    {'key': 'Environment', 'value': 'production'},
                    {'key': 'Service', 'value': 'analytics'}
                ]
            }
            if iam_roles:
                kwargs['iamRoles'] = iam_roles
            if security_group_ids:
                kwargs['securityGroupIds'] = security_group_ids

            response = self.client.create_namespace(**kwargs)
            logger.info(f"Created namespace: {namespace_name}")
            return response['namespace']
        except Exception as e:
            logger.error(f"Failed to create namespace: {e}")
            raise

    def create_workgroup(
        self,
        workgroup_name: str,
        namespace_name: str,
        base_capacity: int = 128,
        security_group_ids: Optional[List[str]] = None,
        subnet_ids: Optional[List[str]] = None,
        publicly_accessible: bool = False
    ) -> Dict:
        """Create a Redshift Serverless workgroup."""
        try:
            kwargs = {
                'workgroupName': workgroup_name,
                'namespaceName': namespace_name,
                'baseCapacity': base_capacity,
                'publiclyAccessible': publicly_accessible,
                'tags': [
                    {'key': 'Environment', 'value': 'production'}
                ]
            }
            if security_group_ids:
                kwargs['securityGroupIds'] = security_group_ids
            if subnet_ids:
                kwargs['subnetIds'] = subnet_ids

            response = self.client.create_workgroup(**kwargs)
            logger.info(f"Created workgroup: {workgroup_name}")
            return response['workgroup']
        except Exception as e:
            logger.error(f"Failed to create workgroup: {e}")
            raise

    def get_workgroup_status(
        self,
        workgroup_name: str
    ) -> Dict:
        """Get workgroup status and endpoint."""
        try:
            response = self.client.get_workgroup(
                workgroupName=workgroup_name
            )
            return response['workgroup']
        except Exception as e:
            logger.error(f"Failed to get workgroup: {e}")
            raise

    def create_data_share(
        self,
        namespace_name: str,
        database_name: str,
        schema_name: str,
        shared_from: str
    ) -> Dict:
        """Create a data share for cross-account sharing."""
        try:
            response = self.client.create_data_share(
                sourceArn=f'arn:aws:redshift-serverless:us-east-1:*:namespace/{namespace_name}',
                allowPubliclyAccessibleSharing=True,
                producerArn=f'arn:aws:redshift-serverless:us-east-1:*:namespace/{shared_from}'
            )
            logger.info(f"Created data share for {database_name}.{schema_name}")
            return response
        except Exception as e:
            logger.error(f"Failed to create data share: {e}")
            raise


# Production usage
if __name__ == '__main__':
    provisioner = RedshiftServerlessProvisioner()
    namespace = provisioner.create_namespace(
        namespace_name='analytics-prod',
        admin_username='admin',
        admin_password='SecureP@ss123!',
        db_name='analytics',
        iam_roles=['arn:aws:iam::*:role/redshift-serverless-role']
    )

    workgroup = provisioner.create_workgroup(
        workgroup_name='analytics-wg',
        namespace_name='analytics-prod',
        base_capacity=128,
        publicly_accessible=False
    )
    print(f"Endpoint: {workgroup.get('endpoint', {}).get('address')}")

Production Bash Commands

#!/bin/bash
# Redshift Serverless monitoring and query optimization script

set -euo pipefail

WORKGROUP="${1:-analytics-wg}"
REGION="${AWS_REGION:-us-east-1}"

echo "=== Redshift Serverless Health Check ==="
echo "Workgroup: ${WORKGROUP}"
echo "Region: ${REGION}"
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"

# Get workgroup status
WG_STATUS=$(aws redshift-serverless get-workgroup \
  --workgroup-name "${WORKGROUP}" \
  --region "${REGION}" \
  --query 'workgroup' \
  --output json)

STATUS=$(echo "$WG_STATUS" | jq -r '.status')
ENDPOINT=$(echo "$WG_STATUS" | jq -r '.endpoint.address')
PORT=$(echo "$WG_STATUS" | jq -r '.endpoint.port')
BASE_CAPACITY=$(echo "$WG_STATUS" | jq -r '.baseCapacity')

echo "Status: ${STATUS}"
echo "Endpoint: ${ENDPOINT}:${PORT}"
echo "Base Capacity: ${BASE_CAPACITY} RPUs"

# Check namespace
NAMESPACE=$(echo "$WG_STATUS" | jq -r '.namespaceName')
NS_INFO=$(aws redshift-serverless get-namespace \
  --namespace-name "${NAMESPACE}" \
  --region "${REGION}" \
  --query 'namespace' \
  --output json)

DB_NAME=$(echo "$NS_INFO" | jq -r '.dbName')
IAM_ROLES=$(echo "$NS_INFO" | jq -r '.iamRoles[]?.roleArn // "none"')
echo "Database: ${DB_NAME}"
echo "IAM Roles: ${IAM_ROLES}"

# Check active queries
echo ""
echo "=== Active Queries ==="
PGPASSWORD="${RS_PASSWORD:-}" psql \
  -h "${ENDPOINT}" \
  -p "${PORT}" \
  -U "${RS_USER:-admin}" \
  -d "${DB_NAME}" \
  -c "SELECT query, pid, user_name, starttime, text 
       FROM stl_query 
       WHERE starttime > DATEADD(hour, -1, GETDATE()) 
       ORDER BY starttime DESC 
       LIMIT 10;" 2>/dev/null || echo "Could not connect to database"

# Monitor RPU usage
echo ""
echo "=== RPU Usage (Last 24 Hours) ==="
aws cloudwatch get-metric-statistics \
  --namespace AWS/Redshift-Serverless \
  --metric-name ServerlessRPUCost \
  --dimensions Name:workgroup,Value="${WORKGROUP}" \
  --start-time "$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 3600 \
  --statistics Sum \
  --region "${REGION}" | jq -r '.Datapoints[] | "\(.Timestamp): \(.Sum | . * 100 | round / 100) RPU-hours"'

# Check query performance
echo ""
echo "=== Top Queries by Duration (Last 24h) ==="
PGPASSWORD="${RS_PASSWORD:-}" psql \
  -h "${ENDPOINT}" \
  -p "${PORT}" \
  -U "${RS_USER:-admin}" \
  -d "${DB_NAME}" \
  -c "SELECT 
        query,
        userid,
        starttime,
        endtime,
        (endtime - starttime) / 1000000 AS duration_sec,
        rows AS rows_returned,
        bytes / 1024 / 1024 AS mb_scanned
      FROM stl_query_metrics
      WHERE starttime > DATEADD(hour, -24, GETDATE())
        AND bytes > 0
      ORDER BY duration_sec DESC
      LIMIT 10;" 2>/dev/null || echo "Could not fetch query metrics"

# Check storage usage
echo ""
echo "=== Storage Usage ==="
PGPASSWORD="${RS_PASSWORD:-}" psql \
  -h "${ENDPOINT}" \
  -p "${PORT}" \
  -U "${RS_USER:-admin}" \
  -d "${DB_NAME}" \
  -c "SELECT 
        database,
        ROUND(SUM(size) / 1024.0 / 1024.0 / 1024.0, 2) AS size_gb
      FROM stv_partitions
      WHERE ptype = 'data'
      GROUP BY database
      ORDER BY size_gb DESC;" 2>/dev/null || echo "Could not fetch storage"

# Check data sharing status
echo ""
echo "=== Data Sharing Status ==="
aws redshift-serverless list-data-shares \
  --region "${REGION}" \
  --query 'dataShares[*].[shareArn,status]' \
  --output table 2>/dev/null || echo "No data shares configured"

echo ""
echo "Health check complete."

Why This Matters

Redshift Serverless eliminates the need to provision and manage clusters, making it ideal for variable workloads, development environments, and organizations that want to focus on analytics rather than infrastructure. The pay-per-query pricing model and auto-scaling capabilities ensure cost efficiency while maintaining performance.

Mathematical Formulas

Performance Considerations

FactorRecommendationImpact
Base CapacityStart with 128 RPUs, adjust based on workloadHigh - affects query speed and cost
Materialized ViewsPre-compute complex aggregationsHigh - reduces query execution time
Result CachingEnable for repeated queriesMedium - returns cached results instantly
Distribution KeysChoose high-cardinality, evenly distributed columnsHigh - balances data across nodes
Sort KeysUse compound sort keys for range queriesMedium - improves query filtering
VacuumMaintain table statistics after large loadsMedium - ensures accurate query plans
ConcurrencyUse WLM queues for workload isolationHigh - prevents query interference
Data SharingUse for cross-account analyticsMedium - avoids data duplication

Security Considerations

  • VPC Deployment: Deploy workgroups in private subnets for network isolation
  • IAM Authentication: Use IAM roles instead of database passwords where possible
  • Encryption at Rest: Enable KMS encryption for all data
  • Enhanced VPC Routing: Route all traffic through VPC for DLP and monitoring
  • Audit Logging: Enable query logging for compliance
  • Resource Policies: Control cross-account data sharing access
  • Parameter Groups: Control database configuration parameters
  • CloudTrail: Log all Redshift Serverless API calls

Common Pitfalls

PitfallConsequenceSolution
Setting base capacity too highHigher costs for light workloadsStart at 128 RPUs and monitor
Ignoring RPU consumptionUnexpected cost spikesSet up CloudWatch billing alerts
No materialized viewsSlow repeated queriesPre-compute common aggregations
Poor distribution keysData skew, slow joinsAnalyze query patterns for key selection
Skipping vacuumDegraded query performanceSchedule vacuum after large loads
No concurrency controlsResource contentionConfigure WLM queues for workload isolation
Disabling audit loggingCompliance violationsAlways enable for production
Not monitoring storageAuto-scaling cost surprisesTrack storage growth and set alarms

Interview Questions & Answers

Q1: What is the difference between Redshift Serverless and Provisioned Redshift?

Answer: Redshift Serverless automatically scales compute capacity (128-512 RPUs) based on workload demands with pay-per-query pricing, eliminating infrastructure management. Provisioned Redshift requires manual cluster provisioning and management with hourly pricing, but offers reserved instance discounts for steady workloads. Serverless is ideal for variable/unpredictable workloads, while Provisioned is better for steady 24/7 operations.

Q2: How does auto-scaling work in Redshift Serverless?

Answer: When queries are submitted, Redshift Serverless analyzes workload complexity and data volume, then automatically allocates appropriate RPUs (128-512). After query execution, resources scale back down. The base capacity sets the minimum, while the maximum can reach 512 RPUs for peak demands. Cost is based on actual RPU-hours consumed, not provisioned capacity.

Q3: What are the limitations of data sharing in Redshift Serverless?

Answer: Data sharing is read-only (consumers cannot modify shared data), requires compatible Redshift versions, and cross-region sharing may have latency considerations. Shared data must be in dedicated schemas, and privileges are managed at database, schema, and table levels. Data sharing requires IAM roles and resource policies for cross-account access.

Q4: When should you choose Redshift Serverless over Provisioned?

Answer: Choose Serverless for: variable/unpredictable workloads, development/testing environments, ad-hoc analytics, new projects with unknown workload patterns, and when you want to avoid infrastructure management. Choose Provisioned for: steady 24/7 workloads, large-scale ETL, when reserved instance discounts are beneficial, and for custom configuration requirements.

Q5: How do you optimize costs in Redshift Serverless?

Answer: Monitor RPU usage via CloudWatch, use result caching to avoid redundant queries, schedule heavy queries during off-peak hours, start with minimum base capacity (128 RPUs), and leverage materialized views to reduce query complexity and execution time. Set up billing alerts and use the usage dashboard to track RPU consumption patterns.

Q6: What is the maximum RPUs Redshift Serverless can scale to?

Answer: Redshift Serverless can auto-scale up to 512 RPUs. The base capacity you configure (minimum 128 RPUs) determines the starting point, and it will scale up to 512 RPUs based on workload demands. You pay only for the RPUs consumed during query execution.

Q7: Can you migrate from Provisioned to Serverless Redshift?

Answer: Yes, AWS provides migration paths from Provisioned to Serverless. You can use the Redshift console to convert provisioned clusters to serverless, though you should test workload compatibility and performance characteristics first. Use snapshot restore or cross-account data sharing for migration strategies.

Q8: What security features are available in Redshift Serverless?

Answer: VPC deployment, IAM authentication, encryption at rest and in transit, audit logging, enhanced VPC routing, and fine-grained access control through IAM roles and database privileges. All features available in Provisioned Redshift are also available in Serverless, including parameter groups and resource policies.

QuizBox

See Also

šŸ”’

Premium Content

Redshift Serverless for Data Engineers

You've previewed the first section. Unlock this full lesson and 900+ advanced tutorials with a Premium plan.

šŸŽÆEnd-to-end Projects
šŸ’¼Interview Prep
šŸ“œCertificates
šŸ¤Community Access

Already a member? Log in

Advertisement