mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 18:06:23 +00:00
✨ 주요 기능 - 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>
612 lines
17 KiB
Markdown
612 lines
17 KiB
Markdown
# Analytics Service 데이터베이스 설계서
|
|
|
|
## 데이터설계 요약
|
|
|
|
### 설계 개요
|
|
- **서비스명**: Analytics Service
|
|
- **데이터베이스**: PostgreSQL 16.x (시계열 최적화)
|
|
- **캐시 DB**: Redis 7.x (분석 결과 캐싱)
|
|
- **아키텍처 패턴**: Layered Architecture
|
|
- **데이터 특성**: 시계열 분석 데이터, 실시간 집계 데이터
|
|
|
|
### 테이블 구성
|
|
| 테이블명 | 설명 | Entity 매핑 | 특징 |
|
|
|---------|------|-------------|------|
|
|
| event_stats | 이벤트별 통계 | EventStats | 집계 데이터, userId 인덱스 |
|
|
| channel_stats | 채널별 성과 | ChannelStats | 외부 API 연동 데이터 |
|
|
| timeline_data | 시계열 분석 | TimelineData | 시간 순서 데이터, 시계열 인덱스 |
|
|
|
|
### Redis 캐시 구조
|
|
| 키 패턴 | 설명 | TTL |
|
|
|--------|------|-----|
|
|
| `analytics:dashboard:{eventId}` | 대시보드 데이터 | 1시간 |
|
|
| `analytics:channel:{eventId}:{channelName}` | 채널별 분석 | 1시간 |
|
|
| `analytics:roi:{eventId}` | ROI 분석 | 1시간 |
|
|
| `analytics:timeline:{eventId}:{granularity}` | 타임라인 데이터 | 1시간 |
|
|
| `analytics:user:{userId}` | 사용자 통합 분석 | 1시간 |
|
|
| `analytics:processed:{messageId}` | 멱등성 처리 (Set) | 24시간 |
|
|
|
|
### 데이터 접근 패턴
|
|
1. **이벤트별 조회**: eventId 기반 빠른 조회 (B-Tree 인덱스)
|
|
2. **사용자별 조회**: userId 기반 다중 이벤트 조회
|
|
3. **시계열 조회**: timestamp 범위 검색 (BRIN 인덱스)
|
|
4. **채널별 조회**: eventId + channelName 복합 인덱스
|
|
|
|
---
|
|
|
|
## 1. 테이블 상세 설계
|
|
|
|
### 1.1 event_stats (이벤트 통계)
|
|
|
|
#### 테이블 설명
|
|
- **목적**: 이벤트별 통계 집계 데이터 관리
|
|
- **데이터 특성**: 실시간 업데이트, Kafka Consumer를 통한 증분 업데이트
|
|
- **조회 패턴**: eventId 단건 조회, userId 기반 목록 조회
|
|
|
|
#### 컬럼 정의
|
|
| 컬럼명 | 데이터 타입 | Null | 기본값 | 설명 |
|
|
|--------|------------|------|-------|------|
|
|
| id | BIGSERIAL | NOT NULL | AUTO | 기본 키 |
|
|
| event_id | VARCHAR(36) | NOT NULL | - | 이벤트 ID (UUID) |
|
|
| event_title | VARCHAR(255) | NOT NULL | - | 이벤트 제목 |
|
|
| user_id | VARCHAR(36) | NOT NULL | - | 사용자 ID (UUID) |
|
|
| total_participants | INTEGER | NOT NULL | 0 | 총 참여자 수 |
|
|
| total_views | INTEGER | NOT NULL | 0 | 총 조회 수 |
|
|
| estimated_roi | DECIMAL(10,2) | NOT NULL | 0.00 | 예상 ROI (%) |
|
|
| target_roi | DECIMAL(10,2) | NOT NULL | 0.00 | 목표 ROI (%) |
|
|
| sales_growth_rate | DECIMAL(10,2) | NOT NULL | 0.00 | 매출 성장률 (%) |
|
|
| total_investment | DECIMAL(15,2) | NOT NULL | 0.00 | 총 투자 금액 (원) |
|
|
| expected_revenue | DECIMAL(15,2) | NOT NULL | 0.00 | 예상 수익 (원) |
|
|
| status | VARCHAR(20) | NOT NULL | 'ACTIVE' | 이벤트 상태 |
|
|
| created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시간 |
|
|
| updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시간 |
|
|
|
|
#### 인덱스
|
|
```sql
|
|
PRIMARY KEY (id)
|
|
UNIQUE INDEX uk_event_stats_event_id (event_id)
|
|
INDEX idx_event_stats_user_id (user_id)
|
|
INDEX idx_event_stats_status (status)
|
|
INDEX idx_event_stats_created_at (created_at DESC)
|
|
```
|
|
|
|
#### 제약 조건
|
|
```sql
|
|
CHECK (total_participants >= 0)
|
|
CHECK (total_views >= 0)
|
|
CHECK (estimated_roi >= 0)
|
|
CHECK (target_roi >= 0)
|
|
CHECK (total_investment >= 0)
|
|
CHECK (expected_revenue >= 0)
|
|
CHECK (status IN ('ACTIVE', 'ENDED', 'ARCHIVED'))
|
|
```
|
|
|
|
---
|
|
|
|
### 1.2 channel_stats (채널 통계)
|
|
|
|
#### 테이블 설명
|
|
- **목적**: 채널별 성과 데이터 관리
|
|
- **데이터 특성**: 외부 API 연동 데이터, Circuit Breaker 패턴 적용
|
|
- **조회 패턴**: eventId 기반 목록 조회, eventId + channelName 단건 조회
|
|
|
|
#### 컬럼 정의
|
|
| 컬럼명 | 데이터 타입 | Null | 기본값 | 설명 |
|
|
|--------|------------|------|-------|------|
|
|
| id | BIGSERIAL | NOT NULL | AUTO | 기본 키 |
|
|
| event_id | VARCHAR(36) | NOT NULL | - | 이벤트 ID (UUID) |
|
|
| channel_name | VARCHAR(50) | NOT NULL | - | 채널명 (WooriTV, GenieTV 등) |
|
|
| channel_type | VARCHAR(20) | NOT NULL | - | 채널 타입 (TV, SNS, VOICE) |
|
|
| impressions | INTEGER | NOT NULL | 0 | 노출 수 |
|
|
| views | INTEGER | NOT NULL | 0 | 조회 수 |
|
|
| clicks | INTEGER | NOT NULL | 0 | 클릭 수 |
|
|
| participants | INTEGER | NOT NULL | 0 | 참여자 수 |
|
|
| conversions | INTEGER | NOT NULL | 0 | 전환 수 |
|
|
| distribution_cost | DECIMAL(15,2) | NOT NULL | 0.00 | 배포 비용 (원) |
|
|
| likes | INTEGER | NOT NULL | 0 | 좋아요 수 (SNS) |
|
|
| comments | INTEGER | NOT NULL | 0 | 댓글 수 (SNS) |
|
|
| shares | INTEGER | NOT NULL | 0 | 공유 수 (SNS) |
|
|
| total_calls | INTEGER | NOT NULL | 0 | 총 통화 수 (VOICE) |
|
|
| completed_calls | INTEGER | NOT NULL | 0 | 완료 통화 수 (VOICE) |
|
|
| average_duration | INTEGER | NOT NULL | 0 | 평균 통화 시간 (초, VOICE) |
|
|
| created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시간 |
|
|
| updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시간 |
|
|
|
|
#### 인덱스
|
|
```sql
|
|
PRIMARY KEY (id)
|
|
UNIQUE INDEX uk_channel_stats_event_channel (event_id, channel_name)
|
|
INDEX idx_channel_stats_event_id (event_id)
|
|
INDEX idx_channel_stats_channel_type (channel_type)
|
|
INDEX idx_channel_stats_participants (participants DESC)
|
|
```
|
|
|
|
#### 제약 조건
|
|
```sql
|
|
CHECK (impressions >= 0)
|
|
CHECK (views >= 0)
|
|
CHECK (clicks >= 0)
|
|
CHECK (participants >= 0)
|
|
CHECK (conversions >= 0)
|
|
CHECK (distribution_cost >= 0)
|
|
CHECK (total_calls >= 0)
|
|
CHECK (completed_calls >= 0 AND completed_calls <= total_calls)
|
|
CHECK (average_duration >= 0)
|
|
CHECK (channel_type IN ('TV', 'SNS', 'VOICE'))
|
|
```
|
|
|
|
---
|
|
|
|
### 1.3 timeline_data (시계열 데이터)
|
|
|
|
#### 테이블 설명
|
|
- **목적**: 시간별 참여 추이 데이터 관리
|
|
- **데이터 특성**: 시계열 데이터, 누적 참여자 수 포함
|
|
- **조회 패턴**: eventId + timestamp 범위 조회 (시간 순서)
|
|
|
|
#### 컬럼 정의
|
|
| 컬럼명 | 데이터 타입 | Null | 기본값 | 설명 |
|
|
|--------|------------|------|-------|------|
|
|
| id | BIGSERIAL | NOT NULL | AUTO | 기본 키 |
|
|
| event_id | VARCHAR(36) | NOT NULL | - | 이벤트 ID (UUID) |
|
|
| timestamp | TIMESTAMP | NOT NULL | - | 기록 시간 |
|
|
| participants | INTEGER | NOT NULL | 0 | 해당 시간 참여자 수 |
|
|
| views | INTEGER | NOT NULL | 0 | 해당 시간 조회 수 |
|
|
| engagement | INTEGER | NOT NULL | 0 | 참여도 (상호작용 수) |
|
|
| conversions | INTEGER | NOT NULL | 0 | 해당 시간 전환 수 |
|
|
| cumulative_participants | INTEGER | NOT NULL | 0 | 누적 참여자 수 |
|
|
| created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시간 |
|
|
| updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시간 |
|
|
|
|
#### 인덱스
|
|
```sql
|
|
PRIMARY KEY (id)
|
|
INDEX idx_timeline_event_timestamp (event_id, timestamp ASC)
|
|
INDEX idx_timeline_timestamp (timestamp ASC)
|
|
```
|
|
|
|
**시계열 최적화**: BRIN 인덱스 사용 권장 (대량 시계열 데이터)
|
|
```sql
|
|
CREATE INDEX idx_timeline_brin_timestamp ON timeline_data USING BRIN (timestamp);
|
|
```
|
|
|
|
#### 제약 조건
|
|
```sql
|
|
CHECK (participants >= 0)
|
|
CHECK (views >= 0)
|
|
CHECK (engagement >= 0)
|
|
CHECK (conversions >= 0)
|
|
CHECK (cumulative_participants >= 0)
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Redis 캐시 설계
|
|
|
|
### 2.1 캐시 구조
|
|
|
|
#### 대시보드 캐시
|
|
```
|
|
Key: analytics:dashboard:{eventId}
|
|
Type: String (JSON)
|
|
TTL: 3600초 (1시간)
|
|
Value: {
|
|
"eventId": "uuid",
|
|
"eventTitle": "이벤트 제목",
|
|
"summary": { ... },
|
|
"channelPerformance": [ ... ],
|
|
"roi": { ... }
|
|
}
|
|
```
|
|
|
|
#### 채널 분석 캐시
|
|
```
|
|
Key: analytics:channel:{eventId}:{channelName}
|
|
Type: String (JSON)
|
|
TTL: 3600초
|
|
Value: {
|
|
"channelName": "WooriTV",
|
|
"metrics": { ... },
|
|
"performance": { ... }
|
|
}
|
|
```
|
|
|
|
#### ROI 분석 캐시
|
|
```
|
|
Key: analytics:roi:{eventId}
|
|
Type: String (JSON)
|
|
TTL: 3600초
|
|
Value: {
|
|
"currentRoi": 15.5,
|
|
"targetRoi": 20.0,
|
|
"investment": { ... }
|
|
}
|
|
```
|
|
|
|
#### 타임라인 캐시
|
|
```
|
|
Key: analytics:timeline:{eventId}:{granularity}
|
|
Type: String (JSON)
|
|
TTL: 3600초
|
|
Value: {
|
|
"dataPoints": [ ... ],
|
|
"trend": { ... }
|
|
}
|
|
```
|
|
|
|
#### 사용자 통합 분석 캐시
|
|
```
|
|
Key: analytics:user:{userId}
|
|
Type: String (JSON)
|
|
TTL: 3600초
|
|
Value: {
|
|
"totalEvents": 5,
|
|
"summary": { ... },
|
|
"events": [ ... ]
|
|
}
|
|
```
|
|
|
|
#### 멱등성 처리 캐시
|
|
```
|
|
Key: analytics:processed:{messageId}
|
|
Type: Set
|
|
TTL: 86400초 (24시간)
|
|
Value: "1" (존재 여부만 확인)
|
|
```
|
|
|
|
### 2.2 캐시 전략
|
|
|
|
#### Cache-Aside 패턴
|
|
1. **조회**: 캐시 먼저 확인 → 없으면 DB 조회 → 캐시 저장
|
|
2. **갱신**: DB 업데이트 → 캐시 무효화 (Kafka Consumer)
|
|
3. **만료**: TTL 만료 시 자동 삭제
|
|
|
|
#### 캐시 무효화 조건
|
|
- **Kafka 이벤트 수신 시**: 관련 캐시 삭제
|
|
- **배치 스케줄러**: 5분마다 전체 갱신
|
|
- **수동 갱신 요청**: `refresh=true` 파라미터
|
|
|
|
---
|
|
|
|
## 3. 데이터베이스 설정
|
|
|
|
### 3.1 PostgreSQL 설정
|
|
|
|
#### Connection Pool
|
|
```properties
|
|
spring.datasource.hikari.maximum-pool-size=20
|
|
spring.datasource.hikari.minimum-idle=5
|
|
spring.datasource.hikari.connection-timeout=30000
|
|
spring.datasource.hikari.idle-timeout=600000
|
|
spring.datasource.hikari.max-lifetime=1800000
|
|
```
|
|
|
|
#### 시계열 최적화
|
|
```sql
|
|
-- timeline_data 테이블 파티셔닝 (월별)
|
|
CREATE TABLE timeline_data (
|
|
...
|
|
) PARTITION BY RANGE (timestamp);
|
|
|
|
CREATE TABLE timeline_data_2025_01 PARTITION OF timeline_data
|
|
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
|
|
-- 자동 파티션 생성 (pg_cron 활용)
|
|
```
|
|
|
|
### 3.2 Redis 설정
|
|
|
|
```properties
|
|
spring.redis.host=${REDIS_HOST:localhost}
|
|
spring.redis.port=${REDIS_PORT:6379}
|
|
spring.redis.password=${REDIS_PASSWORD:}
|
|
spring.redis.database=${REDIS_DATABASE:0}
|
|
spring.redis.timeout=3000
|
|
spring.redis.lettuce.pool.max-active=20
|
|
spring.redis.lettuce.pool.max-idle=10
|
|
spring.redis.lettuce.pool.min-idle=5
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 데이터 접근 패턴
|
|
|
|
### 4.1 이벤트 대시보드 조회
|
|
```sql
|
|
-- Cache Miss 시
|
|
SELECT * FROM event_stats WHERE event_id = ?;
|
|
SELECT * FROM channel_stats WHERE event_id = ?;
|
|
```
|
|
|
|
### 4.2 사용자별 이벤트 조회
|
|
```sql
|
|
SELECT * FROM event_stats WHERE user_id = ? ORDER BY created_at DESC;
|
|
```
|
|
|
|
### 4.3 타임라인 조회
|
|
```sql
|
|
SELECT * FROM timeline_data
|
|
WHERE event_id = ?
|
|
AND timestamp BETWEEN ? AND ?
|
|
ORDER BY timestamp ASC;
|
|
```
|
|
|
|
### 4.4 채널별 성과 조회
|
|
```sql
|
|
SELECT * FROM channel_stats
|
|
WHERE event_id = ?
|
|
AND channel_name IN (?, ?, ...)
|
|
ORDER BY participants DESC;
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 데이터 동기화
|
|
|
|
### 5.1 Kafka Consumer를 통한 실시간 업데이트
|
|
|
|
#### EventCreatedEvent 처리
|
|
```java
|
|
@KafkaListener(topics = "event-created")
|
|
public void handleEventCreated(String message) {
|
|
// 1. event_stats 생성
|
|
EventStats stats = new EventStats(...);
|
|
eventStatsRepository.save(stats);
|
|
|
|
// 2. 캐시 무효화
|
|
redisTemplate.delete("analytics:dashboard:" + eventId);
|
|
}
|
|
```
|
|
|
|
#### ParticipantRegisteredEvent 처리
|
|
```java
|
|
@KafkaListener(topics = "participant-registered")
|
|
public void handleParticipantRegistered(String message) {
|
|
// 1. 멱등성 체크 (Redis Set)
|
|
if (redisTemplate.opsForSet().isMember("analytics:processed", messageId)) {
|
|
return;
|
|
}
|
|
|
|
// 2. event_stats 업데이트 (참여자 증가)
|
|
eventStats.incrementParticipants();
|
|
|
|
// 3. timeline_data 업데이트
|
|
updateTimelineData(eventId);
|
|
|
|
// 4. 캐시 무효화
|
|
redisTemplate.delete("analytics:*:" + eventId);
|
|
|
|
// 5. 멱등성 기록
|
|
redisTemplate.opsForSet().add("analytics:processed", messageId);
|
|
redisTemplate.expire("analytics:processed:" + messageId, 24, TimeUnit.HOURS);
|
|
}
|
|
```
|
|
|
|
#### DistributionCompletedEvent 처리
|
|
```java
|
|
@KafkaListener(topics = "distribution-completed")
|
|
public void handleDistributionCompleted(String message) {
|
|
// 1. channel_stats 생성 또는 업데이트
|
|
channelStatsRepository.save(channelStats);
|
|
|
|
// 2. 캐시 무효화
|
|
redisTemplate.delete("analytics:channel:" + eventId + ":" + channelName);
|
|
}
|
|
```
|
|
|
|
### 5.2 배치 스케줄러를 통한 주기적 갱신
|
|
|
|
```java
|
|
@Scheduled(cron = "0 */5 * * * *") // 5분마다
|
|
public void refreshAnalyticsDashboard() {
|
|
List<EventStats> activeEvents = eventStatsRepository.findByStatus("ACTIVE");
|
|
|
|
for (EventStats event : activeEvents) {
|
|
// 1. 캐시 갱신
|
|
AnalyticsDashboardResponse response = analyticsService.getDashboardData(
|
|
event.getEventId(), null, null, true
|
|
);
|
|
|
|
// 2. 외부 API 호출 (Circuit Breaker)
|
|
externalChannelService.updateChannelStatsFromExternalAPIs(
|
|
event.getEventId(),
|
|
channelStatsRepository.findByEventId(event.getEventId())
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 성능 최적화
|
|
|
|
### 6.1 인덱스 전략
|
|
1. **B-Tree 인덱스**: eventId, userId 등 정확한 매칭
|
|
2. **BRIN 인덱스**: timestamp 시계열 데이터
|
|
3. **복합 인덱스**: (event_id, channel_name) 등 자주 함께 조회
|
|
|
|
### 6.2 쿼리 최적화
|
|
- **N+1 문제 방지**: Fetch Join 사용 (필요 시)
|
|
- **Projection**: 필요한 컬럼만 조회
|
|
- **캐싱**: Redis를 통한 조회 성능 향상
|
|
|
|
### 6.3 파티셔닝
|
|
- **timeline_data**: 월별 파티셔닝으로 대용량 시계열 데이터 관리
|
|
- **자동 파티션 생성**: pg_cron을 통한 자동화
|
|
|
|
---
|
|
|
|
## 7. 데이터 보안
|
|
|
|
### 7.1 접근 제어
|
|
```sql
|
|
-- 읽기 전용 사용자
|
|
CREATE USER analytics_readonly WITH PASSWORD 'secure_password';
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_readonly;
|
|
|
|
-- 읽기/쓰기 사용자
|
|
CREATE USER analytics_readwrite WITH PASSWORD 'secure_password';
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO analytics_readwrite;
|
|
```
|
|
|
|
### 7.2 데이터 마스킹
|
|
- **개인 정보**: userId는 UUID로만 저장 (이름, 연락처 등 제외)
|
|
- **금액 정보**: 암호화 저장 권장 (필요 시)
|
|
|
|
---
|
|
|
|
## 8. 백업 및 복구
|
|
|
|
### 8.1 백업 전략
|
|
```bash
|
|
# 일일 백업 (pg_dump)
|
|
pg_dump -U postgres -F c -b -v -f /backup/analytics_$(date +%Y%m%d).dump analytics_db
|
|
|
|
# 주간 전체 백업 (pg_basebackup)
|
|
pg_basebackup -U postgres -D /backup/full -Ft -z -P
|
|
```
|
|
|
|
### 8.2 복구 전략
|
|
```bash
|
|
# 데이터베이스 복구
|
|
pg_restore -U postgres -d analytics_db -v /backup/analytics_20250129.dump
|
|
```
|
|
|
|
### 8.3 Redis 백업
|
|
```bash
|
|
# RDB 스냅샷 (매일 자정)
|
|
redis-cli --rdb /backup/redis_$(date +%Y%m%d).rdb
|
|
|
|
# AOF 로그 (실시간)
|
|
redis-cli CONFIG SET appendonly yes
|
|
```
|
|
|
|
---
|
|
|
|
## 9. 모니터링
|
|
|
|
### 9.1 데이터베이스 모니터링
|
|
```sql
|
|
-- 테이블 크기 확인
|
|
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;
|
|
|
|
-- 느린 쿼리 확인
|
|
SELECT
|
|
query,
|
|
calls,
|
|
total_time,
|
|
mean_time
|
|
FROM pg_stat_statements
|
|
ORDER BY mean_time DESC
|
|
LIMIT 10;
|
|
```
|
|
|
|
### 9.2 Redis 모니터링
|
|
```bash
|
|
# 메모리 사용량
|
|
redis-cli INFO memory
|
|
|
|
# 캐시 히트율
|
|
redis-cli INFO stats | grep keyspace
|
|
|
|
# 느린 명령어 확인
|
|
redis-cli SLOWLOG GET 10
|
|
```
|
|
|
|
---
|
|
|
|
## 10. 트러블슈팅
|
|
|
|
### 10.1 일반적인 문제 및 해결
|
|
|
|
#### 문제 1: 캐시 Miss 증가
|
|
- **원인**: TTL이 너무 짧거나, 잦은 캐시 무효화
|
|
- **해결**: TTL 조정, 캐시 무효화 로직 최적화
|
|
|
|
#### 문제 2: 시계열 쿼리 느림
|
|
- **원인**: BRIN 인덱스 미사용, 파티셔닝 미적용
|
|
- **해결**: BRIN 인덱스 생성, 월별 파티셔닝 적용
|
|
|
|
#### 문제 3: 외부 API 장애
|
|
- **원인**: Circuit Breaker 미동작, Timeout 설정 문제
|
|
- **해결**: Resilience4j 설정 확인, Timeout 조정
|
|
|
|
---
|
|
|
|
## 부록: Entity 매핑 확인
|
|
|
|
### EventStats 클래스 매핑
|
|
```java
|
|
@Entity
|
|
@Table(name = "event_stats")
|
|
public class EventStats extends BaseTimeEntity {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(name = "event_id", nullable = false, unique = true, length = 36)
|
|
private String eventId;
|
|
|
|
@Column(name = "event_title", nullable = false)
|
|
private String eventTitle;
|
|
|
|
@Column(name = "user_id", nullable = false, length = 36)
|
|
private String userId;
|
|
|
|
@Column(name = "total_participants", nullable = false)
|
|
private Integer totalParticipants = 0;
|
|
|
|
// ... 기타 필드
|
|
}
|
|
```
|
|
|
|
### ChannelStats 클래스 매핑
|
|
```java
|
|
@Entity
|
|
@Table(name = "channel_stats")
|
|
public class ChannelStats extends BaseTimeEntity {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(name = "event_id", nullable = false, length = 36)
|
|
private String eventId;
|
|
|
|
@Column(name = "channel_name", nullable = false, length = 50)
|
|
private String channelName;
|
|
|
|
// ... 기타 필드
|
|
}
|
|
```
|
|
|
|
### TimelineData 클래스 매핑
|
|
```java
|
|
@Entity
|
|
@Table(name = "timeline_data")
|
|
public class TimelineData extends BaseTimeEntity {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(name = "event_id", nullable = false, length = 36)
|
|
private String eventId;
|
|
|
|
@Column(name = "timestamp", nullable = false)
|
|
private LocalDateTime timestamp;
|
|
|
|
// ... 기타 필드
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
**문서 버전**: v1.0
|
|
**작성자**: Backend Architect (최수연 "아키텍처")
|
|
**작성일**: 2025-10-29
|