들어가며
해당 블로그는 솝트 메이커스에서 운영하는 인프라 환경에 대한 이야기이다. 메이커스 36기, 37기부터 그리고 현재 38기까지 이어져온 서버 통합에 대한 작업을 다룬다 (해당 블로그는 솝트 메이커스 37기 재헌이와 함께 작성했습니다)
솝트 메이커스란 ?
솝트 메이커스의 각 제품 팀은 PM, 프로덕트 디자이너, 개발자가 모여 하나의 독립적인 팀으로 일하고, 3천여명의 구성원을 연결하고 새로운 가치를 제공하기 위한 서비스를 제공해주는 SOPT 동아리의 정식 기구이다
https://makers.sopt.org/
SOPT makers
SOPT에 필요한 프로덕트를 만들어 3천여 명의 구성원들을 연결하고 새로운 가치를 제공하기 위한 SOPT 정식 기구예요. 수년간 방치되어 SOPT를 잘 드러내지 못하는 공식 홈페이지를 보고 홈페이지
makers.sopt.org
현재는 4개의 제품 팀 (크루, 앱, 플그, 공홈) 과 디자인 플랫폼 팀 으로 이루어져있지만, 해당 글은 총 6개의 팀 CX팀 플랫폼 팀과 크루팀, 플레이 그라운드팀, 앱팀, 어드민팀으로 구성되어 있을때부터 시작된 작업임을 참고해주었으면 좋겠다

서버 통합을 고려하게 된 배경
다시 본론으로 돌아와서, 36기때부터 서버 통합 작업을 고려하게된 배경부터 설명 해보려고 한다
1. 오버 엔지니어링
솝트 메이커스의 서비스들은 SOPT 동아리 특성상 “솝커톤”, “스터디 개설”, “정기 행사”와 같은 특정 시기에 트래픽이 집중되고, 그 외 기간에는 트래픽이 상대적으로 낮은 편이다. 따라서 항상 높은 트래픽을 감당하기 위한 구조를 유지하기 보다 스파이크 트래픽에 대응할 있는 서버 설계를 해야한다고 생각했었고, 일부분은 오버엔지니어링에 가까운 상태라고 판단했었다

2. 과도한 서버 비용
이중에서도 서버 통합을 고민하게 된 가장 큰 이유는 서버비 절감이였다. 팀이 늘어날수록 각 팀이 별도의 인스턴스 (12개), 별도의 reverse proxy, 별도의 인증서/도메인 세팅을 들고 가는 구조는 운영 측면에서도 복잡도가 높고 비용 측면에서도 비효율적이였다

또한 인스턴스를 팀별로 분리해서 운영하다 보니, 각 인스턴스마다 발생하는 Public IP 비용도 무시할 수 없는 수준이였고 이것도 통합을 통해 줄일 수 있는 요소 중 하나였다
3. 관리 포인트 증가
또 하나의 이유는 관리 포인트 증가였다. 배포 방식이 팀마다 다르고, 이슈 발생시에 확인해야 할 지점이 많아아서 병목이 심해졌었고, 각 팀이 서로 다른 배포 스크립트와 프록시 구성을 사용하다 보니 배포 스크립트, 프록시 구성, 외부 라이브러리 및 통신 방식 등이 팀마다 달라지게 되었다
특히 솝트 메이커스는 기수제로 운영되기 때문에, 인수인계 과정에서도 이러한 문제는 더 크게 드러났다. 새로운 인원이 다른 팀 인프라의 세부적인 구조를 이해하기 어렵고, 각 팀의 설정을 개별적으로 파악해야 하기 때문에 다른 팀의 인프라 구조까지 신경 쓰기 힘든 구조가 되어버렸다. 결과적으로 스스로 운영 관리 포인트를 계속 늘리게 되는 방향이었다고 판단했다
+ 추후에 뒷부분에서 서술할 내용이지만, 서버 통합 이후에는 앞서 얘기한 부분들 중에서 예상하지 못했던 부분에서 장점이 있었다. 구체적으로는 컨테이너가 한 곳에 모이면서 로그 확인도 훨씬 편했고, 이슈 발생 시 다른 팀의 흐름까지 함께 파악할 수 있어 트러블슈팅 속도가 자연스럽게 빨라질 수 있었다
서버 통합으로 기대한 변화
이제 서버 통합에 대한 내용을 결정한 이후, 우리가 기대한 서비스의 변화에 대해서 설명해보려고 한다. 기존과 비교했을때 여러 서비스를 하나의 서버에 배치하는 것만으로는 충분하지 않았고, 각 프로덕트의 특성을 유지하면서도 안정적으로 운영할 수 있는 구조가 필요했다
기존에는 팀별로 개별 인스턴스를 사용하고 있었기 때문에, 서비스 간 영향을 최소화할 수 있다는 장점은 있었다. 하지만 현재 메이커스 서비스들의 트래픽 규모를 고려했을 때, 대부분의 인스턴스는 여유 자원을 남긴 채 운영되고 있었다. 실제로 사용할 수 있는 리소스는 충분히 남아 있었지만, 인프라가 팀별로 분리되어 있었기 때문에 자원을 효율적으로 활용하기 어려웠고, 그만큼 비용도 계속 발생하고 있었다
당시 인스턴스 구성은 small * 3 + micro * 5 형태였고, 이를 시간당 비용으로 환산하면 약 0.1147 수준이었다. 평상시 솝트 메이커스 서비스의 트래픽을 고려했을 때 해당 구성은 다소 과하다고 판단했다
결론적으로 서버 통합을 통해 기대한 변화는 단순한 비용 절감만은 아니었다
여러 팀의 서비스를 하나의 통합 인프라 안에서 운영하면서 낮은 리소스 활용도를 개선하고, 중복된 인프라 비용을 줄이며, 동시에 운영 관리 포인트까지 줄이는 것이 핵심 목표였다
서버 통합시 고려 사항
이처럼 서버 통합을 통해 얻을 수 있는 기대 효과는 확실했지만, 실제 운영 환경에서도 안정적으로 동작할 수 있는지에 대한 별도의 검증의 검증이 필요하다고 생각하게 되었다
1. 부하 테스트의 필요성
앞서 다룬 대로 우리가 선택한 방향은 물리적으로는 하나의 서버를 사용하되, 각 서비스는 컨테이너 단위로 분리해서 운영하는 구조였다. 좀 더 자세하게는 하나의 인스턴스 안에서 여러 프로덕트가 함께 실행되지만, 애플리케이션 자체는 각각 독립된 컨테이너로 관리되는 방식으로 설계하였다
해당 구조는 완전한 물리적 분리는 아니지만, 각 서비스를 컨테이너 단위로 나누어 운영할 수 있기 때문에 기존 구조보다 자원을 효율적으로 사용할 수 있다고 판단했었고, 팀별 서비스의 실행 환경을 어느 정도 분리할 수 있다는 점에서도 현실적인 선택지였다
하지만 여러 서비스가 하나의 서버에서 동시에 동작하게 되면 자원 경합에 대한 고려가 반드시 필요했다. 특히 Spring 기반 애플리케이션은 기본적으로 JVM 메모리 사용량이 적지 않고, 특정 서비스에 순간적으로 트래픽이 몰릴 경우 CPU나 메모리 사용량이 증가하면서 같은 인스턴스 위에 있는 다른 서비스에도 영향을 줄 수 있었기에, 실제 장애로 이어질 수 있는 가능성을 고려 하면서 사전에 확인이 필요했다
따라서 실제 요청이 들어오는 상황에서 메모리 사용량, CPU 사용량, 응답 시간, 헬스체크 안정성 등을 함께 확인해야 했다. 그래서 통합 구조가 실제 장애로 이어질 가능성은 없는지 사전에 검증하기 위해 부하 테스트가 필요했다
2. 인스턴스 선택
서버 통합을 진행하면서 어떤 수준의 인스턴스를 사용할지도 중요한 결정 요소였다. 더 높은 스펙의 인스턴스를 선택하면 안정성은 확보할 수 있지만, 서버 통합의 주된 목적이었던 비용 절감 효과는 줄어들 수밖에 없다. 반대로 작은 스펙의 인스턴스를 선택하면 비용 효율성은 높아지지만, 실제 운영 트래픽을 감당할 수 있는지 검증이 필요했다.
서버 통합의 목적이 비용 절감에 있었던 만큼, 가능한 한 작은 스펙의 인스턴스를 기준으로 검토했고, 기준을 t3.small로 삼았다

3. dev 환경에서의 인스턴스 스펙 검증
앞서 정한 인스턴스 스펙을 바로 prod 환경에 적용하기에는 실제 사용자가 있는 환경이기 때문에, 비용 절감만을 이유로 안정성이 검증되지 않은 구조를 바로 적용할 수는 없었다. 그래서 먼저 dev 환경에서 동일한 통합 구조를 구성하고 실제 운영을 통해 안정성을 확인하는 방식을 선택했다
좀 더 구체적으론 dev 환경에서 통합된 서버 구조를 기반으로 t3.small 인스턴스를 사용해 두 기수 동안 서비스를 운영했다. 짧은 시간 동안 부하 테스트 용으로 진행한 것은 아니고, 실제 사용자들이 사용하는 환경에서 유의미한 성능 저하나 장애 없이 운영이 가능하다는 점을 확인할 수 있었다
이러한 검증 결과를 바탕으로, 현재 메이커스 서비스들의 트래픽 규모에서는 t3.small 인스턴스에서도 충분히 서비스를 감당할 수 있다고 판단하였고, prod 환경에서도 유사한 수준의 인스턴스 구성으로 운영할 수 있는 근거를 확보할 수 있었다
결과적으로 서버 통합 이후에는 기존의 small * 3 + micro * 5 구조를 small * 2 + micro * 1 수준으로 단순화할 수 있었다. 이를 통해 기존 대비 약 55% 수준의 인스턴스 비용 절감을 기대할 수 있었다. 추가적으로 dev 환경을 서버리스 구조로 전환할 경우, 최대 약 70% 이상까지 비용을 줄일 수 있는 가능성도 확인했다
서버 통합 플로우
dev 환경에서의 검증을 통해 실제 환경의 운영 근거를 확보한 이후 마침 AWS 세이빙 플랜 갱신 시점도 다가오고 있었기 때문에, 이 시점에 인프라 구조를 함께 정리한다면 향후 1년간의 서버 비용을 의미 있게 줄일 수 있을 것이라고 판단했다
이미 dev 환경에서 검증한 통합 구조를 prod 환경으로 확장하는 작업으로 바라보고 서버 통합 태스크를 진행하긴 했었지만, dev 환경과 다르게 prod는 실제 사용자 트래픽이 있는 환경이기 때문에 dev 환경처럼 바로 구조를 변경할 수는 없었다
기존 서비스에 영향을 주지 않으면서 서버를 이전해야 했고, 아래처럼 순차적인 태스크를 설정하였다
1. Dev 서버 Lambda 전환
2. Prod 서버 통합
3. SSL 인증서 마이그레이션
4. NGINX 구조 설계
5. 로컬 hosts 기반 사전 검증
6. CI/CD 파이프라인 업데이트 및 Route53 전환
1. Dev 서버 Lambda 전환
첫 번째로 진행한 작업은 DEV 환경의 Lambda 전환이었다.
AWS Lambda란 ?
서버를 직접 프로비저닝하거나 관리하지 않고 코드를 실행할 수 있는 서버리스 컴퓨팅 서비스이다.
AWS 공식 : “서버나 클러스터를 신경 쓰지 않고 코드를 실행할 수 있는 서비스”, 사용한 컴퓨팅 시간에 대해서만 비용을 지불하는 구조
기존에는 DEV 환경에서도 EC2 인스턴스를 계속 실행하는 구조였지만, DEV 환경은 실제 운영 트래픽이 상시 발생하는 환경은 아니었기 때문에, 필요한 시점에만 실행되는 Lambda 구조가 더 적합하다고 판단했다
이를 통해 DEV 인프라 자원을 더 효율적으로 사용할 수 있었고, 상시 실행되는 인스턴스를 줄이면서 불필요한 비용도 함께 최적화할 수 있었다
물론 Lambda 환경에서는 cold start가 단점으로 언급될 수 있지만, DEV 환경에서는 응답 속도보다 비용 효율성과 운영 유연성이 더 중요했기 때문에 충분히 합리적인 선택이라고 판단했다
ps. 추가로 이 내용을 아젠다하고 TF를 운영하면서 적극적으로 작업해주신 찬기형님께 감사드립니다 🙇🏻♂️

2. Prod 서버 통합
두 번째로는 PROD 환경의 인스턴스 통합을 진행했다.
기존에는 팀별로 인스턴스를 분리하여 운영하고 있었지만, 이를 하나의 통합 인스턴스에서 각 팀의 서비스를 컨테이너 단위로 실행하는 구조로 변경했다. 해당 과정에서 팀별 운영 환경의 일치도 진행 하였다. 좀 더 구체적으로는 Playground팀은 기존에 Amazon Linux 기반 인스턴스 이미지를 사용하고 있었지만, 다른 팀들은 주로 Ubuntu 기반 인스턴스를 사용하고 있어 운영 환경을 맞추는 작업을 진행해주었다
3. SSL 인증서 마이그레이션
서버를 이전하더라도 HTTPS 연결은 중단 없이 유지되어야 했다. 이를 위해 기존 서버에서 발급받아 사용 중이던 Let's Encrypt 인증서를 새 통합 인스턴스로 직접 이전했다. Let's Encrypt 인증서는 /etc/letsencrypt 내부에서 심볼릭 링크 구조로 관리되기 때문에, 단순히 일부 인증서 파일만 복사하는 방식으로는 정상적으로 동작하지 않을 수 있다. 따라서 /etc/letsencrypt 디렉토리 전체를 tar로 압축하여 심볼릭 링크 구조를 유지한 상태로 이전하는 방식을 사용했다. 따라서 새 서버에서도 기존과 동일한 인증서 경로를 사용할 수 있었고, 이후 NGINX 설정에서도 HTTPS 구성을 안정적으로 이어갈 수 있었다.
https://letsencrypt.org/ko/docs/
문서
Let's Encrypt는 비영리 단체 인터넷 보안 연구 그룹(ISRG)이 제공하는 무료, 자동화된 개방형 인증 기관입니다. 올해 비영리 단체의 활동에 대한 자세한 내용은 2025 연례 보고서에서 확인하세요.
letsencrypt.org
4. NGINX 구조 설계
기존에는 팀별로 인스턴스가 분리되어 있었기 때문에 각 팀이 자신만의 NGINX 또는 프록시 구성을 독립적으로 관리할 수 있었다.
하지만 통합 이후에는 여러 팀의 서비스를 하나의 서버에서 운영해야 했고, 따라서 하나의 NGINX가 여러 팀의 도메인과 포트를 함께 처리해야 했다.
이를 위해 팀별로 NGINX 설정 파일을 분리했다
각 팀은 자신의 도메인 라우팅을 담당하는 .conf 파일을 가지고, 실제 연결될 서비스 포트는 별도의 .inc 파일에서 관리하도록 구성했다.
이렇게 구성하면 특정 팀의 배포나 포트 변경이 다른 팀의 NGINX 설정에 직접적인 영향을 주지 않도록 만들 수 있다.
또한 blue-green 배포 시에도 .inc 파일의 upstream 대상만 바꾸고 NGINX를 reload하면 되기 때문에, 통합 서버 안에서도 팀별 배포 흐름을 비교적 독립적으로 유지할 수 있었다
5. 로컬 hosts 기반 사전 검증
NGINX 설정과 인증서 이전이 완료되었다고 해서 바로 DNS를 전환할 수는 없었다. 만약 Route53의 A 레코드를 바로 변경했다가 문제가 발생하면, 실제 사용자 곧바로 영향을 받기 때문에, 먼저 로컬 PC의 hosts 파일을 수정해, 특정 도메인이 새 통합 서버 IP를 바라보도록 설정했다
이렇게 하면 실제 사용자들은 여전히 기존 서버를 사용하지만, 테스트하는 로컬 환경에서는 동일한 도메인으로 새 서버에 접근할 수 있다
이 방식으로 SSL 인증서가 정상적으로 동작하는지, NGINX 라우팅이 의도한 포트로 연결되는지, 각 서비스의 health check가 정상적으로 응답하는지를 실제 도메인 기준으로 미리 검증할 수 있었다
즉, 실제 트래픽을 전환하기 전에 로컬 환경에서만 새 서버를 바라보게 만들어 안전하게 사전 검증을 진행해주었다
6. CI/CD 파이프라인 업데이트 및 Route53 전환
사전 검증이 완료된 이후에는 각 팀의 배포 스크립트와 CI/CD 설정을 통합 서버 구조에 맞게 수정했다.
기존에는 팀별 인스턴스를 대상으로 배포하던 방식이었다면, 이제는 통합 서버 안에서 각 팀이 할당받은 포트와 컨테이너 이름, NGINX 설정 파일을 기준으로 배포가 이루어져야 했다
배포 스크립트에서는 새 컨테이너를 띄우고, 로컬 포트 기준으로 health check를 수행한 뒤, 성공하면 NGINX의 .inc 파일을 갱신하고 reload하는 흐름으로 정리했다
이후 최종적으로 Route53의 A 레코드를 새 통합 서버 IP로 변경하면서 실제 트래픽을 전환했다
사전에 hosts 기반 검증을 진행한 덕분에 DNS 전환 이후에도 별다른 문제 없이 안정적으로 작업을 마무리할 수 있었다
부하 테스트
위 과정의 서버 통합은 단순히 서버 수를 줄이는 작업이 아니었다. 실제 트래픽 환경에서 감당 가능한 수준을 먼저 검증하고, 그 결과를 바탕으로 인프라를 다시 설계하는 과정 이후에는 다양한 부하 상황에서도 안정적으로 동작하는지 확인하기 위해, 부하 테스트를 통해 추가 검증을 진행했다
고려 사항
서버 통합 이후에는 Playground, 플랫폼, 앱, 크루, 인증 서버 등 여러 컨테이너가 하나의 서버 위에서 함께 동작하긴하지만, 각 서비스가 받는 트래픽의 성격은 모두 다르다. 때문에 각 팀의 트래픽의 특성을 잘 살린 테스트가 중요하다고 생각했다.
예를 들어 앱팀은 기수 활동 인원이 동시에 앱을 켜는 상황처럼 지속적인 고정 TPS를 버틸 수 있는지가 중요했고, 크루팀은 솝커톤이나 모임 신청처럼 특정 시점에 요청이 몰리는 스파이크성 트래픽이 중요했다. 공홈은 방문자 증가, 메인 페이지 조회, 프로젝트/후기 조회처럼 실제 사용자의 탐색 흐름을 반영해야 했고, Playground는 커뮤니티 게시글 목록처럼 요청 하나당 DB 조회와 응답 조립 비용이 큰 API를 함께 고려해야 했다.
따라서 모든 팀에 동일한 부하 테스트 방식을 적용하는 것은 적절하지 않다고 판단했다. 각 팀은 자신의 도메인 특성에 맞춰 테스트 시나리오를 구성했고, 서버 통합 이후에도 현재 트래픽을 충분히 감당할 수 있는지 확인하는 방향으로 검증을 진행했다.
앱팀 : 동시 앱 실행 기준, 200 TPS 기준 부하 테스트
앱의 핵심 사용자는 매 기수 활동하는 파트원과 임원진을 합쳐 약 200명 내외이다. 물론 명예 OB 분들도 사용하시지만, 가장 트래픽이 몰리는 시나리오는 활동 인원 전원이 동시에 앱을 켜는 피크 타임이라고 판단하여 200 TPS를 정상적으로 처리하는 것을 목표로 잡고 부하테스트를 진행하였다.
테스트 시나리오

테스트 시나리오는 실제 사용자가 앱에 진입한 뒤 자연스럽게 수행하는 행동 흐름을 기준으로 구성했다.
앱에 진입한 후 자연스럽게 랭킹을 확인하고, 미션 목록을 훑어보고, 스탬프를 찍거나 박수를 치는 동선을 따라가면서, 각 행동의 빈도에 따라 TPS 가중치를 다르게 배분했다
읽기와 쓰기 비율은 대략 9:1 로 두었는데, 랭킹 조회나 미션 확인처럼 데이터를 조회하는 행동이 대부분이고, 박수 치기나 스탬프 작성처럼 데이터를 변경하는 행동은 상대적으로 적게 발생하기 때문이다
테스트 결과

결론부터 말하면, 시스템은 무너지지 않았다
가장 많은 트래픽을 받은 개인 랭킹 조회는 p95 기준 약 57.36ms, 파트별 랭킹 조회는 약 21.29ms 수준으로 응답했다. 미션 목록 조회는 약 25.43ms, 파트 내 유저 랭킹은 약 57.77ms 수준이었고, 유저 정보 조회와 미션 상세 조회도 30ms 안팎에서 처리됐다

쓰기 작업인 박수 치기와 스탬프 등록/수정 역시 목표 응답 범위 안에서 처리됐고, 전체 p95 응답시간은 약 57.70ms 수준이었다.
다만 리소스 관점에서는 여유가 아주 많다고 보기는 어려웠다. 피크 타임에 Load Average가 최대 3.63까지 올라갔는데, 이는 2코어 기준으로 보면 CPU 큐잉이 발생한 상태라고 볼 수 있다. 그럼에도 응답시간 임계치를 초과한 요청은 없었기 때문에, “여유롭게 남았다”기보다는 아슬아슬하지만 안정적으로 버틴 상태에 가까웠다
이후 앱팀은 단순히 목표 TPS를 통과했는지에 그치지 않고, 서버가 어느 지점에서 한계에 도달하는지도 확인했다. 100 TPS 워밍업 이후 2분마다 100 TPS씩 증가시키는 스트레스 테스트를 진행했고, 실패율이 일정 기준을 넘으면 graceful하게 종료되도록 스크립트를 구성했다

그 결과 300 TPS 구간까지는 안정적으로 버텼지만, 400 TPS로 넘어가는 시점부터 CPU 부족과 응답 지연이 발생하기 시작했다. 400~500 TPS 구간에서는 그래프가 끊길 정도로 서버 상태가 좋지 않았다
추가로 일부 API에서는 1초 이상의 지연이 발생했었고, 결론적으로 앱팀의 포화점은 300 TPS 구간으로 해석할 수 있었다. 처음 목표였던 200 TPS의 1.5배 수준까지도 안정적으로 버틴 것을 확인하면서, 현재 통합 서버 구조에서 앱팀 서비스는 충분히 운영 가능하다고 판단했다
크루팀 : 스파이크성 트래픽과 신청 API 병목 확인
크루팀은 다른 팀과 트래픽 성격이 조금 달랐다. 크루 서비스는 다른 팀과 비교 하였을때 평소에는 트래픽이 높지 않지만, 모임 신청이나 솝커톤, 네트워킹 행사처럼 특정 시점에 요청이 몰리는 경우가 많았다. 따라서 크루팀의 부하 테스트는 평균 TPS보다 순간적으로 몰리는 요청을 어떻게 버티는지를 확인하는 데 초점을 두었다
테스트 시나리오

따라서 크루팀의 부하 테스트는 평균 TPS를 오래 유지하는 것보다, 짧은 시간에 몰리는 요청을 통합 서버가 어떻게 처리하는지를 확인하는 데 초점을 두었다. 그중에서도 모임 신청 API는 단순 조회 API과 달리 동일 유저의 중복 신청을 막기 위한 Lock 처리와 DB write가 포함되어 있다. 그래서 요청량이 많아졌을 때 단순히 CPU나 메모리를 포함하여, Lock 대기, 응답 지연, timeout 발생 여부를 함께 확인해주었다
테스트 결과
결론부터 말하면, 정상적인 사용자 분산 시나리오에서는 시스템이 완전히 무너지지는 않았다.
다만 크루 신청 API는 조회 API와 달리 DB write와 Lock 처리가 포함되어 있어, 부하가 커질수록 응답시간이 빠르게 증가하는 모습을 보였다. 1차 테스트에서는 최대 VU 250 수준까지 부하를 올렸고, 이때 HTTP request rate는 약 13.3 req/s 수준으로 관측됐다.http_req_failed는 0/s로, 요청 자체가 실패하지는 않았다.

하지만 응답시간이 이미 꽤 높았다. 평균 응답시간은 약 15초, p95는 약 21초 수준까지 증가했다
결론적으로 서버가 요청을 처리하지 못하고 바로 죽은 것은 아니지만, 사용자 경험 관점은 이미 매우 떨어질 수도 있다는 생각도 들었다

이후 더 높은 부하에서는 request rate가 약 47.8 req/s 수준까지 올라갔지만, 이 구간부터는 실패 요청이 발생하기 시작했다.
응답시간도 18초 이상으로 유지되었고, 일부 요청은 timeout에 가까운 지연을 보였다. 즉, 응답시간을 중점적으로 보면 크루팀의 신청 API는 분산 사용자 시나리오에서는 서버가 완전히 다운되지는 않았지만, p95가 20초 이상으로 증가했다
추가적으로 DB Lock을 의도적으로 제거한 다음에 진행한 동일 유저 Lock 테스트에서는 상황이 더 명확했다

이 결과를 보면 크루팀의 병목은 단순히 서버 스펙 부족이라기보다, 동일 유저 신청을 직렬화하는 Lock 구조와 신청 처리 로직의 대기 시간에서 발생한다고 볼 수 있었다 (이 과정에서 앞선 요청이 끝날 때까지 뒤의 요청들이 계속 대기하게 되고, 대기 시간이 누적되면서 결국 많은 요청이 60초 timeout에 도달했다)
플그팀: 비용이 큰 조회 API와 DB 커넥션 풀 병목 확인
플그팀은 다른 팀의 단순 조회 API와 비교했을 때 요청 하나당 처리 비용이 상대적으로 큰 편이었다.
이번 테스트 대상에는 커뮤니티 홈 인기글 조회, 최근 솝티클 조회, 전체 최근글 조회, 커뮤니티 카테고리별 글 목록 조회, 멤버 프로필 전체 조회 등이 포함되어 있었다
플그팀의 community/posts API는 게시글 목록을 가져온 뒤에도 댓글 목록, 익명 프로필, 좋아요 여부, 좋아요 수 등 여러 정보를 함께 조합해야 했기 때문에 요청당 DB 접근 횟수와 응답 조립 비용이 상대적으로 컸다
따라서 플그팀 부하 테스트는 단순히 높은 TPS를 목표로 잡기보다는,
현재 구조에서 어느 구간부터 응답시간이 증가하고, 어떤 리소스가 먼저 병목이 되는지 확인하는 방향으로 진행했다
테스트 시나리오

처음에는 웜업 이후 20 TPS, 30 TPS, 50 TPS 수준으로 테스트를 진행했다
하지만 목표 TPS보다 실제 처리량이 낮게 나오고, 응답시간이 급격히 증가하는 현상이 관찰됐다
이후 원인을 더 정확히 확인하기 위해 10 / 15 / 20 / 25 / 30 TPS로 테스트 구간을 세분화했다
테스트 결과
- jdbc.connections.max = 10
- jdbc.connections.active = 10
- jdbc.connections.idle = 0
- hikaricp.connections.pending = 67 ~ 113
Hikari connection pool (DB Connection Pool) 관측 결과, 최대치인 10개가 모두 사용 중인 상태였고, idle connection은 0개로 이후 요청들은 DB connection을 얻지 못하고 pending queue에서 대기하고 있었다.
결론적으로 플그팀은 조회 API 이후 게시글 목록 이후 댓글, 익명 프로필, 좋아요 여부, 좋아요 수 등 여러 데이터를 조합하는 과정에서 TPS가 25 이상, 전체 처리량은 약 24~25 req/s 수준에서 포화되었다
공홈팀: 사용자 행동 시뮬레이션과 TPS 고정 테스트
공홈팀은 다른 팀과 비교했을 때 외부 사용자가 가장 먼저 접하는 진입점이라는 특성이 있었다. 사용자는 보통 메인 페이지에 진입한 뒤, 활동 후기나 프로젝트, 솝트스토리, 모집 페이지 등을 둘러본다. 따라서 실제 사용자가 공홈에 방문했을 때의 흐름을 최대한 반영하는 것이 중요하게 생각하며 테스트를 진행하였다
첫 번째는 실제 사용자 행동을 시뮬레이션하는 VU 기반 테스트,
두 번째는 초당 요청 수를 고정해 서버의 한계 TPS를 확인하는 테스트
크게 두 방향으로 나누어 진행했다
테스트 시나리오


테스트 결과 1
VU 기반 테스트는 실제 사용자의 방문 흐름을 반영하여, 한 명의 사용자는 대략 아래와 같은 순서로 API를 호출한다
POST /visitor
GET /homepage
GET /reviews/random
GET /projects | /reviews | /soptstory | /homepage/recruit 중 랜덤 1개
+ 페이지를 읽는 시간 sleep

- 총 52,512개 요청 처리, 실패 0건
- 동시 접속자가 50명으로 급증하는 상황에서도 P95 응답시간 53ms로 안정적
- 한계점 미발견 (스크립트 상한(50명) 내에서 서버가 전혀 한계를 보이지 않음)
테스트 결과 2
두 번째로, 초당 요청 수를 강제로 고정하는 TPS 테스트를 진행하여 실제 트래픽 비율을 반영해 7개 API에 가중치를 부여했다
GET /homepage (20%), POST /visitor (15%), GET /homepage/recruit (15%),
GET /reviews (15%), GET /reviews/random (15%),
GET /projects (10%), GET /soptstory (10%)

100 TPS에서 150 TPS로 올라갔을 때는 p95 응답시간이 54ms → 55ms 수준으로 거의 변하지 않았지만, 200 TPS에서는 p95가 108ms, p99가 469ms까지 증가했다.
에러율은 동일하게 0%였고, 150 TPS 구간과 비교하면 응답시간 증가폭이 했으며 일부 API에서는 max 응답시간이 2초를 넘기기 시작했다. 즉 200 TPS까지는 안정적으로 처리했지만, 이 구간부터는 한계에 가까워지는 신호가 보였다고 해석할 수 있었다
공홈팀 테스트에서 특히 주의했던 부분은 외부 API 의존성이었다. 주요 API 중 하나인 GET /projects는 내부적으로 다른 팀의 API를 호출한다. Playground 서버 호출의 경우 캐싱 처리가 되어 있어 캐시 히트 시에는 외부 API 호출이 발생하지 않지만, 인증 서버와 크루 서버 호출은 별도 캐싱이 되어 있지 않아 테스트 중 외부 API 호출이 계속 발생할 수 있었다.
따라서 공홈팀은 부하 테스트를 진행할 때 다른 팀의 부하 테스트 시간과 겹치지 않도록 조정하여, 통합 서버에서 함께 동작하는 다른 팀 서비스에 불필요한 부하를 주지 않도록 하였다.
부하 테스트 결론
각 팀의 부하 테스트 결과를 종합해보면, 이번 서버 통합 구조는 현재 메이커스 서비스들의 일반적인 트래픽과 예상 피크 트래픽을 감당할 수 있는 수준이라고 판단할 수 있었다
팀마다 도메인 특성이 달랐고, 병목이 드러나는 지점도 서로 달랐기 때문에, 모든 서비스가 동일한 방식으로 안정적이었다고 보기는 어렵지만 각 서비스가 어떤 방식으로 병목을 만드는지 구체적으로 확인할 수도 있었다
동시에 통합 인프라에서 각 팀의 병목을 더 명확히 관찰하고, 앞으로 서비스별 최적화를 이어가야 한다는 과제도 함께 확인할 수 있었다
마치며
서버 통합 작업으로 인해 힘든 부분들도 많았었지만, 서버 통합 이후에는 small * 2 + micro * 1 수준으로 단순화하면서 인스턴스 비용 기준 약 55% 절감을 기대할 수 있다. 30일 기준으로 단순 환산하면 기존 약 $82.6 수준에서 약 $37.2 수준까지 줄어드는 구조이므로, 월 기준 약 $45 정도의 인스턴스 비용 절감이 가능해진다
여기에 DEV 환경을 Lambda로 전환하는 작업까지 포함하면, 최대 약 70% 이상까지 비용을 줄일 수 있는 가능성도 확인했다. 서버 통합 과정에서 사용하지 않던 CloudWatch 로그 비용이나 과도한 Public IP 비용처럼 평소에는 크게 의식하지 못했던 항목들도 서버 통합 과정에서 함께 절감할 수 있었다
이번 작업으로 모든 인프라 정리가 끝났다고 보기는 어렵다
서버 통합은 비용 절감과 운영 단순화를 위한 첫 단계였고, 앞으로도 남아있는 작업들이 있다
1. 레포 통합
2. 컨테이너별 모니터링 강화
3. 가용성을 높이기 위한 구조 개선 (SPOF 제거, ALB 기반 헬스체크, 레디스 전략 등)
동아리 단위에서 힘든 특별한 경험을 한 것은, 솝트 메이커스이기 때문에 가능했던 작업이라고 생각한다
물론 앞으로 남은 작업들도 많지만, 서비스 규모에 맞는 인프라를 직접 검증하고 다시 설계하는 경험을 얻을 수 있어서 뿌듯하다
