Intro
AWS EC2 t2.micro 환경에서 Socket.IO 기반 채팅 서비스 를 테스트하는 과정에서 발생한 Socket connection timeout 문제와 해결방안
이번 포스트에서는 다음 내용을 다룹니다
문제 원인 분석 과정
t2.micro의 리소스 제약을 극복하기 위한 최적화 단계
문제 상황
테스트 Phase
phases:
- name: "Warm up" # 기존 단계
duration: 30
arrivalRate: 1
rampTo: 2
- name: "Normal load" # 해당 단계부터 추가 단계
duration: 60
arrivalRate: 2
rampTo: 5
- name: "Peak load" # 문제 발생 단계
duration: 30
arrivalRate: 10
rampTo: 15
- duration: 60 # Test duration in seconds
arrivalRate: 5 # 5 users per second
테스트 구성
테스트 결과
전체 가상 사용자(VUs) :
EC2 : 930명 생성, 117명 실패.
로컬 : 930명 생성, 0명 실패.
WebSocket 오류 :
EC2 : 30개의 WebSocket 오류 발생.
로컬 : 오류 없음.
Emit Rate :
응답 시간 :
EC2 :
최소: 0.1초, 최대: 5.3초.
평균: 0.3초, 중앙값: 0.3초.
P95: 0.6초, P99: 1.1초.
로컬 :
최소: 0.1초, 최대: 8.3초.
평균: 0.2초, 중앙값: 0.2초.
P95: 0.3초, P99: 1.1초.
분석 :
P99 수준에서는 두 환경이 유사한 성능을 보임.
EC2는 더 큰 변동성을 보이며, 최대 응답 시간이 5.3초로 로컬 환경보다 낮지만(로컬 8.3초) 더 일관성이 부족함.
세션 길이 :
EC2 :
최소: 4528.1ms, 최대: 37164ms.
평균: 17216.4ms, 중앙값: 14332.3ms.
P95: 35964.9ms, P99: 36691.5ms.
로컬 :
최소: 2109.7ms, 최대: 23195.3ms.
평균: 7101.8ms, 중앙값: 2465.6ms.
P95: 21813.5ms, P99: 22703.7ms.
분석 :
EC2의 세션 길이는 P95와 P99 수준에서 현저히 길어 백엔드 처리 지연이 있음을 나타냄.
로컬 환경은 세션 길이를 더 효율적으로 처리.
로컬 환경 :
모든 사용자 완료.
WebSocket 연결 성공률 100%.
EC2 환경 :
194개의 WebSocket 오류 발생.
사용자 중 24% 실패.
주요 오류: ERR_SOCKET_CONNECTION_TIMEOUT
상세 비교 테이블
지표
EC2 (AWS t2.micro)
로컬 (MacBook Pro)
분석
생성된 가상 사용자
930
930
두 환경 모두 동일한 사용자 수를 시뮬레이션
실패 사용자
117
0
EC2는 리소스 제한으로 117명 실패
WebSocket 오류
30
0
EC2는 서버 과부하로 WebSocket 오류 발생
Emit Rate
4회/초
6회/초
로컬이 50% 더 높은 처리량을 보여줌
응답 시간 (평균)
0.3초
0.2초
EC2는 평균 응답 시간이 더 느림
응답 시간 (P95)
0.6초
0.3초
EC2가 약간 더 높은 응답 시간 변동성을 보임
응답 시간 (P99)
1.1초
1.1초
두 환경 모두 P99 수준에서 비슷한 성능
세션 길이 (평균)
17216.4ms
7101.8ms
EC2는 백엔드 지연으로 평균 세션 길이가 더 긺
세션 길이 (P95)
35964.9ms
21813.5ms
EC2의 세션 길이가 약 65% 더 긺
세션 길이 (P99)
36691.5ms
22703.7ms
EC2는 긴 세션 길이를 처리하는 데 어려움
세션 길이(Session Length)의 의미
테스트에서 "세션 길이(Session Length)"는 가상 사용자가 서버와의 상호작용을 유지한 총 시간을 의미
사용자가 서버와 연결을 맺고, 요청을 주고받으며, 세션이 종료될 때까지 걸린 시간을 측정한 것
세션 길이의 주요 측정값
Min
(최소 세션 길이)
가장 짧게 유지된 세션의 시간
빠르게 연결이 종료된 사용자, 혹은 간단한 작업만 수행한 경우에 해당
Max
(최대 세션 길이)
가장 오랫동안 유지된 세션의 시간
느린 응답 처리, 서버 부하, 네트워크 지연 등으로 세션이 길어졌을 수 있음
Mean
(평균 세션 길이)
모든 세션 길이의 평균값
시스템이 요청을 처리하고 세션을 종료하는 데 소요된 평균적인 시간을 나타냄
Median
(중앙값)
세션 길이 데이터의 중앙값. 일부 극단적인 값(너무 짧거나 긴 세션 길이)의 영향을 받지 않기 때문에 시스템의 일반적인 동작을 더 잘 반영
P95
, P99
(상위 95%, 99% 세션 길이)
세션 길이의 상위 95%, 99% 값
전체 세션 중 상위 5%, 1%의 길이를 가진 "긴 세션"들을 나타냄
서버 과부하나 특정 요청이 비정상적으로 오래 걸리는 경우를 분석하는 데 유용
세션 길이의 의미와 해석
짧은 세션 길이
서버가 요청을 빠르게 처리하고 연결을 효율적으로 종료
예를 들어, 요청-응답 패턴이 간단하거나 서버와의 연결이 즉시 닫힐 경우
긴 세션 길이
서버가 요청 처리에 시간이 오래 걸리거나, 네트워크 지연, 백엔드 병목, 또는 비정상적인 연결 유지로 인해 세션이 길어짐
WebSocket 테스트의 경우, 서버가 메시지를 처리하는 속도가 느리거나 연결을 적절히 종료하지 못할 때 발생
세션 길이의 균일성
세션 길이가 짧고 일관되면 서버가 안정적으로 작동하고 있음을 의미
세션 길이의 편차가 크거나 긴 세션이 많다면 병목현상, 서버 부하, 또는 특정 요청의 비효율성을 나타냄
테스트에서 세션 길이의 분석
EC2 테스트 결과
평균 세션 길이: 17,216.4ms
P99 세션 길이: 36,691.5ms
최대 세션 길이: 37,164ms
의미
일부 세션이 지나치게 길어지고, P95/P99 값도 매우 높음
이는 백엔드의 요청 처리 지연이나 서버가 병목 현상을 겪고 있음을 나타냄
로컬 테스트 결과
평균 세션 길이: 7,101.8ms
P99 세션 길이: 22,703.7ms
최대 세션 길이: 23,195.3ms
의미
세션 길이가 EC2에 비해 짧고, P99 수준에서도 비교적 안정적
로컬 환경은 서버 리소스가 충분하므로 요청을 더 빠르고 안정적으로 처리
문제 상황
phase 추가 후 peak load 진행 시 EC2 환경에서만 에러 발생
메모리 사용량(좌) CPU(우) 사용량을 통해 시스템 리소스 사용과 문제 원인 분석
CloudWatch 및 htop 결과 해석
CloudWatch 메트릭
CloudWatch에서 EC2 인스턴스의 CPU, 네트워크 및 크레딧 사용량을 관찰한 결과는 다음과 같다
지표
결과
분석
CPU 사용률(%)
최대 43%
CPU 부하가 크지는 않으나, 동시 연결 처리 시 리소스 부족 가능성이 있음
네트워크 입출력
최대 1.22MB/4.53MB
WebSocket 트래픽으로 인해 네트워크 부하가 일시적으로 증가
패킷 처리량
최대 6.57k 패킷
동시 요청 처리 시 네트워크 패킷 처리량이 증가
CPU 크레딧
사용량 1.53, 잔여 144
크레딧 잔여량이 충분하지만, 높은 사용량은 CPU 제한의 신호일 수 있음
htop 프로세스 관찰
CPU 사용량
Node.js 프로세스
dist/main.js
프로세스가 각 8.6%씩 CPU를 점유하며, 병렬 처리에도 불구하고 높은 부하를 보임
PostgreSQL
데이터베이스 프로세스가 2.2%의 CPU를 사용하며, 동시에 여러 인스턴스가 작동 중
Docker 및 containerd
Docker 관련 프로세스가 총 4.8%의 CPU를 사용
Nginx
Nginx는 리버스 프록시로 작동하며 CPU 사용량은 2% 미만으로 병목 원인은 아님
메모리 사용량
Node.js 프로세스
각 프로세스가 가상 메모리(Virtual Memory) 10.7GB와 실제 메모리(Resident Memory) 13MB를 소비
PostgreSQL
전체적으로 217MB의 Resident Memory를 사용
t2.micro 인스턴스 (1GB RAM)
메모리 제한이 뚜렷하며, 리소스 부족으로 성능 문제가 발생할 가능성 높음
multipathd
, polkitd
, snapd
불필요한 리소스이며, 제거 시 성능 최적화 가능
Nginx
Nginx 워커 프로세스는 활성 상태이지만 리소스 소비가 미미함
EC2 vs 로컬 비교
항목
EC2
로컬(MacBook Pro)
분석
WebSocket 에러
30
0
EC2에서 연결 시간 초과로 인한 오류
Emit Rate
4/sec
6/sec
EC2는 처리율이 로컬보다 33% 낮음
응답 시간 (P95)
0.6초
0.3초
EC2가 더 높은 응답 시간 변동성을 보임
세션 길이 (P99)
36,691.5ms
22,703.7ms
EC2의 세션 길이가 더 길어 백엔드 처리 지연을 나타냄
CPU 사용량
최대 43%
낮음
EC2의 CPU 제한이 성능 병목으로 작용할 가능성
메모리 사용량
1GB RAM(부족)
충분함
EC2는 Node.js 및 PostgreSQL 메모리 점유로 메모리 압박
병목 원인 분석
CPU 자원 부족
EC2의 단일 vCPU는 동시 연결(WebSocket) 처리에 한계
Nginx와 Node.js가 CPU 사용률을 분산하지만 충분치 않음
메모리 압박
Node.js와 PostgreSQL이 제한된 t2.micro의 RAM(1GB)을 소모하며, 스왑(Swap) 설정 부족으로 인해 성능 저하 가능
Node.js 프로세스 관리 비효율
과도한 Node.js 프로세스 생성으로 인해 불필요한 리소스 낭비
PostgreSQL 문제
PostgreSQL의 동시 연결 처리 속도가 느려져 전체 성능에 영향을 미침
캐싱이 없을 경우, 다수의 데이터베이스 쿼리로 I/O가 과부하 상태
불필요한 시스템 서비스
multipathd
, snapd
등 불필요한 서비스가 추가적인 리소스를 소모
네트워크 지연
WebSocket 트래픽이 집중되면서 네트워크 대역폭이 일시적으로 높아짐
우선순위별 해결 방안
우선순위 1: Artillery 테스트 부하 조정
이유
테스트 부하를 줄이는 것이 가장 간단하고 즉각적인 해결책
EC2 환경이 감당할 수 있는 수준으로 부하를 조정해야 올바른 성능 분석 가능
적용 방법
그러나 주 목적이 부하 분산을 통한 최적화 이기 때문에 보류
우선순위 2: Node.js 프로세스 최적화
이유
Node.js 프로세스가 CPU와 메모리를 집중적으로 사용하므로 병목 해소의 핵심
적용 방법
PM2를 사용한 프로세스 관리
메모리 사용량 점검
그러나 주 목적이 부하 분산을 통한 최적화 이기 때문에 보류
우선순위 3: Nginx 설정 최적화
이유
WebSocket 연결 유지 및 타임아웃 방지
우선순위 4: 불필요한 서비스 비활성화
이유
multipathd
, snapd
등의 서비스는 불필요한 리소스를 사용.
적용 방법
우선순위 5: Swap 메모리 추가
우선순위 6: Redis 캐시 도입
이유
적용 방법
추가적 리소스가 필요하기 때문에 보류
우선순위 7: PostgreSQL 최적화
이유
데이터베이스 부하가 WebSocket 처리의 주요 병목 중 하나
적용 방법
PostgreSQL 설정 변경
pgbouncer를 활용한 연결 풀링
Redis를 활용한 데이터 캐싱
2.8 우선순위 8: 장기적 개선
이유
단기적 해결책으로 부족한 성능을 보완하기 위한 인프라 확장
적용 방법
수평적 확장
로드 밸런서 사용 및 다중 EC2 인스턴스 배포
컨테이너화
더 높은 EC2 인스턴스 사용
t3.micro 또는 t3.small로 업그레이드
비용 효율성을 위해 현재는 t2.micro만을 사용할 것이므로 보류
우선순위 기준
1. 성능에 미치는 영향
우선순위 높음 : 주요 병목을 직접 해결하며 성능 개선 효과가 큰 솔루션
예시: Node.js 또는 PostgreSQL의 CPU/메모리 사용 최적화
우선순위 낮음 : 현재 문제에 간접적인 영향을 미치거나 개선 효과가 적은 솔루션
2. 구현 용이성
우선순위 높음 : 간단하고 빠르게 적용할 수 있으며 시스템에 큰 변경이 필요하지 않은 솔루션
예시: Artillery 테스트 부하 조정, Redis 캐싱 활성화
우선순위 낮음 : 구현에 많은 시간과 리소스가 소요되거나 복잡한 솔루션
예시: 추가 EC2 인스턴스 배포 또는 인프라 업그레이드
3. 현재 리소스 사용 가능 여부
우선순위 높음 : 현재 t2.micro와 같은 제한된 환경에서도 실행 가능한 솔루션
예시: 불필요한 서비스 비활성화, PostgreSQL 설정 변경
우선순위 낮음 : 추가 리소스나 고사양 인프라가 필요한 솔루션
4. 비용 효율성
우선순위 높음 : 비용이 적게 들거나 무료 도구로 구현 가능한 솔루션
예시: Swap 메모리 추가, Nginx 설정 최적화
우선순위 낮음 : 높은 비용이 소요되거나 유료 도구가 필요한 솔루션
5. 확장성 및 미래 대비
우선순위 높음 : 시스템 확장성을 보장하며 장기적인 성능 안정성을 지원하는 솔루션
예시: Redis 캐싱 도입, Node.js 프로세스 최적화
우선순위 낮음 : 임시적인 해결책으로 장기적 관점에서 비효율적이거나 재작업이 필요한 솔루션
예시: t2.micro 인스턴스에서 최대 부하 처리
6. 문제의 심각성
우선순위 높음 : WebSocket 연결 타임아웃과 같은 치명적인 문제를 해결하는 솔루션
예시: WebSocket 연결 최적화, Node.js CPU 최적화
우선순위 낮음 : 작은 비효율성을 해결하거나 주요 성능 지표와 무관한 솔루션
7. 리스크
우선순위 높음 : 시스템 안정성에 영향을 주지 않고 안전하게 적용할 수 있는 솔루션
예시: 불필요한 서비스 비활성화, Nginx 타임아웃 설정 조정
우선순위 낮음 : 새로운 문제를 유발할 가능성이 높거나 복구 계획이 필요한 솔루션
예시: PostgreSQL 설정을 크게 변경하거나 새로운 구성 도구 도입
우선순위 기준 요약
기준
핵심 질문
예시
성능에 미치는 영향
주요 병목을 직접 해결하는가?
Redis 캐싱으로 데이터베이스 부하 감소
구현 용이성
적용이 간단하고 빠른가?
Artillery 테스트 부하 조정
리소스 사용 가능 여부
현재 사용 가능한 리소스로 가능한가?
PostgreSQL 설정 변경
비용 효율성
비용 대비 효과가 높은가?
Swap 메모리 추가
확장성 및 미래 대비
시스템 확장성을 준비할 수 있는가?
Node.js 프로세스 최적화
문제의 심각성
치명적인 문제를 해결하는가?
WebSocket 연결 타임아웃 오류 해결
리스크
안정성을 저해하지 않고 안전하게 구현 가능한가?
불필요한 서비스 비활성화
결론
로컬과 EC2의 리소스 차이와 네트워크 환경 차이로 인한 부하테스트에서의 ERR_SOCKET_CONNECTION_TIMEOUT
발생
테스트 결과 분석
cloudwatch와 htop을 이용해 병목 원인 분석
문제 해결을 위한 방안 우선순위 별 방법과 이유
위에 언급된 방법을 통해 앞으로의 글에서 해결과정과 그 결과 차이를 분석할 예정