M13 ‐ 현재 Docker Compose 기반 구조의 한계 분석 - 100-hours-a-week/KTB4-4th-wiki GitHub Wiki
V1 Docker Compose 기반 구조 한계 분석
1. V1 구조
V1은 하나의 EC2에서 Frontend, Backend, AI Server, Redis를 Docker Compose로 실행하고, MySQL과 PostgreSQL은 동일 EC2 호스트에 직접 설치하여 운영한다.
Internet
│
▼
Nginx (:443)
├── Frontend (Next.js :3000)
└── Backend (Spring Boot :8080)
├── AI Server (FastAPI :8000)
│ └── PostgreSQL + pgvector
├── Redis
└── MySQL
1.1 V1 구성요소
| 구성요소 | V1 구성 |
|---|---|
| Compute | EC2 t3.medium (2 vCPU, 4 GiB) |
| Reverse Proxy | Nginx |
| Frontend | Next.js, Docker Container |
| Backend | Spring Boot, Docker Container |
| AI Server | FastAPI, Docker Container |
| Cache | Redis, Docker Container |
| 서비스 DB | MySQL, EC2 Host |
| AI DB | PostgreSQL + pgvector, EC2 Host |
| 배포 | GitHub Actions + ECR + SSM + Docker Compose |
| 모니터링 | CloudWatch, CloudWatch Agent |
V1은 초기 서비스에서 구성이 단순하고 운영 비용이 낮다는 장점이 있다. 하지만 단일 EC2에 주요 서비스와 DB가 집중되어 있어 장애 격리, 자원 확장 및 배포 안정성 측면에서 한계가 존재한다.
2. 단일 EC2의 장애가 전체 서비스 장애로 이어짐
2.1 단일 장애 지점(SPOF)
V1은 다음 구성요소를 동일 EC2에서 운영한다.
EC2
├── Nginx
├── Frontend
├── Backend
├── AI Server
├── Redis
├── MySQL
└── PostgreSQL + pgvector
EC2 인스턴스 자체에 장애가 발생하면 주요 서비스가 동시에 영향을 받는다.
개별 컨테이너 장애가 반드시 전체 서비스 장애로 이어지는 것은 아니지만, EC2 장애를 격리할 수 없다는 한계가 있다.
2.2 V2 개선 방향
- FE / BE / AI를 각각 독립적인 ECS Service로 분리
- Production 환경에서 서비스별 복수 Task 운영
- ALB Health Check를 통한 정상 Task로 요청 전달
- RDS 및 ElastiCache를 애플리케이션 실행 환경에서 분리
- 다중 AZ 배치를 통한 단일 AZ 장애 대응 기반 확보
3. V1 부하 테스트 목적 및 성능 한계 분석
3.1 부하 테스트 수행 배경
V1은 MAU 약 1,000명, DAU 약 80~140명 규모의 초기 서비스를 가정하여 단일 EC2(t3.medium) 환경으로 구성하였다.
| 지표 | V1 예상 규모 |
|---|---|
| MAU | 약 1,000명 |
| WAU | 약 400~500명 |
| DAU | 약 80~140명 |
| 일반 Peak Traffic | 약 0.16 RPS |
예상 Peak Traffic은 약 0.16 RPS로, 초기 서비스 규모에서는 높은 처리량이 요구되지 않았다.
그러나 예상 트래픽을 처리할 수 있다는 사실만으로 서버의 처리 성능이나 향후 트래픽 증가에 대응할 수 있는 범위를 판단하기는 어렵다.
또한 V1은 Frontend, Backend, AI Server, Redis, MySQL, PostgreSQL이 동일 EC2의 컴퓨팅 자원을 공유하므로, 부하 증가에 따른 응답시간 변화와 자원 사용량을 확인할 필요가 있었다.
이에 따라 다음 세 가지를 목적으로 부하 테스트를 수행하였다.
- 예상 트래픽 수용 여부 검증: V1에서 예상한 사용자 규모의 요청을 안정적으로 처리할 수 있는지 확인
- SLO 충족 처리량 확인: 부하를 증가시키면서 Backend API의 지연시간 SLO를 만족하는 처리량 범위 측정
- 성능 저하 및 자원 포화 구간 탐색: CPU, Memory, HikariCP 등의 지표를 관측하여 추가 분석이 필요한 구간 파악
이번 테스트의 목적은 특정 병목 원인을 사전에 가정하고 검증하는 것이 아니라, 현재 인프라가 어느 수준의 요청까지 성능 목표를 충족하는지 확인하고 이후 개선을 위한 기준 데이터를 확보하는 것이었다.
3.2 테스트 부하 확대 배경
초기에는 예상 Peak Traffic 약 0.16 RPS를 기준으로 서비스 처리 가능 여부를 검토하였다.
이후 100 RPS 수준에서도 Backend API 지연시간 SLO를 충족하여, 단순한 예상 트래픽 검증에서 시스템의 성능 한계 탐색으로 테스트 목적을 확대하였다.
500 RPS는 서비스가 반드시 충족해야 하는 목표 처리량이 아니라, 높은 부하에서 시스템의 응답시간과 자원 사용량이 어떻게 변하는지 관측하기 위한 테스트 조건으로 설정하였다.
500 RPS 목표 테스트에서 CPU 포화 및 DB Connection 대기 증가가 관측됨에 따라, 이후 100~300 RPS 구간에서 단계별 테스트를 수행하여 성능 저하가 발생하는 구간을 구체화하였다.
3.3 고부하 테스트 결과
| 지표 | 500 RPS 목표 테스트 결과 |
|---|---|
| 실제 처리량 | 198.82 RPS |
| EC2 CPU | 약 99% |
| Memory | 최대 약 64% |
| HikariCP Active | 최대 10 |
| HikariCP Pending | 최대 186 |
| p95 | 3.92초 |
| p99 | 5.17초 |
500 RPS 목표 테스트에서는 목표 처리량에 도달하지 못했으며, CPU 사용률이 포화 수준에 도달하고 DB Connection 대기가 증가하였다.
이를 통해 고부하 환경에서 자원 포화 및 요청 처리 지연 현상이 발생한다는 점을 확인하였다.
다만 해당 결과만으로 CPU, DB Connection Pool, 애플리케이션 로직 중 어느 요소가 최초 병목인지 특정할 수는 없었다.
3.4 단계별 부하 테스트 결과
Backend API 지연시간 SLO
- p95 ≤ 300ms
- p99 ≤ 1,000ms
| 목표 RPS | 실제 RPS | p95 | p99 | SLO |
|---|---|---|---|---|
| 100 | 99.94 | 22.21ms | 47.02ms | PASS |
| 200 | 199.97 | 70.23ms | 238.30ms | PASS |
| 250 | 249.72 | 279.81ms | 808.24ms | PASS |
| 300 | 279.64 | 1.96s | 2.87s | FAIL |
단계별 테스트 결과, 약 250 RPS까지 지연시간 SLO를 충족하였으나 약 280 RPS에서는 SLO를 초과하였다.
따라서 해당 테스트 조건에서 SLO 충족이 확인된 최대 부하는 약 250 RPS이며, 약 280 RPS 수준에서는 성능 저하가 발생한다는 점을 확인하였다.
다만 250 RPS에서 p95가 279.81ms로 SLO 기준에 근접하므로, 이를 장기간 안정적으로 처리할 수 있는 운영 용량으로 단정할 수는 없다.
3.5 분석 결과 및 한계
이번 부하 테스트를 통해 다음 사항을 확인하였다.
- V1 예상 Peak Traffic인 약 0.16 RPS보다 높은 100~250 RPS 구간에서 지연시간 SLO 충족
- 약 280 RPS 수준에서 지연시간 SLO 초과
- 500 RPS 목표 고부하 테스트에서 CPU 포화 및 DB Connection 대기 증가
- 단일 EC2 환경에서 부하 증가에 따른 처리 성능 저하 현상 관측
반면 다음 사항은 확인하지 못하였다.
- 성능 저하를 최초로 유발한 구체적인 병목 지점
- CPU 포화와 DB Connection 대기 사이의 정확한 인과관계
- 특정 Backend API 또는 SQL Query가 성능 저하에 기여한 정도
- 서비스별 자원 사용량과 성능 저하의 직접적인 연관성
따라서 이번 테스트는 병목 원인 규명보다는 성능 한계 구간 식별 및 운영 기준 데이터 확보에 의미가 있다.
3.6 V2 개선 방향 및 후속 검증
V1 부하 테스트 결과만으로 ECS 전환이 성능 저하를 해결한다고 단정할 수는 없다.
다만 단일 EC2에서 여러 서비스가 자원을 공유하는 구조적 한계를 고려하여, V2에서는 다음과 같이 인프라를 분리한다.
- FE / BE / AI를 각각 독립적인 ECS Service로 구성
- MySQL과 PostgreSQL을 RDS로 이전
- Redis를 ElastiCache로 이전
- ECS Task별 CPU 및 Memory 설정
- 서비스별 Auto Scaling 적용 검토
V2 구축 이후에는 V1과 동일한 API 및 부하 조건으로 테스트를 수행하여 성능 변화를 비교한다.
또한 CPU, JVM/GC, Tomcat Thread, HikariCP, DB Query Latency 등의 지표를 추가 수집하여 성능 저하 발생 시 구체적인 병목 원인을 분석할 수 있도록 모니터링을 개선한다.
이를 통해 V1 테스트 결과를 V2의 성능 비교 기준선(Baseline)으로 활용한다.
4. 서비스별 리소스 격리가 부족함
V1에서는 FE, BE, AI, Redis, DB가 동일 EC2의 CPU와 Memory를 공유한다.
Backend나 AI Server에 부하가 집중되면 다른 서비스의 처리 성능에도 영향을 줄 수 있다.
Docker Compose에서도 컨테이너별 CPU 및 Memory 제한을 설정할 수 있지만, 단일 EC2의 물리적 자원 한계를 제거할 수는 없다.
4.1 V2 개선 방향
- ECS Task 단위로 CPU 및 Memory 지정
- 서비스별 독립적인 Task 실행
- 서비스별 Auto Scaling 정책 적용
- RDS 및 ElastiCache를 통한 DB와 Cache 자원 분리
이를 통해 서비스 간 자원 경쟁을 완화하고 독립적인 확장 기반을 마련한다.
5. 배포 시 서비스 중단 가능성이 존재함
5.1 V1 배포 구조
V1 CD는 GitHub Actions에서 수동으로 실행하며, AWS SSM SendCommand를 통해 EC2 배포 스크립트를 호출한다.
GitHub Actions CD
↓
AWS OIDC 인증
↓
SSM SendCommand
↓
EC2 배포 스크립트
↓
Docker Image Pull
↓
Docker Compose 컨테이너 교체
↓
Health Check
↓
배포 완료
기존 컨테이너를 교체하는 동안 요청을 처리할 대체 인스턴스가 없으므로 일시적인 서비스 중단이 발생할 수 있다.
5.2 실제 CD 워크플로 실행 시간
GitHub Actions 실행 기록을 기준으로 확인한 CD 소요 시간은 다음과 같다.
| 서비스 | 실행 일시 (KST) | CD 소요 시간 |
|---|---|---|
| Frontend | 2026.10.01 14:02 | 1분 1초 |
| Backend | 2026.10.04 16:33 | 1분 4초 |
| AI Server | 2026.10.02 11:24 | 37초 |
실행 기록
- Frontend: Frontend CD
- Backend: Backend CD
- AI Server: AI CD
위 값은 GitHub Actions 워크플로 시작부터 완료까지의 전체 실행 시간이다.
AWS 인증, SSM 명령 전송, 이미지 교체, Health Check 등의 과정이 포함될 수 있다.
수동으로 배포 버튼을 누르거나 입력값을 작성하는 시간은 포함하지 않는다.
5.3 CD 실행 시간과 서비스 중단 시간 구분
| 서비스 | CD 실행 시간 | 실제 서비스 중단 시간 |
|---|---|---|
| Frontend | 1분 1초 | 미측정 |
| Backend | 1분 4초 | 미측정 |
| AI Server | 37초 | 미측정 |
전체 CD 실행 시간이 실제 서비스 중단 시간을 의미하는 것은 아니다.
서비스 중단 시간은 기존 컨테이너가 정상 요청을 처리하지 못하는 시점부터 신규 컨테이너가 정상 응답을 제공하는 시점까지로 정의할 수 있다.
실제 중단 시간은 배포 중 외부에서 서비스 엔드포인트에 지속적으로 요청을 보내 측정해야 한다.
5.4 V2 개선 방향
V2에서는 ECS Service와 ALB를 활용한 Rolling Deployment를 적용한다.
기존 Task v1 정상 요청 처리
↓
신규 Task v2 실행
↓
ALB Health Check
↓
신규 Task Healthy
↓
신규 Task로 트래픽 전달
↓
기존 Task Connection Draining
↓
기존 Task 종료
신규 Task가 준비된 이후 기존 Task를 종료하도록 구성하여 배포 중 서비스 중단을 최소화한다.
단, 적절한 Task 수, Deployment Configuration, Health Check 및 Graceful Shutdown 설정이 필요하다.
6. 컨테이너 장애 복구 기능이 제한적임
Docker Compose에서도 restart 정책과 healthcheck를 적용할 수 있다.
하지만 V1에서는 단일 EC2 인스턴스 장애 발생 시 다른 실행 노드에서 자동으로 서비스를 복구하기 어렵다.
또한 단일 컨테이너가 재시작되는 동안 요청을 처리할 대체 인스턴스가 부족하다.
6.1 V2 개선 방향
ECS Service의 Desired State 관리 기능을 활용하여 설정된 Task 수를 유지한다.
ECS Service (desired_count = 2)
↓
Task 1개 장애
↓
새 Task 자동 실행
↓
ALB Health Check
↓
정상 Task로 요청 전달
이를 통해 Task 장애 시 자동 교체와 정상 Task 중심의 요청 처리가 가능하도록 구성한다.
7. DB까지 애플리케이션 서버와 장애 영역을 공유함
V1의 MySQL과 PostgreSQL은 EC2 호스트에 직접 설치되어 있다.
따라서 애플리케이션과 CPU, Memory, Disk I/O를 공유하며 EC2 장애 시 함께 영향을 받는다.
주요 한계
- 애플리케이션과 DB 간 자원 경쟁
- EC2 장애 시 DB 동시 접근 불가
- DB 백업, 패치 및 장애 대응 직접 관리
- DB만 독립적으로 확장하기 어려움
7.1 V2 개선 방향
- MySQL → Amazon RDS
- PostgreSQL + pgvector → Amazon RDS
- Redis → Amazon ElastiCache
애플리케이션과 데이터베이스의 컴퓨팅 자원을 분리하고 DB 운영 관리 일부를 AWS 관리형 서비스에 위임한다.
단, RDS Single-AZ 구성만으로는 DB 고가용성이 보장되지 않으므로 별도 검토가 필요하다.
8. 인프라 구성이 EC2 내부 상태에 의존함
V1에서는 Docker Compose, Nginx, MySQL, PostgreSQL, 배포 스크립트 등의 설정이 EC2 내부에 존재한다.
서버에서 직접 설정을 변경하고 이를 코드에 반영하지 않으면 실제 운영 환경과 저장소의 설정 사이에 차이가 발생할 수 있다.
이러한 차이는 서버 재구축과 장애 복구 시 동일한 환경을 재현하기 어렵게 만든다.
8.1 V2 개선 방향 — Terraform IaC
V2에서는 주요 AWS 인프라를 Terraform으로 관리한다.
- VPC, Subnet, Route Table, Security Group
- ALB, Listener, Target Group
- ECS Cluster, Service, Task Definition 기반 설정
- RDS, ElastiCache
- IAM Role 및 Policy
- Parameter Store
- CloudWatch Log Group 및 Alarm
Terraform State는 다음과 같이 분리한다.
| 구분 | 관리 범위 |
|---|---|
| Bootstrap | Terraform State S3, Terraform 실행 Role |
| Shared | ECR, CI Role, 공통 Parameter |
| Dev | Dev 전용 인프라 |
| Prod | Prod 전용 인프라 |
적용 순서: Bootstrap → Shared → Dev → Prod
Terraform은 인프라 구성을 관리하고, GitHub Actions CD는 애플리케이션 이미지 버전과 배포를 담당하도록 역할을 분리한다.
9. 상시 실행 서비스와 일회성 작업의 생명주기 분리 필요
V1에서는 상시 실행 서비스와 데이터 처리 작업이 동일 EC2 자원을 공유한다.
AI Catalog Sync, AI Catalog Import 등의 실행 단위가 추가되면 실행 시점과 자원 할당을 별도로 관리할 필요가 있다.
9.1 V2 개선 방향
| 실행 유형 | AWS 구성 |
|---|---|
| Frontend | ECS Service |
| Backend | ECS Service |
| AI Server | ECS Service |
| AI Catalog Sync | ECS RunTask |
| AI Catalog Import | ECS RunTask |
상시 실행 서비스는 ECS Service로 유지하고, 일회성 작업은 필요한 시점에 ECS Task로 실행하여 실행 생명주기를 분리한다.
10. Auto Scaling이 어려움
V1에서는 특정 서비스에 부하가 증가하더라도 동일 EC2의 컴퓨팅 자원을 공유한다.
따라서 서비스별 독립적인 확장이 어렵고 EC2 인스턴스 사양을 높이는 Vertical Scaling에 의존하기 쉽다.
10.1 V2 개선 방향
ECS Service Auto Scaling을 활용하여 CPU, Memory 등 지표를 기준으로 필요한 서비스의 Task 수를 조절한다.
Backend CPU 증가
↓
Backend ECS Auto Scaling
↓
Backend Task 증가
Frontend Task 유지
AI Task 유지
이를 통해 부하가 증가한 서비스만 선택적으로 확장할 수 있도록 한다.
11. V1 한계와 V2 개선 방향 비교
| V1 구조적 한계 | V2 개선 방향 | 기대 효과 |
|---|---|---|
| 단일 EC2 장애 지점 | ECS Fargate + ALB | 장애 격리 기반 확보 |
| 서비스별 확장 제약 | ECS Service 분리 | 독립적인 Scale-out |
| CPU / Memory 자원 공유 | Task별 리소스 설정 | 자원 경쟁 완화 |
| 배포 중 일시적 중단 가능성 | ECS Rolling Deployment | 배포 가용성 개선 |
| 단일 컨테이너 복구 의존 | ECS Desired State 관리 | 자동 Task 교체 |
| DB와 애플리케이션 자원 공유 | RDS 분리 | DB 자원 격리 |
| Redis 자체 운영 | ElastiCache | Cache 운영 부담 완화 |
| EC2 수동 설정 의존 | Terraform IaC | 인프라 재현성 확보 |
| Batch와 상시 서비스 자원 공유 | ECS Service / RunTask 분리 | 실행 생명주기 분리 |
| EC2 중심 확장 | ECS Auto Scaling | 서비스별 탄력적 확장 |
12. 결론
V1은 일반 Peak Traffic 약 0.16 RPS를 예상한 초기 서비스 규모에 맞춰 단일 EC2 기반으로 구성하였다.
부하 테스트에서는 예상 트래픽의 수용 가능 여부를 확인한 이후 시스템의 성능 한계와 병목을 파악하기 위해 테스트 부하를 확대하였다.
그 결과 약 250 RPS까지 Backend API 지연시간 SLO를 충족했으나, 약 280 RPS 수준에서는 p95 1.96초로 SLO를 초과하였다.
500 RPS 목표 고부하 테스트에서는 EC2 CPU 포화와 DB Connection 대기 증가가 관측되었다.
배포 측면에서는 GitHub Actions CD 실행 시간이 Frontend 1분 1초, Backend 1분 4초, AI Server 37초로 확인되었다. 다만 실제 서비스 중단 시간은 별도로 측정되지 않았다.
V2 전환의 목적은 단순히 Docker Compose를 ECS로 교체하는 것이 아니라, 단일 EC2에 집중된 컴퓨팅 자원과 장애 영역을 분리하고 서비스별 배포·확장·운영을 독립적으로 관리할 수 있는 구조를 마련하는 것이다.
다만 V1의 성능 저하가 Docker Compose 자체로 인해 발생했다고 단정할 수 없으며, V2 구축 이후 동일한 테스트 조건에서 성능 및 안정성을 재검증해야 한다.