Amazon Redshift는 페타바이트 규모의 데이터 웨어하우스 서비스입니다. MPP(Massively Parallel Processing) 아키텍처와 컬럼형 저장으로 분석 쿼리에 최적화되어 있습니다.
┌─────────────────────────────────────────────────────────────────────┐
│ Redshift Cluster Architecture │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ Leader Node ││
│ │ • SQL 파싱 및 쿼리 계획 생성 ││
│ │ • 쿼리 분산 및 결과 집계 ││
│ │ • 클라이언트 연결 관리 ││
│ └──────────────────────────┬──────────────────────────────────────┘│
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Compute Node 1 │ │ Compute Node 2 │ │ Compute Node N │ │
│ │ │ │ │ │ │ │
│ │ ┌─────┐ ┌─────┐│ │ ┌─────┐ ┌─────┐│ │ ┌─────┐ ┌─────┐│ │
│ │ │Slice│ │Slice││ │ │Slice│ │Slice││ │ │Slice│ │Slice││ │
│ │ │ 1 │ │ 2 ││ │ │ 3 │ │ 4 ││ │ │ N │ │ N+1 ││ │
│ │ └─────┘ └─────┘│ │ └─────┘ └─────┘│ │ └─────┘ └─────┘│ │
│ │ │ │ │ │ │ │
│ │ Local Storage │ │ Local Storage │ │ Local Storage │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ Managed Storage (RA3): │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ Amazon S3 ││
│ │ • 자동 데이터 계층화 ││
│ │ • 컴퓨팅과 스토리지 독립 확장 ││
│ └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘| 유형 | 스토리지 | 특징 | 사용 사례 |
|---|---|---|---|
| RA3 | Managed (S3) | 컴퓨팅/스토리지 분리 | 대부분의 워크로드 (권장) |
| DC2 | Local SSD | 고성능, 고정 스토리지 | 1TB 미만, 고성능 필요 |
| DS2 | Local HDD | 대용량, 저비용 | 레거시 (RA3 권장) |
RA3는 컴퓨팅과 스토리지를 독립적으로 확장할 수 있어 비용 효율적입니다. 자주 사용하는 데이터는 로컬 SSD에 캐시되어 성능도 우수합니다.
행 기반 저장 (OLTP) 컬럼 기반 저장 (OLAP/Redshift)
┌─────────────────────────┐ ┌─────────────────────────┐
│ id │ name │ amount │ │ id: 1, 2, 3, 4, 5... │
├────┼───────┼───────────┤ ├─────────────────────────┤
│ 1 │ Alice │ 100 │ │ name: Alice, Bob, ... │
│ 2 │ Bob │ 200 │ ├─────────────────────────┤
│ 3 │ Carol │ 150 │ │ amount: 100, 200, 150...│
└─────────────────────────┘ └─────────────────────────┘
SELECT SUM(amount) FROM orders;
행 기반: 모든 컬럼 읽기 필요 컬럼 기반: amount 컬럼만 읽기
████████████████████████████ ░░░░░░░░░░░░████████████████
장점:
• 필요한 컬럼만 읽어 I/O 감소
• 같은 타입 데이터 연속 → 압축률 향상 (최대 10x)
• 벡터화 처리로 CPU 효율 증가┌─────────────────────────────────────────────────────────────────────┐
│ Distribution Styles │
│ │
│ 1. KEY Distribution (DISTKEY) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ user=A │ │ user=B │ │ user=C │ 같은 키 값 → 같은 노드 │
│ │ user=A │ │ user=B │ │ user=C │ JOIN 시 데이터 이동 최소화 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 2. ALL Distribution (DISTSTYLE ALL) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ 전체복사│ │ 전체복사│ │ 전체복사│ 작은 테이블 전체 복제 │
│ └─────────┘ └─────────┘ └─────────┘ JOIN 시 네트워크 이동 없음 │
│ │
│ 3. EVEN Distribution (DISTSTYLE EVEN) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ Row 1,4 │ │ Row 2,5 │ │ Row 3,6 │ 라운드 로빈 분산 │
│ └─────────┘ └─────────┘ └─────────┘ 기본값, JOIN 시 재분산 필요 │
│ │
│ 4. AUTO Distribution │
│ Redshift가 테이블 크기에 따라 자동 선택 (권장) │
└─────────────────────────────────────────────────────────────────────┘// 분산키 설정 예시
-- KEY 분산: 자주 JOIN하는 컬럼
CREATE TABLE orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(10,2)
)
DISTSTYLE KEY
DISTKEY (customer_id);
-- ALL 분산: 작은 차원 테이블
CREATE TABLE regions (
region_id INT,
region_name VARCHAR(50)
)
DISTSTYLE ALL;
-- AUTO 분산: Redshift 자동 결정 (권장)
CREATE TABLE events (
event_id BIGINT,
event_type VARCHAR(50)
)
DISTSTYLE AUTO;SORTKEY (date, region)INTERLEAVED SORTKEY (a, b, c)-- 시계열 데이터: 날짜를 첫 번째 정렬키
CREATE TABLE sales (
sale_date DATE,
product_id INT,
amount DECIMAL(10,2)
)
SORTKEY (sale_date, product_id);
-- Zone Map 활용
-- Redshift는 각 블록의 min/max 값을 저장
-- WHERE sale_date = '2024-01-15' 시 해당 블록만 스캔
┌─────────────────────────────────────────────────────────────────────┐
│ Block 1: sale_date [2024-01-01 ~ 2024-01-10] → 스킵 │
│ Block 2: sale_date [2024-01-11 ~ 2024-01-20] → 스캔 ✓ │
│ Block 3: sale_date [2024-01-21 ~ 2024-01-31] → 스킵 │
└─────────────────────────────────────────────────────────────────────┘| 특성 | Serverless | Provisioned |
|---|---|---|
| 관리 | 완전 자동 | 클러스터 관리 필요 |
| 스케일링 | 자동 (RPU 기반) | 수동/Elastic Resize |
| 비용 | 사용량 기반 (RPU-시간) | 노드 시간 기반 |
| 시작 시간 | 즉시 (웜 풀) | 수 분 |
| 적합한 사용 | 간헐적, 예측 불가 | 지속적, 예측 가능 |
// Terraform - Redshift Serverless
resource "aws_redshiftserverless_namespace" "analytics" {
namespace_name = "analytics"
db_name = "analytics_db"
admin_username = "admin"
admin_user_password = var.admin_password
iam_roles = [aws_iam_role.redshift.arn]
}
resource "aws_redshiftserverless_workgroup" "analytics" {
namespace_name = aws_redshiftserverless_namespace.analytics.namespace_name
workgroup_name = "analytics-workgroup"
base_capacity = 32 # RPU (8~512)
config_parameter {
parameter_key = "max_query_execution_time"
parameter_value = "3600" # 1시간
}
}Redshift Spectrum은 S3에 저장된 데이터를 Redshift로 로드하지 않고 직접 쿼리합니다. Data Lake와 Data Warehouse를 통합하는 Lakehouse 패턴을 구현합니다.
┌─────────────────────────────────────────────────────────────────────┐
│ Redshift Spectrum Architecture │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ Redshift Cluster ││
│ │ ┌─────────────┐ ┌─────────────────────────────────────────┐ ││
│ │ │ Local Data │ │ External Schema │ ││
│ │ │ (Redshift) │ │ CREATE EXTERNAL SCHEMA spectrum_schema │ ││
│ │ │ │ │ FROM DATA CATALOG │ ││
│ │ └─────────────┘ └──────────────────┬──────────────────────┘ ││
│ └─────────────────────────────────────────┼────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ Spectrum Layer ││
│ │ • 수천 개의 임시 노드로 병렬 처리 ││
│ │ • Parquet/ORC 컬럼 프루닝 ││
│ │ • 파티션 프루닝 ││
│ └──────────────────────────┬──────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ S3 Data Lake ││
│ │ s3://data-lake/ ││
│ │ └── historical_orders/year=2023/month=01/ ││
│ └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘// Spectrum 설정 및 쿼리
-- External Schema 생성 (Glue Catalog 연결)
CREATE EXTERNAL SCHEMA spectrum_schema
FROM DATA CATALOG
DATABASE 'analytics_db'
IAM_ROLE 'arn:aws:iam::123456789:role/RedshiftSpectrumRole'
REGION 'ap-northeast-2';
-- S3 데이터 직접 쿼리
SELECT
DATE_TRUNC('month', order_date) as month,
SUM(amount) as total_sales
FROM spectrum_schema.historical_orders
WHERE year = '2023'
GROUP BY 1;
-- Redshift 로컬 + S3 조인 (Lakehouse 패턴)
SELECT
c.customer_name,
SUM(o.amount) as total_amount
FROM redshift_schema.customers c
JOIN spectrum_schema.orders o ON c.customer_id = o.customer_id
WHERE o.year = '2024'
GROUP BY 1;Leader Node가 쿼리를 분산하고 Compute Node가 병렬 처리. RA3 노드로 컴퓨팅/스토리지 독립 확장.
DISTKEY로 JOIN 최적화, SORTKEY로 범위 쿼리 최적화. Zone Map을 활용한 블록 스킵으로 I/O 감소.
Serverless는 간헐적 워크로드에 적합, 자동 스케일링. Provisioned는 지속적 워크로드에 비용 효율적.
S3 데이터를 로드 없이 직접 쿼리. Lakehouse 패턴 구현. Parquet + 파티셔닝으로 비용 최적화.
Athena, QuickSight, OpenSearch를 활용한 데이터 분석 방법을 학습합니다.
Redshift 성능 최적화는 쿼리 성능, 데이터 로딩, 워크로드 관리 등 여러 측면에서 접근해야 합니다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Query Optimization Strategies │
│ │
│ 1. 컬럼 선택 최적화 │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ ❌ SELECT * FROM orders WHERE order_date = '2024-01-15' ││
│ │ ✅ SELECT order_id, customer_id, amount ││
│ │ FROM orders WHERE order_date = '2024-01-15' ││
│ │ ││
│ │ → 컬럼 스토어이므로 필요한 컬럼만 선택 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │
│ 2. SORTKEY 활용 │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ -- SORTKEY가 order_date인 경우 ││
│ │ ✅ WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31' ││
│ │ → Zone Map으로 블록 스킵 ││
│ │ ││
│ │ ❌ WHERE EXTRACT(month FROM order_date) = 1 ││
│ │ → 함수 적용 시 Zone Map 무효화 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │
│ 3. DISTKEY 활용 │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ -- 두 테이블 모두 customer_id가 DISTKEY인 경우 ││
│ │ SELECT * FROM orders o ││
│ │ JOIN customers c ON o.customer_id = c.customer_id ││
│ │ → 네트워크 전송 없이 로컬 조인 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘-- 실행 계획 확인
EXPLAIN
SELECT c.customer_name, SUM(o.amount) as total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2024-01-01'
GROUP BY c.customer_name
ORDER BY total_amount DESC
LIMIT 100;
-- 실행 계획 결과 예시
XN Limit (cost=1000000000000.00..1000000000000.10 rows=100)
-> XN Merge (cost=1000000000000.00..1000000000000.10 rows=1000)
Merge Key: sum(o.amount)
-> XN Network (cost=1000000000000.00..1000000000000.10 rows=1000)
Send to leader
-> XN Sort (cost=1000000000000.00..1000000000000.10 rows=1000)
Sort Key: sum(o.amount)
-> XN HashAggregate (cost=100000.00..100001.00 rows=1000)
-> XN Hash Join DS_DIST_NONE -- ✅ 로컬 조인
Hash Cond: (o.customer_id = c.customer_id)
-> XN Seq Scan on orders o
Filter: (order_date >= '2024-01-01')
-> XN Hash (cost=1000.00..1000.00 rows=10000)
-> XN Seq Scan on customers c// COPY 명령 최적화
-- 최적화된 COPY 명령
COPY orders
FROM 's3://data-lake/orders/'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftCopyRole'
FORMAT AS PARQUET
COMPUPDATE OFF -- 압축 분석 스킵 (이미 알고 있는 경우)
STATUPDATE OFF -- 통계 업데이트 스킵 (나중에 ANALYZE)
MAXERROR 100 -- 최대 100개 에러 허용
MANIFEST -- 매니페스트 파일 사용
;
-- 매니페스트 파일 예시 (manifest.json)
{
"entries": [
{"url": "s3://data-lake/orders/part-00000.parquet", "mandatory": true},
{"url": "s3://data-lake/orders/part-00001.parquet", "mandatory": true},
{"url": "s3://data-lake/orders/part-00002.parquet", "mandatory": true}
]
}
-- 슬라이스 수에 맞춘 파일 분할
-- 노드당 슬라이스 수 × 노드 수 = 총 슬라이스
-- 예: dc2.large 2개 노드 = 4 슬라이스 → 4의 배수 파일
-- 로딩 후 통계 업데이트
ANALYZE orders;
-- 또는 특정 컬럼만
ANALYZE orders (order_date, customer_id);// VACUUM 및 유지보수
-- 삭제된 행 정리 및 정렬
VACUUM FULL orders;
-- 삭제된 행만 정리 (빠름)
VACUUM DELETE ONLY orders;
-- 정렬만 수행
VACUUM SORT ONLY orders;
-- 자동 VACUUM 설정 확인
SELECT * FROM svv_table_info
WHERE "table" = 'orders';
-- 테이블 상태 확인
SELECT
"table",
tbl_rows,
unsorted, -- 정렬되지 않은 행 비율
stats_off, -- 통계 오래된 정도
pct_used -- 사용된 공간 비율
FROM svv_table_info
WHERE schema = 'public'
ORDER BY unsorted DESC;┌─────────────────────────────────────────────────────────────────────────────┐
│ Workload Management (WLM) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Queue Configuration ││
│ │ ││
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ││
│ │ │ ETL Queue │ │ BI Queue │ │ Ad-hoc Queue │ ││
│ │ │ │ │ │ │ │ ││
│ │ │ Memory: 40% │ │ Memory: 40% │ │ Memory: 20% │ ││
│ │ │ Concurrency: 5 │ │ Concurrency: 10│ │ Concurrency: 5 │ ││
│ │ │ Timeout: 3600s │ │ Timeout: 300s │ │ Timeout: 60s │ ││
│ │ │ │ │ │ │ │ ││
│ │ │ User Groups: │ │ User Groups: │ │ User Groups: │ ││
│ │ │ - etl_users │ │ - bi_users │ │ - analysts │ ││
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ││
│ │ ││
│ │ Query Routing Rules: ││
│ │ • User group membership ││
│ │ • Query group label ││
│ │ • Query priority ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘// WLM 쿼리 그룹 사용
-- 쿼리 그룹 설정
SET query_group TO 'etl';
-- ETL 쿼리 실행
INSERT INTO analytics.daily_summary
SELECT ...
FROM orders
WHERE order_date = CURRENT_DATE - 1;
-- 쿼리 그룹 초기화
RESET query_group;
-- 쿼리 우선순위 설정 (Automatic WLM)
SET query_priority TO 'HIGH';
-- 현재 실행 중인 쿼리 확인
SELECT
query,
pid,
user_name,
query_group,
slot_count,
DATEDIFF(second, starttime, GETDATE()) as runtime_seconds,
SUBSTRING(querytxt, 1, 50) as query_text
FROM stv_recents
WHERE status = 'Running'
ORDER BY runtime_seconds DESC;
-- 장시간 실행 쿼리 취소
CANCEL <pid>;┌─────────────────────────────────────────────────────────────────────────────┐
│ Redshift Data Sharing │
│ │
│ Producer Cluster (Account A) │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Database: analytics_db ││
│ │ ├── Schema: sales ││
│ │ │ ├── Table: orders ││
│ │ │ ├── Table: customers ││
│ │ │ └── View: daily_summary ││
│ │ │ ││
│ │ │ Datashare: sales_share ││
│ │ │ └── Objects: sales.orders, sales.customers, sales.daily_summary ││
│ │ └───────────────────────────────────────────────────────────────────────││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ │ Grant to Consumer │
│ ▼ │
│ Consumer Cluster (Account B) │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Database from Datashare: sales_db ││
│ │ ├── Schema: sales ││
│ │ │ ├── Table: orders (read-only) ││
│ │ │ ├── Table: customers (read-only) ││
│ │ │ └── View: daily_summary (read-only) ││
│ │ │ ││
│ │ │ → 실시간 데이터 접근 (복사 없음) ││
│ │ │ → 스토리지 비용 없음 ││
│ │ └───────────────────────────────────────────────────────────────────────││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘// Producer 클러스터에서 Datashare 생성
-- 1. Datashare 생성
CREATE DATASHARE sales_share;
-- 2. 스키마 추가
ALTER DATASHARE sales_share ADD SCHEMA sales;
-- 3. 테이블 추가
ALTER DATASHARE sales_share ADD TABLE sales.orders;
ALTER DATASHARE sales_share ADD TABLE sales.customers;
-- 4. 뷰 추가
ALTER DATASHARE sales_share ADD TABLE sales.daily_summary;
-- 5. 모든 테이블 추가 (스키마 내)
ALTER DATASHARE sales_share ADD ALL TABLES IN SCHEMA sales;
-- 6. Consumer에게 권한 부여
-- 같은 계정 내 다른 클러스터
GRANT USAGE ON DATASHARE sales_share TO NAMESPACE 'consumer-namespace-id';
-- 다른 AWS 계정
GRANT USAGE ON DATASHARE sales_share TO ACCOUNT '123456789012';
-- Datashare 상태 확인
SELECT * FROM svv_datashares;
SELECT * FROM svv_datashare_objects;// Consumer 클러스터에서 Datashare 사용
-- 1. 사용 가능한 Datashare 확인
SELECT * FROM svv_datashares WHERE share_type = 'INBOUND';
-- 2. Datashare에서 데이터베이스 생성
CREATE DATABASE sales_db FROM DATASHARE sales_share
OF NAMESPACE 'producer-namespace-id';
-- 또는 다른 계정에서
CREATE DATABASE sales_db FROM DATASHARE sales_share
OF ACCOUNT '987654321098';
-- 3. 데이터 쿼리 (읽기 전용)
SELECT * FROM sales_db.sales.orders
WHERE order_date >= '2024-01-01';
-- 4. 로컬 테이블과 조인
SELECT
o.order_id,
o.amount,
l.region_name
FROM sales_db.sales.orders o
JOIN local_db.public.regions l ON o.region_id = l.region_id;
-- 5. 권한 관리
GRANT USAGE ON DATABASE sales_db TO analyst_role;
GRANT SELECT ON ALL TABLES IN SCHEMA sales_db.sales TO analyst_role;// Row-Level Security (RLS)
-- 1. RLS 정책 생성
CREATE RLS POLICY region_policy
WITH (region VARCHAR(50))
USING (region = current_setting('app.user_region'));
-- 2. 테이블에 정책 적용
ALTER TABLE orders ATTACH RLS POLICY region_policy;
-- 3. RLS 활성화
ALTER TABLE orders ROW LEVEL SECURITY ON;
-- 4. 사용자별 리전 설정
-- 세션 시작 시
SET app.user_region = 'APAC';
-- 쿼리 실행 (자동으로 APAC 데이터만 반환)
SELECT * FROM orders; -- WHERE region = 'APAC' 자동 적용
-- 5. RLS 정책 확인
SELECT * FROM svv_rls_policy;
SELECT * FROM svv_rls_attached_policy;// Column-Level Security
-- 1. 역할 생성
CREATE ROLE analyst_role;
CREATE ROLE admin_role;
-- 2. 컬럼별 권한 부여
-- analyst_role: PII 컬럼 제외
GRANT SELECT (order_id, product_id, amount, order_date)
ON orders TO analyst_role;
-- admin_role: 모든 컬럼
GRANT SELECT ON orders TO admin_role;
-- 3. 동적 데이터 마스킹 (DDM)
CREATE MASKING POLICY email_mask
WITH (email VARCHAR(255))
USING (
CASE
WHEN current_user_has_role('admin_role') THEN email
ELSE CONCAT(LEFT(email, 2), '***@***.com')
END
);
-- 4. 마스킹 정책 적용
ALTER TABLE customers
ATTACH MASKING POLICY email_mask ON email;
-- 5. 결과 확인
-- admin_role: john.doe@example.com
-- analyst_role: jo***@***.com-- 사용자 활동 로그 조회
SELECT
userid,
query,
database,
querytxt,
starttime,
endtime,
DATEDIFF(second, starttime, endtime) as duration_sec
FROM stl_query
WHERE userid > 1 -- 시스템 사용자 제외
AND starttime >= DATEADD(day, -7, GETDATE())
ORDER BY starttime DESC
LIMIT 100;
-- 연결 로그
SELECT
event,
recordtime,
username,
dbname,
remotehost,
remoteport
FROM stl_connection_log
WHERE recordtime >= DATEADD(day, -1, GETDATE())
ORDER BY recordtime DESC;
-- 권한 변경 로그
SELECT
userid,
query,
querytxt,
starttime
FROM stl_ddltext
WHERE querytxt ILIKE '%GRANT%' OR querytxt ILIKE '%REVOKE%'
ORDER BY starttime DESC;
-- S3로 감사 로그 내보내기 (클러스터 설정)
-- AWS Console에서 Audit Logging 활성화
-- - User activity log
-- - User log
-- - Connection logDISTKEY 선택
조인 키 = DISTKEY → 네트워크 전송 없이 로컬 조인
SORTKEY 선택
WHERE 절 범위 조건 컬럼 → Zone Map 블록 스킵
RA3 vs DC2
RA3 = 대용량, 컴퓨팅/스토리지 분리 | DC2 = 소규모, SSD
Spectrum 비용
스캔 데이터량 기반 → Parquet + 파티셔닝 필수
Serverless vs Provisioned
Serverless = 가변 워크로드 | Provisioned = 예측 가능
Data Sharing
복사 없이 실시간 공유, 읽기 전용, 스토리지 비용 없음
| 기준 | Redshift | Athena |
|---|---|---|
| 쿼리 패턴 | 복잡한 조인, 반복 쿼리 | Ad-hoc, 탐색적 분석 |
| 데이터 크기 | TB~PB (자주 접근) | GB~TB (간헐적 접근) |
| 동시성 | 높은 동시성 지원 | 제한적 (계정당 한도) |
| 비용 모델 | 노드/RPU 시간 | 스캔 데이터량 |
| 관리 | 클러스터 관리 필요 | 서버리스 |
| 권장 사용 | BI 대시보드, 정기 리포트 | 로그 분석, 데이터 탐색 |
┌─────────────────────────────────────────────────────────────────────────────┐
│ Lakehouse Architecture with Redshift │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ S3 Data Lake ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Raw │ │ Curated │ │ Analytics │ ││
│ │ │ (Bronze) │ │ (Silver) │ │ (Gold) │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Athena │ │ Redshift │ │ Redshift │ │
│ │ (Ad-hoc) │ │ (Hot Data) │ │ Spectrum │ │
│ │ │ │ │ │ (Cold Data) │ │
│ │ • 탐색적 분석 │ │ • 자주 쿼리 │ │ • S3 직접 쿼리 │ │
│ │ • 로그 분석 │ │ • BI 대시보드 │ │ • 히스토리 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ QuickSight ││
│ │ (Visualization) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘Redshift ML을 사용하면 SQL만으로 머신러닝 모델을 생성하고 예측할 수 있습니다. SageMaker Autopilot과 통합되어 자동으로 최적의 모델을 선택합니다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Redshift ML Architecture │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Redshift Cluster ││
│ │ ││
│ │ CREATE MODEL customer_churn_model ││
│ │ FROM training_data ││
│ │ TARGET churn ││
│ │ FUNCTION predict_churn ││
│ │ IAM_ROLE 'arn:aws:iam::xxx:role/RedshiftML' ││
│ │ SETTINGS (S3_BUCKET 'ml-bucket') ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ SageMaker Autopilot ││
│ │ ││
│ │ 1. 데이터 분석 및 전처리 ││
│ │ 2. 여러 알고리즘 자동 시도 ││
│ │ • XGBoost ││
│ │ • Linear Learner ││
│ │ • Deep Learning ││
│ │ 3. 하이퍼파라미터 튜닝 ││
│ │ 4. 최적 모델 선택 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Redshift에서 예측 ││
│ │ ││
│ │ SELECT customer_id, predict_churn(features...) ││
│ │ FROM customers ││
│ │ WHERE predict_churn(features...) > 0.7 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘// 고객 이탈 예측 모델
-- 1. 학습 데이터 준비
CREATE TABLE training_data AS
SELECT
customer_id,
tenure_months,
monthly_charges,
total_charges,
contract_type,
payment_method,
num_support_tickets,
CASE WHEN churned = 'Yes' THEN 1 ELSE 0 END as churn
FROM customer_history
WHERE data_date < '2024-01-01';
-- 2. ML 모델 생성 (자동 알고리즘 선택)
CREATE MODEL customer_churn_model
FROM training_data
TARGET churn
FUNCTION predict_churn
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftMLRole'
AUTO ON
SETTINGS (
S3_BUCKET 'redshift-ml-bucket',
MAX_RUNTIME 3600
);
-- 3. 모델 상태 확인
SHOW MODEL customer_churn_model;
-- 4. 모델 정확도 확인
SELECT
accuracy,
precision,
recall,
f1_score
FROM stv_ml_model_info
WHERE model_name = 'customer_churn_model';// 예측 실행
-- 5. 이탈 가능성 높은 고객 예측
SELECT
c.customer_id,
c.customer_name,
c.email,
predict_churn(
c.tenure_months,
c.monthly_charges,
c.total_charges,
c.contract_type,
c.payment_method,
c.num_support_tickets
) as churn_probability
FROM customers c
WHERE predict_churn(
c.tenure_months,
c.monthly_charges,
c.total_charges,
c.contract_type,
c.payment_method,
c.num_support_tickets
) > 0.7
ORDER BY churn_probability DESC;
-- 6. 배치 예측 결과 저장
CREATE TABLE churn_predictions AS
SELECT
customer_id,
predict_churn(...) as churn_probability,
CURRENT_DATE as prediction_date
FROM customers;
-- 7. 모델 재학습 (새 데이터로)
CREATE MODEL customer_churn_model_v2
FROM (
SELECT * FROM training_data
UNION ALL
SELECT * FROM new_training_data
)
TARGET churn
FUNCTION predict_churn_v2
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftMLRole';// 고급 윈도우 함수
-- 1. 누적 합계 및 이동 평균
SELECT
order_date,
daily_revenue,
SUM(daily_revenue) OVER (
ORDER BY order_date
ROWS UNBOUNDED PRECEDING
) as cumulative_revenue,
AVG(daily_revenue) OVER (
ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as moving_avg_7d,
daily_revenue - LAG(daily_revenue, 1) OVER (ORDER BY order_date) as daily_change,
ROUND(
(daily_revenue - LAG(daily_revenue, 1) OVER (ORDER BY order_date))
/ LAG(daily_revenue, 1) OVER (ORDER BY order_date) * 100, 2
) as pct_change
FROM daily_sales;
-- 2. 순위 및 백분위
SELECT
product_id,
product_name,
total_sales,
ROW_NUMBER() OVER (ORDER BY total_sales DESC) as rank,
RANK() OVER (ORDER BY total_sales DESC) as rank_with_ties,
DENSE_RANK() OVER (ORDER BY total_sales DESC) as dense_rank,
NTILE(4) OVER (ORDER BY total_sales DESC) as quartile,
PERCENT_RANK() OVER (ORDER BY total_sales) as percentile
FROM product_sales;
-- 3. 카테고리별 순위
SELECT
category,
product_name,
total_sales,
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) as category_rank
FROM product_sales
QUALIFY category_rank <= 5; -- 카테고리별 상위 5개// 코호트 분석
-- 4. 코호트 분석 (고객 유지율)
WITH cohort_data AS (
SELECT
customer_id,
DATE_TRUNC('month', first_order_date) as cohort_month,
DATE_TRUNC('month', order_date) as order_month
FROM orders
),
cohort_size AS (
SELECT
cohort_month,
COUNT(DISTINCT customer_id) as cohort_customers
FROM cohort_data
GROUP BY cohort_month
),
retention AS (
SELECT
cohort_month,
order_month,
DATEDIFF(month, cohort_month, order_month) as month_number,
COUNT(DISTINCT customer_id) as retained_customers
FROM cohort_data
GROUP BY cohort_month, order_month
)
SELECT
r.cohort_month,
r.month_number,
r.retained_customers,
cs.cohort_customers,
ROUND(r.retained_customers::FLOAT / cs.cohort_customers * 100, 2) as retention_rate
FROM retention r
JOIN cohort_size cs ON r.cohort_month = cs.cohort_month
ORDER BY r.cohort_month, r.month_number;
-- 5. RFM 분석
WITH rfm AS (
SELECT
customer_id,
DATEDIFF(day, MAX(order_date), CURRENT_DATE) as recency,
COUNT(*) as frequency,
SUM(amount) as monetary
FROM orders
WHERE order_date >= DATEADD(year, -1, CURRENT_DATE)
GROUP BY customer_id
),
rfm_scores AS (
SELECT
customer_id,
recency,
frequency,
monetary,
NTILE(5) OVER (ORDER BY recency DESC) as r_score,
NTILE(5) OVER (ORDER BY frequency) as f_score,
NTILE(5) OVER (ORDER BY monetary) as m_score
FROM rfm
)
SELECT
customer_id,
r_score,
f_score,
m_score,
CONCAT(r_score, f_score, m_score) as rfm_segment,
CASE
WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'Champions'
WHEN r_score >= 3 AND f_score >= 3 THEN 'Loyal Customers'
WHEN r_score >= 4 AND f_score <= 2 THEN 'New Customers'
WHEN r_score <= 2 AND f_score >= 3 THEN 'At Risk'
WHEN r_score <= 2 AND f_score <= 2 THEN 'Lost'
ELSE 'Others'
END as customer_segment
FROM rfm_scores;-- Materialized View 생성
CREATE MATERIALIZED VIEW daily_sales_summary
AUTO REFRESH YES
AS
SELECT
DATE_TRUNC('day', order_date) as sale_date,
product_category,
COUNT(*) as order_count,
SUM(quantity) as total_quantity,
SUM(amount) as total_revenue,
AVG(amount) as avg_order_value,
COUNT(DISTINCT customer_id) as unique_customers
FROM orders o
JOIN products p ON o.product_id = p.product_id
GROUP BY DATE_TRUNC('day', order_date), product_category;
-- 새로고침 상태 확인
SELECT
mv_name,
state,
autorefresh,
last_refresh_time
FROM stv_mv_info
WHERE mv_name = 'daily_sales_summary';
-- 수동 새로고침
REFRESH MATERIALIZED VIEW daily_sales_summary;
-- Materialized View 쿼리 (빠른 응답)
SELECT * FROM daily_sales_summary
WHERE sale_date >= DATEADD(day, -30, CURRENT_DATE)
ORDER BY sale_date DESC;┌─────────────────────────────────────────────────────────────────────────────┐
│ Redshift Node Types │
│ │
│ RA3 Nodes (권장) │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ ra3.xlplus │ 4 vCPU │ 32 GB │ 32 TB Managed Storage ││
│ │ ra3.4xlarge │ 12 vCPU │ 96 GB │ 128 TB Managed Storage ││
│ │ ra3.16xlarge │ 48 vCPU │ 384 GB │ 128 TB Managed Storage ││
│ │ ││
│ │ 특징: ││
│ │ • 컴퓨팅/스토리지 독립 확장 ││
│ │ • Managed Storage (S3 기반) ││
│ │ • 자동 데이터 티어링 ││
│ │ • Cross-AZ 데이터 공유 ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │
│ DC2 Nodes (SSD) │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ dc2.large │ 2 vCPU │ 15 GB │ 160 GB SSD ││
│ │ dc2.8xlarge │ 32 vCPU │ 244 GB │ 2.56 TB SSD ││
│ │ ││
│ │ 특징: ││
│ │ • 로컬 SSD 스토리지 ││
│ │ • 소규모 데이터셋에 적합 ││
│ │ • 낮은 비용 (소규모) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │
│ 선택 가이드: │
│ • 데이터 > 1TB: RA3 권장 │
│ • 데이터 < 1TB, 예산 제한: DC2 │
│ • 스토리지 독립 확장 필요: RA3 │
│ • 데이터 공유 필요: RA3 필수 │
└─────────────────────────────────────────────────────────────────────────────┘| 노드 타입 | vCPU | 메모리 | 스토리지 | 시간당 비용 |
|---|---|---|---|---|
| ra3.xlplus | 4 | 32 GB | 32 TB | $1.086 |
| ra3.4xlarge | 12 | 96 GB | 128 TB | $3.26 |
| ra3.16xlarge | 48 | 384 GB | 128 TB | $13.04 |
| dc2.large | 2 | 15 GB | 160 GB | $0.25 |
| dc2.8xlarge | 32 | 244 GB | 2.56 TB | $4.80 |
// Terraform - 클러스터 구성
resource "aws_redshift_cluster" "main" {
cluster_identifier = "analytics-cluster"
database_name = "analytics"
master_username = "admin"
master_password = var.master_password
node_type = "ra3.4xlarge"
number_of_nodes = 3
# Elastic Resize 활성화
elastic_ip = null
# 암호화
encrypted = true
kms_key_id = aws_kms_key.redshift.arn
# 네트워크
cluster_subnet_group_name = aws_redshift_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.redshift.id]
publicly_accessible = false
# 유지보수
preferred_maintenance_window = "sun:05:00-sun:06:00"
automated_snapshot_retention_period = 7
# 파라미터 그룹
cluster_parameter_group_name = aws_redshift_parameter_group.main.name
# IAM 역할
iam_roles = [
aws_iam_role.redshift_spectrum.arn,
aws_iam_role.redshift_ml.arn
]
# 로깅
logging {
enable = true
bucket_name = aws_s3_bucket.redshift_logs.id
s3_key_prefix = "redshift-logs/"
}
tags = {
Environment = "production"
}
}
# 파라미터 그룹
resource "aws_redshift_parameter_group" "main" {
name = "analytics-params"
family = "redshift-1.0"
parameter {
name = "enable_user_activity_logging"
value = "true"
}
parameter {
name = "require_ssl"
value = "true"
}
parameter {
name = "max_concurrency_scaling_clusters"
value = "5"
}
parameter {
name = "wlm_json_configuration"
value = jsonencode([
{
name = "etl"
memory_percent_to_use = 40
concurrency = 5
user_group = ["etl_users"]
},
{
name = "bi"
memory_percent_to_use = 40
concurrency = 15
user_group = ["bi_users"]
},
{
name = "default"
memory_percent_to_use = 20
concurrency = 5
}
])
}
}// 클러스터 관리 명령
-- Elastic Resize (노드 수 변경)
-- AWS CLI 또는 콘솔에서 실행
-- 몇 분 내 완료, 다운타임 최소화
-- Concurrency Scaling 상태 확인
SELECT
service_class,
num_queued_queries,
num_executing_queries,
query_working_mem
FROM stv_wlm_service_class_state;
-- 동시성 스케일링 사용량 확인
SELECT
DATE_TRUNC('hour', start_time) as hour,
SUM(total_exec_time) / 1000000.0 as total_seconds,
COUNT(*) as query_count
FROM svl_query_metrics_summary
WHERE is_concurrency_scaling = 't'
GROUP BY DATE_TRUNC('hour', start_time)
ORDER BY hour DESC;
-- 클러스터 일시 중지/재개 (비용 절감)
-- AWS CLI
-- aws redshift pause-cluster --cluster-identifier analytics-cluster
-- aws redshift resume-cluster --cluster-identifier analytics-cluster
-- 스냅샷 생성
CREATE SNAPSHOT analytics_backup_20240115
FROM analytics_cluster;
-- 스냅샷에서 복원
RESTORE FROM SNAPSHOT analytics_backup_20240115
TO analytics_cluster_restored;┌─────────────────────────────────────────────────────────────────────────────┐
│ Redshift Network Architecture │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ VPC (10.0.0.0/16) ││
│ │ ││
│ │ ┌─────────────────────────────────────────────────────────────────┐ ││
│ │ │ Private Subnet (10.0.1.0/24) │ ││
│ │ │ │ ││
│ │ │ ┌─────────────────────────────────────────────────────────┐ │ ││
│ │ │ │ Redshift Cluster │ │ ││
│ │ │ │ • Leader Node │ │ ││
│ │ │ │ • Compute Nodes (3x ra3.4xlarge) │ │ ││
│ │ │ │ • Port: 5439 │ │ ││
│ │ │ └─────────────────────────────────────────────────────────┘ │ ││
│ │ │ │ ││
│ │ │ Security Group: │ ││
│ │ │ • Inbound: 5439 from Application SG │ ││
│ │ │ • Inbound: 5439 from BI Tools SG │ ││
│ │ │ • Outbound: 443 to S3 (Spectrum) │ ││
│ │ └─────────────────────────────────────────────────────────────────┘ ││
│ │ ││
│ │ ┌─────────────────────────────────────────────────────────────────┐ ││
│ │ │ VPC Endpoints │ ││
│ │ │ • S3 Gateway Endpoint (Spectrum 쿼리) │ ││
│ │ │ • Glue Interface Endpoint (Data Catalog) │ ││
│ │ │ • SageMaker Interface Endpoint (Redshift ML) │ ││
│ │ └─────────────────────────────────────────────────────────────────┘ ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘// Terraform - 네트워크 구성
# Redshift 서브넷 그룹
resource "aws_redshift_subnet_group" "main" {
name = "redshift-subnet-group"
subnet_ids = var.private_subnet_ids
tags = {
Name = "Redshift Subnet Group"
}
}
# 보안 그룹
resource "aws_security_group" "redshift" {
name = "redshift-sg"
description = "Security group for Redshift cluster"
vpc_id = var.vpc_id
# 애플리케이션에서 접근
ingress {
from_port = 5439
to_port = 5439
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
# BI 도구에서 접근
ingress {
from_port = 5439
to_port = 5439
protocol = "tcp"
security_groups = [var.bi_security_group_id]
}
# S3 (Spectrum) 접근
egress {
from_port = 443
to_port = 443
protocol = "tcp"
prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id]
}
tags = {
Name = "Redshift Security Group"
}
}
# S3 Gateway Endpoint
resource "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
route_table_ids = var.private_route_table_ids
}