kt-event-marketing/design/backend/database/participation-service-schema.psql
jhbkjh 3075a5d49f 물리아키텍처 설계 완료
 주요 기능
- Azure 기반 물리아키텍처 설계 (개발환경/운영환경)
- 7개 마이크로서비스 물리 구조 설계
- 네트워크 아키텍처 다이어그램 작성 (Mermaid)
- 환경별 비교 분석 및 마스터 인덱스 문서

📁 생성 파일
- design/backend/physical/physical-architecture.md (마스터)
- design/backend/physical/physical-architecture-dev.md (개발환경)
- design/backend/physical/physical-architecture-prod.md (운영환경)
- design/backend/physical/*.mmd (4개 Mermaid 다이어그램)

🎯 핵심 성과
- 비용 최적화: 개발환경 월 $143, 운영환경 월 $2,860
- 확장성: 개발환경 100명 → 운영환경 10,000명 (100배)
- 가용성: 개발환경 95% → 운영환경 99.9%
- 보안: 다층 보안 아키텍처 (L1~L4)

🛠️ 기술 스택
- Azure Kubernetes Service (AKS)
- Azure Database for PostgreSQL Flexible
- Azure Cache for Redis Premium
- Azure Service Bus Premium
- Application Gateway + WAF

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 15:13:01 +09:00

383 lines
12 KiB
Plaintext

-- ============================================================
-- Participation Service Database Schema
-- ============================================================
-- Description: 이벤트 참여자 관리 및 당첨자 추첨 시스템
-- Database: PostgreSQL 15+
-- Author: Backend Developer (최수연 "아키텍처")
-- Created: 2025-10-29
-- Version: v1.0
-- ============================================================
-- 데이터베이스 생성 (필요시)
-- CREATE DATABASE participation_db
-- WITH ENCODING 'UTF8'
-- LC_COLLATE = 'ko_KR.UTF-8'
-- LC_CTYPE = 'ko_KR.UTF-8'
-- TEMPLATE = template0;
-- 스키마 설정
\c participation_db;
SET client_encoding = 'UTF8';
SET timezone = 'Asia/Seoul';
-- ============================================================
-- 1. 테이블 생성
-- ============================================================
-- 1.1 참여자 테이블
CREATE TABLE IF NOT EXISTS participants (
-- 기본 키
id BIGSERIAL PRIMARY KEY,
-- 비즈니스 키
participant_id VARCHAR(50) NOT NULL,
event_id VARCHAR(50) NOT NULL,
-- 참여자 정보
name VARCHAR(100) NOT NULL,
phone_number VARCHAR(20) NOT NULL,
email VARCHAR(100),
-- 참여 정보
channel VARCHAR(50) NOT NULL,
store_visited BOOLEAN NOT NULL DEFAULT false,
bonus_entries INTEGER NOT NULL DEFAULT 1,
-- 동의 정보
agree_marketing BOOLEAN NOT NULL DEFAULT false,
agree_privacy BOOLEAN NOT NULL DEFAULT true,
-- 당첨 정보
is_winner BOOLEAN NOT NULL DEFAULT false,
winner_rank INTEGER,
won_at TIMESTAMP,
-- 감사 정보
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 1.2 추첨 이력 테이블
CREATE TABLE IF NOT EXISTS draw_logs (
-- 기본 키
id BIGSERIAL PRIMARY KEY,
-- 이벤트 정보
event_id VARCHAR(50) NOT NULL,
-- 추첨 정보
total_participants INTEGER NOT NULL,
winner_count INTEGER NOT NULL,
apply_store_visit_bonus BOOLEAN NOT NULL DEFAULT false,
algorithm VARCHAR(50) NOT NULL DEFAULT 'RANDOM',
-- 추첨 실행 정보
drawn_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
drawn_by VARCHAR(100) NOT NULL DEFAULT 'SYSTEM',
-- 감사 정보
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================
-- 2. 제약 조건 생성
-- ============================================================
-- 2.1 participants 테이블 제약 조건
-- Unique Constraints
ALTER TABLE participants
ADD CONSTRAINT uk_participant_id UNIQUE (participant_id),
ADD CONSTRAINT uk_event_phone UNIQUE (event_id, phone_number);
-- Check Constraints
ALTER TABLE participants
ADD CONSTRAINT chk_bonus_entries CHECK (bonus_entries >= 1 AND bonus_entries <= 3),
ADD CONSTRAINT chk_channel CHECK (channel IN ('WEB', 'MOBILE', 'INSTORE')),
ADD CONSTRAINT chk_winner_rank CHECK (winner_rank IS NULL OR winner_rank > 0);
-- 2.2 draw_logs 테이블 제약 조건
-- Unique Constraints
ALTER TABLE draw_logs
ADD CONSTRAINT uk_draw_event UNIQUE (event_id);
-- Check Constraints
ALTER TABLE draw_logs
ADD CONSTRAINT chk_winner_count CHECK (winner_count > 0),
ADD CONSTRAINT chk_total_participants CHECK (total_participants >= winner_count),
ADD CONSTRAINT chk_algorithm CHECK (algorithm IN ('RANDOM', 'WEIGHTED'));
-- ============================================================
-- 3. 인덱스 생성
-- ============================================================
-- 3.1 participants 테이블 인덱스
-- 이벤트별 참여자 조회 (최신순)
CREATE INDEX IF NOT EXISTS idx_participants_event_created
ON participants(event_id, created_at DESC);
-- 이벤트별 당첨자 조회
CREATE INDEX IF NOT EXISTS idx_participants_event_winner
ON participants(event_id, is_winner, winner_rank);
-- 매장 방문자 필터링
CREATE INDEX IF NOT EXISTS idx_participants_event_store
ON participants(event_id, store_visited);
-- 3.2 draw_logs 테이블 인덱스
-- 이벤트별 추첨 이력 조회
CREATE INDEX IF NOT EXISTS idx_draw_logs_event
ON draw_logs(event_id);
-- 추첨 일시별 조회
CREATE INDEX IF NOT EXISTS idx_draw_logs_drawn_at
ON draw_logs(drawn_at DESC);
-- ============================================================
-- 4. 트리거 함수 생성
-- ============================================================
-- 4.1 updated_at 자동 갱신 트리거 함수
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 4.2 participants 테이블에 트리거 적용
DROP TRIGGER IF EXISTS trg_participants_updated_at ON participants;
CREATE TRIGGER trg_participants_updated_at
BEFORE UPDATE ON participants
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 4.3 draw_logs 테이블에 트리거 적용
DROP TRIGGER IF EXISTS trg_draw_logs_updated_at ON draw_logs;
CREATE TRIGGER trg_draw_logs_updated_at
BEFORE UPDATE ON draw_logs
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- ============================================================
-- 5. 샘플 데이터 삽입 (테스트용)
-- ============================================================
-- 5.1 샘플 참여자 데이터
INSERT INTO participants (
participant_id, event_id, name, phone_number, email,
channel, store_visited, bonus_entries,
agree_marketing, agree_privacy
) VALUES
('EVT001-20251029-001', 'EVT001', '홍길동', '010-1234-5678', 'hong@example.com', 'WEB', false, 1, true, true),
('EVT001-20251029-002', 'EVT001', '김철수', '010-2345-6789', 'kim@example.com', 'MOBILE', false, 1, false, true),
('EVT001-20251029-003', 'EVT001', '이영희', '010-3456-7890', 'lee@example.com', 'INSTORE', true, 3, true, true),
('EVT001-20251029-004', 'EVT001', '박민수', '010-4567-8901', 'park@example.com', 'WEB', false, 1, true, true),
('EVT001-20251029-005', 'EVT001', '정수연', '010-5678-9012', 'jung@example.com', 'INSTORE', true, 3, true, true)
ON CONFLICT (participant_id) DO NOTHING;
-- 5.2 샘플 추첨 이력 데이터
INSERT INTO draw_logs (
event_id, total_participants, winner_count,
apply_store_visit_bonus, algorithm, drawn_by
) VALUES
('EVT001', 5, 2, true, 'WEIGHTED', 'admin@example.com')
ON CONFLICT (event_id) DO NOTHING;
-- 5.3 당첨자 업데이트 (샘플)
UPDATE participants
SET is_winner = true, winner_rank = 1, won_at = CURRENT_TIMESTAMP
WHERE participant_id = 'EVT001-20251029-003';
UPDATE participants
SET is_winner = true, winner_rank = 2, won_at = CURRENT_TIMESTAMP
WHERE participant_id = 'EVT001-20251029-005';
-- ============================================================
-- 6. 데이터 정합성 검증 쿼리
-- ============================================================
-- 6.1 중복 참여자 확인
SELECT
event_id,
phone_number,
COUNT(*) as duplicate_count
FROM participants
GROUP BY event_id, phone_number
HAVING COUNT(*) > 1;
-- 6.2 당첨자 순위 중복 확인
SELECT
event_id,
winner_rank,
COUNT(*) as duplicate_rank_count
FROM participants
WHERE is_winner = true
GROUP BY event_id, winner_rank
HAVING COUNT(*) > 1;
-- 6.3 추첨 이력 정합성 확인
SELECT
d.event_id,
d.winner_count as expected_winners,
COUNT(p.id) as actual_winners,
CASE
WHEN d.winner_count = COUNT(p.id) THEN '정합성 OK'
ELSE '정합성 오류'
END as status
FROM draw_logs d
LEFT JOIN participants p ON d.event_id = p.event_id AND p.is_winner = true
GROUP BY d.event_id, d.winner_count;
-- ============================================================
-- 7. 유용한 조회 쿼리
-- ============================================================
-- 7.1 이벤트별 참여 현황 조회
SELECT
event_id,
COUNT(*) as total_participants,
SUM(CASE WHEN store_visited THEN 1 ELSE 0 END) as store_visited_count,
SUM(bonus_entries) as total_entries,
COUNT(CASE WHEN is_winner THEN 1 END) as winner_count,
ROUND(AVG(bonus_entries), 2) as avg_bonus_entries
FROM participants
GROUP BY event_id
ORDER BY event_id;
-- 7.2 채널별 참여 현황 조회
SELECT
event_id,
channel,
COUNT(*) as participant_count,
SUM(CASE WHEN is_winner THEN 1 ELSE 0 END) as winner_count,
ROUND(100.0 * SUM(CASE WHEN is_winner THEN 1 ELSE 0 END) / COUNT(*), 2) as win_rate_percent
FROM participants
GROUP BY event_id, channel
ORDER BY event_id, channel;
-- 7.3 당첨자 목록 조회
SELECT
p.event_id,
p.participant_id,
p.name,
p.phone_number,
p.winner_rank,
p.store_visited,
p.bonus_entries,
p.won_at,
d.algorithm
FROM participants p
LEFT JOIN draw_logs d ON p.event_id = d.event_id
WHERE p.is_winner = true
ORDER BY p.event_id, p.winner_rank;
-- 7.4 매장 방문 보너스 효과 분석
SELECT
event_id,
store_visited,
COUNT(*) as participant_count,
COUNT(CASE WHEN is_winner THEN 1 END) as winner_count,
ROUND(100.0 * COUNT(CASE WHEN is_winner THEN 1 END) / COUNT(*), 2) as win_rate_percent,
AVG(bonus_entries) as avg_bonus_entries
FROM participants
GROUP BY event_id, store_visited
ORDER BY event_id, store_visited DESC;
-- ============================================================
-- 8. 권한 설정 (필요시)
-- ============================================================
-- 애플리케이션 사용자 생성 및 권한 부여
-- CREATE USER participation_user WITH PASSWORD 'your_secure_password';
-- GRANT CONNECT ON DATABASE participation_db TO participation_user;
-- GRANT USAGE ON SCHEMA public TO participation_user;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO participation_user;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO participation_user;
-- ============================================================
-- 9. 성능 모니터링 쿼리
-- ============================================================
-- 9.1 테이블 크기 조회
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- 9.2 인덱스 사용률 조회
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE schemaname = 'public'
ORDER BY idx_scan DESC;
-- 9.3 느린 쿼리 분석 (pg_stat_statements 확장 필요)
-- SELECT
-- query,
-- calls,
-- total_exec_time,
-- mean_exec_time,
-- min_exec_time,
-- max_exec_time
-- FROM pg_stat_statements
-- WHERE query LIKE '%participants%' OR query LIKE '%draw_logs%'
-- ORDER BY mean_exec_time DESC
-- LIMIT 10;
-- ============================================================
-- 10. 마이그레이션 및 롤백
-- ============================================================
-- 롤백 스크립트 (필요시 실행)
/*
-- 트리거 삭제
DROP TRIGGER IF EXISTS trg_participants_updated_at ON participants;
DROP TRIGGER IF EXISTS trg_draw_logs_updated_at ON draw_logs;
-- 트리거 함수 삭제
DROP FUNCTION IF EXISTS update_updated_at_column();
-- 인덱스 삭제
DROP INDEX IF EXISTS idx_participants_event_created;
DROP INDEX IF EXISTS idx_participants_event_winner;
DROP INDEX IF EXISTS idx_participants_event_store;
DROP INDEX IF EXISTS idx_draw_logs_event;
DROP INDEX IF EXISTS idx_draw_logs_drawn_at;
-- 테이블 삭제
DROP TABLE IF EXISTS draw_logs CASCADE;
DROP TABLE IF EXISTS participants CASCADE;
-- 데이터베이스 삭제 (주의!)
-- DROP DATABASE IF EXISTS participation_db;
*/
-- ============================================================
-- 스키마 생성 완료
-- ============================================================
\echo '=========================================='
\echo 'Participation Service Schema Created Successfully!'
\echo '=========================================='
\echo ''
\echo 'Tables created:'
\echo ' - participants (참여자)'
\echo ' - draw_logs (추첨 이력)'
\echo ''
\echo 'Sample data inserted for testing'
\echo '=========================================='