저희의 아키텍처는 api 서버와 running 서버가 나뉘어있습니다.

api 서버에서 클라이언트의 코드 실행 요청을 받고 running 서버에서 실제로 코드를 실행하게 됩니다.
코드 실행 요청 발전 과정
클라이언트 → api 서버 | api서버 → running 서버 | running 서버 → api 서버 | |
V1 | http 요청 | http 요청 | http 응답 |
V2 | http 요청 | Redis Message Queue | Redis Key-Value, 주기적 탐색 |
V3 | 소켓 연결 후 소켓 메세지 | Redis Message Queue | Redis Pub-Sub |
초기에는 코드 실행요청을 단순 http로 보냈습니다.

하지만 코드 실행이 오래걸린다면 http 요청을 보낸 후 계속 대기해야한다는 것이 문제가 있다고 생각했고 개선 방법을 탐색하였습니다.
서버간 http 요청을 제거하기 위해서 Message Queue를 도입하기로 했습니다.

요청이 들어온 순서대로 메세지 큐에 쌓고, 실행 결과는 key-value 형태로 저장했습니다.
하지만 이 방법도 문제가 있었습니다.
여전히 클라이언트는 응답을 받을 때까지 대기하고 있고, api 서버는 결과를 찾을 때 까지 주기적으로 redis를 탐색해야하는 부담이 생겼습니다.
프로그래머스, 구름 등 기존 코드 실행기능을 제공하고 있는 서비스들을 벤치마크하여 코드 실행 기능을 발전시켰습니다.

완성된 코드 요청방식은 소켓, MQ, Pub/Sub 구조를 활용합니다.
클라이언트는 소켓을 연결하고 코드 실행 요청을 보냅니다.
api 서버는 해당 요청을 메세지 큐에 쌓고 running 서버에서는 실행이 완료되면 결과를 publish 합니다. 다시 api 서버는 구독 메시지를 받고 소켓으로 응답 메세지를 전달해주어 연결을 해제합니다.
- 서버에서 코드 실행이 완성된 시점에 실행 결과를 클라이언트에 보내주어야하기 때문에 양방향 통신이 가능한 소켓으로 연결했습니다.
- 실행이 요청된 코드들을 요청이 들어온 순서대로 처리해주어야하므로 메세지 큐를 이용해 running 서버에 전달했습니다.
- 하지만 코드 실행이 끝나는 시점은 순서를 보장할 수 없고 먼저끝나는 것이 먼저 응답되어야 한다고 생각하여 Pub/Sub구조를 활용하여 코드 실행결과를 보여주었습니다.
테스트 환경
코드 실행 기능을 완성하고 각 버전별로 어떤 차이가 있는지 실제 성능을 테스트해보며 차이를 확인해보고 싶어 부하테스트를 도입하였습니다.
- api 서버 :
[Compact] 2vCPU, 2GB Mem [g1]
- running 서버 :
[Standard] 8vCPU, 8GB Mem [g1]
pm2로 코어수만큼 프로세스 작동
- redis 서버 :
[Compact] 2vCPU, 2GB Mem [g1]
테스트 툴

Node 환경에서 대중적인 부하테스트 툴로 socket.io를 지원하고 실행 결과를 html로 쉽게 변환이 가능하여 Artillery.IO로 부하테스트를 진행했습니다.
서버 자원 모니터링 : htop
서버 자원모니터링을 htop을 이용해서 진행했습니다.

NCP에서 상세 모니터링을 제공하지만,
서버 응답이 느린 상황에서는 모니터링 정보가 전달되지 않아 정확히 볼 수 없어서 직접 서버에서 모니터링을 진행하였습니다.
테스트 조건
모든 시나리오에서 같은 언어, 코드로 진행했습니다.
언어 Python
실행 코드
cnt=0
for i in range(10):
cnt+=1
시나리오별 결과
→ 180초동안 가상사용자수를 500까지 점차적으로 초당 요청수를 늘려 어느정도의 요청까지 처리할 수 있는지 파악하는 테스트
V1 | V2 | V3 | |
vusers.failed | 9836 (21.4%) | 44268(96%) | 3 (0%) |
최대 cpu 사용률 | 100% | 83.5% | 73% |
평균 응답시간 | 106.7 ms | 620.3 ms | 125.2 ms |
V2는 메모리 사용률이 급격히 올라가는 것으로 보아 응답을 기다리는 로직에 문제가 있는 것으로 판단하여 다른 시나리오에서 제외
시간은 오래걸리지만 V3버전에서 더 높은 성공률과 더 낮은 cpu 사용률을 보임.
→ 180초동안 가상사용자(vusers)수를 300까지 점차적으로 초당 요청수를 늘려 어느정도의 요청까지 처리할 수 있는지 파악하는 테스트
V1 | V3 | |
vusers.failed | 3 (0%) | 28 (0%) |
최대 cpu 사용률 | 80.1% | 47.7% |
평균 응답시간 | 34.8 ms | 32.1ms |
응답 성공률, 평균 응답 시간은 비슷했으나 더 낮은 cpu 사용률을 보임.
ramp up 300 (180s) - sustain 600s
→ 180초동안 가상사용자(vusers)수를 300까지 점차적으로 늘리고, 600초간 동일한 수의 요청을 지속적으로 보내 서버의 동작을 확인하는 테스트
V1 | V3 | |
vusers.failed | 12 (0%) | 660 (0.003%) |
평균 cpu 사용률 | 70%대 | 40%대 |
최대 cpu 사용률 | 92% | 60%대 |
최대 메모리 사용량 | 1.3GB/1.92GB | 900MB/1.92GB |
평균 응답시간 | 85.6 ms | 32.1ms |
지속적인 부하가 있을 때 V3에서 약간 실패율이 올라갔지만 실패율이 1%가 되지 않음
평균 응답시간, 메모리 사용량, cpu 사용률에서 유의미한 차이를 보임
-> cpu 사용률 43% , memory 사용률 21% 감소
결론
이렇게 여러가지 시행착오를 겪으며 코드 실행 기능을 발전시켰습니다.
다른 서비스들을 참고하며 소켓, pub/sub, 메세지 큐를 도입하였고, 서버를 더 안정적으로 운영할 수 있게 기능을 구현할 수 있었습니다.
지속적인 부하가 있는 상황에서 cpu 사용률과 메모리 사용률에서 큰 감소를 볼 수 있었고(cpu 사용률 43% , memory 사용률 21% 감소), 실제 부하테스트 중에도 다른 api의 장애없이 이용할 수 있는 것을 확인할 수 있었습니다.
부하테스트를 진행하며 개발시 보이지 않았던 문제들을 발견하며 문제가 발생할 수 있는 코드들을 찾아 해결하며 코드를 개선할 수 있었습니다.
특히 메모리 누수가 발생하는 지점을 찾아 해결했던 것이 가장 기억에 남습니다. (메모리누수 잡기)
부하테스트를 진행하며 그 차이를 더 명확하게 비교해보는 경험을 할 수 있었습니다.
개발기
[코드실행기능 개발기 #1] 작업이 오래걸리는 요청을 어떻게 응답할까?
프로젝트에서 코드 실행 요청을 보내는 기능을 구현하고 있다. 클라이언트가 코드 실행 요청을 보내면 서버에서 그 코드를 실행해 결과를 알려주는 형태이다. 실행을 요청한 코드가 실행에 오
stop-thinking-start-now.tistory.com
[코드실행기능 개발기 #2] 작업이 오래걸리는 요청을 어떻게 응답할까?
[코드실행기능 개발기 #1] 작업이 오래걸리는 요청을 어떻게 응답할까? 프로젝트에서 코드 실행 요청을 보내는 기능을 구현하고 있다. 클라이언트가 코드 실행 요청을 보내면 서버에서 그 코드
stop-thinking-start-now.tistory.com
'Projects' 카테고리의 다른 글
게시판 전체 글 랭킹 점수 데이터 업데이트 속도 77.72% 개선 (bulk update vs batch update) (1) | 2024.01.23 |
---|---|
Querydsl을 이용한 커서 기반 페이지네이션 구현기 (0) | 2024.01.20 |
NestJS에 winston으로 로그 남기기 (0) | 2023.12.17 |
refresh token 도입기 (1) | 2023.12.17 |
OAuth 2.0은 무엇이고 어떻게 적용할 수 있을까? (1) | 2023.12.17 |
저희의 아키텍처는 api 서버와 running 서버가 나뉘어있습니다.

api 서버에서 클라이언트의 코드 실행 요청을 받고 running 서버에서 실제로 코드를 실행하게 됩니다.
코드 실행 요청 발전 과정
클라이언트 → api 서버 | api서버 → running 서버 | running 서버 → api 서버 | |
V1 | http 요청 | http 요청 | http 응답 |
V2 | http 요청 | Redis Message Queue | Redis Key-Value, 주기적 탐색 |
V3 | 소켓 연결 후 소켓 메세지 | Redis Message Queue | Redis Pub-Sub |
초기에는 코드 실행요청을 단순 http로 보냈습니다.

하지만 코드 실행이 오래걸린다면 http 요청을 보낸 후 계속 대기해야한다는 것이 문제가 있다고 생각했고 개선 방법을 탐색하였습니다.
서버간 http 요청을 제거하기 위해서 Message Queue를 도입하기로 했습니다.

요청이 들어온 순서대로 메세지 큐에 쌓고, 실행 결과는 key-value 형태로 저장했습니다.
하지만 이 방법도 문제가 있었습니다.
여전히 클라이언트는 응답을 받을 때까지 대기하고 있고, api 서버는 결과를 찾을 때 까지 주기적으로 redis를 탐색해야하는 부담이 생겼습니다.
프로그래머스, 구름 등 기존 코드 실행기능을 제공하고 있는 서비스들을 벤치마크하여 코드 실행 기능을 발전시켰습니다.

완성된 코드 요청방식은 소켓, MQ, Pub/Sub 구조를 활용합니다.
클라이언트는 소켓을 연결하고 코드 실행 요청을 보냅니다.
api 서버는 해당 요청을 메세지 큐에 쌓고 running 서버에서는 실행이 완료되면 결과를 publish 합니다. 다시 api 서버는 구독 메시지를 받고 소켓으로 응답 메세지를 전달해주어 연결을 해제합니다.
- 서버에서 코드 실행이 완성된 시점에 실행 결과를 클라이언트에 보내주어야하기 때문에 양방향 통신이 가능한 소켓으로 연결했습니다.
- 실행이 요청된 코드들을 요청이 들어온 순서대로 처리해주어야하므로 메세지 큐를 이용해 running 서버에 전달했습니다.
- 하지만 코드 실행이 끝나는 시점은 순서를 보장할 수 없고 먼저끝나는 것이 먼저 응답되어야 한다고 생각하여 Pub/Sub구조를 활용하여 코드 실행결과를 보여주었습니다.
테스트 환경
코드 실행 기능을 완성하고 각 버전별로 어떤 차이가 있는지 실제 성능을 테스트해보며 차이를 확인해보고 싶어 부하테스트를 도입하였습니다.
- api 서버 :
[Compact] 2vCPU, 2GB Mem [g1]
- running 서버 :
[Standard] 8vCPU, 8GB Mem [g1]
pm2로 코어수만큼 프로세스 작동
- redis 서버 :
[Compact] 2vCPU, 2GB Mem [g1]
테스트 툴

Node 환경에서 대중적인 부하테스트 툴로 socket.io를 지원하고 실행 결과를 html로 쉽게 변환이 가능하여 Artillery.IO로 부하테스트를 진행했습니다.
서버 자원 모니터링 : htop
서버 자원모니터링을 htop을 이용해서 진행했습니다.

NCP에서 상세 모니터링을 제공하지만,
서버 응답이 느린 상황에서는 모니터링 정보가 전달되지 않아 정확히 볼 수 없어서 직접 서버에서 모니터링을 진행하였습니다.
테스트 조건
모든 시나리오에서 같은 언어, 코드로 진행했습니다.
언어 Python
실행 코드
cnt=0
for i in range(10):
cnt+=1
시나리오별 결과
→ 180초동안 가상사용자수를 500까지 점차적으로 초당 요청수를 늘려 어느정도의 요청까지 처리할 수 있는지 파악하는 테스트
V1 | V2 | V3 | |
vusers.failed | 9836 (21.4%) | 44268(96%) | 3 (0%) |
최대 cpu 사용률 | 100% | 83.5% | 73% |
평균 응답시간 | 106.7 ms | 620.3 ms | 125.2 ms |
V2는 메모리 사용률이 급격히 올라가는 것으로 보아 응답을 기다리는 로직에 문제가 있는 것으로 판단하여 다른 시나리오에서 제외
시간은 오래걸리지만 V3버전에서 더 높은 성공률과 더 낮은 cpu 사용률을 보임.
→ 180초동안 가상사용자(vusers)수를 300까지 점차적으로 초당 요청수를 늘려 어느정도의 요청까지 처리할 수 있는지 파악하는 테스트
V1 | V3 | |
vusers.failed | 3 (0%) | 28 (0%) |
최대 cpu 사용률 | 80.1% | 47.7% |
평균 응답시간 | 34.8 ms | 32.1ms |
응답 성공률, 평균 응답 시간은 비슷했으나 더 낮은 cpu 사용률을 보임.
ramp up 300 (180s) - sustain 600s
→ 180초동안 가상사용자(vusers)수를 300까지 점차적으로 늘리고, 600초간 동일한 수의 요청을 지속적으로 보내 서버의 동작을 확인하는 테스트
V1 | V3 | |
vusers.failed | 12 (0%) | 660 (0.003%) |
평균 cpu 사용률 | 70%대 | 40%대 |
최대 cpu 사용률 | 92% | 60%대 |
최대 메모리 사용량 | 1.3GB/1.92GB | 900MB/1.92GB |
평균 응답시간 | 85.6 ms | 32.1ms |
지속적인 부하가 있을 때 V3에서 약간 실패율이 올라갔지만 실패율이 1%가 되지 않음
평균 응답시간, 메모리 사용량, cpu 사용률에서 유의미한 차이를 보임
-> cpu 사용률 43% , memory 사용률 21% 감소
결론
이렇게 여러가지 시행착오를 겪으며 코드 실행 기능을 발전시켰습니다.
다른 서비스들을 참고하며 소켓, pub/sub, 메세지 큐를 도입하였고, 서버를 더 안정적으로 운영할 수 있게 기능을 구현할 수 있었습니다.
지속적인 부하가 있는 상황에서 cpu 사용률과 메모리 사용률에서 큰 감소를 볼 수 있었고(cpu 사용률 43% , memory 사용률 21% 감소), 실제 부하테스트 중에도 다른 api의 장애없이 이용할 수 있는 것을 확인할 수 있었습니다.
부하테스트를 진행하며 개발시 보이지 않았던 문제들을 발견하며 문제가 발생할 수 있는 코드들을 찾아 해결하며 코드를 개선할 수 있었습니다.
특히 메모리 누수가 발생하는 지점을 찾아 해결했던 것이 가장 기억에 남습니다. (메모리누수 잡기)
부하테스트를 진행하며 그 차이를 더 명확하게 비교해보는 경험을 할 수 있었습니다.
개발기
[코드실행기능 개발기 #1] 작업이 오래걸리는 요청을 어떻게 응답할까?
프로젝트에서 코드 실행 요청을 보내는 기능을 구현하고 있다. 클라이언트가 코드 실행 요청을 보내면 서버에서 그 코드를 실행해 결과를 알려주는 형태이다. 실행을 요청한 코드가 실행에 오
stop-thinking-start-now.tistory.com
[코드실행기능 개발기 #2] 작업이 오래걸리는 요청을 어떻게 응답할까?
[코드실행기능 개발기 #1] 작업이 오래걸리는 요청을 어떻게 응답할까? 프로젝트에서 코드 실행 요청을 보내는 기능을 구현하고 있다. 클라이언트가 코드 실행 요청을 보내면 서버에서 그 코드
stop-thinking-start-now.tistory.com
'Projects' 카테고리의 다른 글
게시판 전체 글 랭킹 점수 데이터 업데이트 속도 77.72% 개선 (bulk update vs batch update) (1) | 2024.01.23 |
---|---|
Querydsl을 이용한 커서 기반 페이지네이션 구현기 (0) | 2024.01.20 |
NestJS에 winston으로 로그 남기기 (0) | 2023.12.17 |
refresh token 도입기 (1) | 2023.12.17 |
OAuth 2.0은 무엇이고 어떻게 적용할 수 있을까? (1) | 2023.12.17 |