이전 이야기 : https://thepin.tistory.com/210
제9화. 붕괴의 시련
D-Day.
6개월을 매달린 ‘넥솔브 차세대 커머스 플랫폼’ 오픈 날. 상황실의 공기는 팽팽하게 당겨진 활시위 같았다. 에어컨이 돌아가고 있었지만, 사람들의 이마에는 송골송골 땀이 맺혀 있었다.
“오픈 10분 경과. 현재 동시 접속자 30만 명. 특이사항 없습니다.”
모니터링 담당자의 보고에 상황실 곳곳에서 안도의 한숨이 새어 나왔다. 박성우 팀장도 그제야 굳어있던 어깨를 펴며 식은 커피를 한 모금 들이켰다. 나 역시 모니터링 대시보드의 초록색 불빛들을 보며 마른침을 삼켰다.
이대로만 가라. 제발.
하지만 신은 우리의 기도를 들어주지 않았다.
삐이익—!
날카로운 경고음이 고막을 찢고 들어왔다. 평화롭던 대형 모니터의 초록색 그래프들이 일제히 핏빛으로 물들었다. 완만하던 트래픽 곡선이 비명처럼 수직으로 치솟더니, 이내 바닥으로 곤두박질쳤다.
“뭐야? 무슨 일이야!”
“서… 서버 응답이 없습니다! 504 Gateway Time-out 에러가 폭주합니다!”
“DB CPU 100% 찍었습니다! 커넥션 풀(Connection Pool)이 꽉 찼어요! 쿼리가 전혀 안 먹힙니다!”
상황실은 순식간에 아비규환의 지옥으로 변했다. 키보드를 부서져라 두드리는 소리, 의미 없는 고함, 누군가의 절망적인 탄식이 뒤섞였다. 서버가 죽었다. 6개월의 피땀 어린 노력이 단 1분 만에 재가 되어 흩어지고 있었다.
“롤백해! 당장 이전 버전으로 돌려!”
박 팀장이 핏대를 세우며 소리쳤지만, 배포 담당자의 얼굴은 이미 하얗게 질려 있었다. 그는 덜덜 떨리는 손으로 마우스를 잡고 있었지만, 커서는 허공을 맴돌 뿐이었다.
“그게… DB 스키마가 변경돼서 롤백하려면 데이터 마이그레이션을 다시 해야 합니다. 최소 2시간은 걸립니다….”
“2시간?”
박 팀장의 목소리가 갈라졌다. 그는 다리에 힘이 풀린 듯 의자에 주저앉으며 머리를 감싸 쥐었다.
“망했군….”
그의 중얼거림이 상황실의 소음을 잠재웠다. 그것은 항복 선언이었다. 팀원들의 눈동자에는 공포가 서려 있었다. 프로젝트의 실패, 쏟아질 고객의 비난, 회사의 문책, 그리고 잘려 나갈 자신의 미래. 그 무거운 절망감이 납덩이처럼 어깨를 짓눌러, 누구도 감히 손을 쓰지 못하고 있었다.
그 순간, 내 머릿속에서 잊고 싶었던 기억 하나가 섬광처럼 스쳐 지나갔다.
3년 전, 인턴으로 일했던 스타트업 ‘데이터웨이브’.
그때도 똑같았다. 붉은색 경고등, 멈춰버린 화면, 그리고 대표님의 울음 섞인 고함. 나는 구석에 서서 아무것도 할 수 없었다. 내 눈으로 사람들의 기억을 지워 공포를 없앨 순 있었지만, 죽어버린 서버를 살릴 순 없었다.
하지만 이상했다.
으레 이쯤 되면 울려야 할 임원실의 독촉 전화가 없었다. 상황실 문을 박차고 들어와 고함을 지르는 사람도 없었다.
대신, 대형 모니터 구석의 사내 메신저 알림창이 반짝였다.
[CS 운영팀장]: "고객 항의는 저희가 온몸으로 막고 있습니다. 욕받이는 우리가 할 테니, 개발팀은 복구에만 집중해주세요. 믿습니다."
[사업본부장]: "지금 가장 힘든 건 자네들일 걸 압니다. 위에서 쪼는 건 내가 다 커버할 테니, 당황하지 말고 해결해주세요."
가슴 한구석이 묵직해졌다. 이곳은 달랐다.
사람을 부품으로 보지 않고, 위기 앞에서 서로를 지켜주는 회사.
결국 망해버린 전 회사. 짐을 싸서 나오던 날, 텅 빈 사무실의 서늘한 공기가 아직도 생생하다.
‘그 무력감. 다시는 겪고 싶지 않아.’
그때, 내가 자리에서 일어났다.
‘눈을 쓸까?’
유혹이 뱀처럼 스멀거렸다. 아주 쉬운 길이었다.
지금 당장 박 팀장의 눈을 보고 ‘서버는 곧 복구된다’는 확신을 심어주면 된다. 배포 담당자에게는 ‘네 잘못이 아니다’라는 기억을, 임원들에게는 ‘이건 천재지변이었다’는 인식을 심으면 그만이다.
내 능력이라면 이 아비규환을 1초 만에 고요한 천국으로 바꿀 수 있다. 공포에 질린 저 눈동자들을 평온하게 만들 수 있다. 심장이 쿵쿵 뛰었다. 보라색 광채가 시신경을 타고 올라오려 했다.
하지만.
‘그건 마약이야.’
사람들의 기억을 조작한다고 해서 죽어버린 서버가 살아나지는 않는다. 3년 전 그때처럼, 가짜 평온함 뒤에는 결국 파국만이 남을 뿐이다. 504 에러는 거짓말을 하지 않는다. 내가 능력을 써서 만든 평화는, 결국 서버가 완전히 셧다운되는 순간 더 끔찍한 비극으로 돌아올 것이다.
‘나는 마법사가 아니야. 나는 엔지니어다.’
현실을 외면하게 만드는 건 구원이 아니라 기만이다. 지금 필요한 건 사람을 홀리는 보라색 눈빛이 아니라, 팩트를 꿰뚫어 보는 차가운 이성이다.
나는 메인 터미널 앞으로 걸어갔다.
“제가 로그 보겠습니다.”
“이한결 대리? 지금 개발팀도 원인을 못 찾고 있는데 자네가 뭘… 비켜! 방해하지 말고!”
박 팀장이 신경질적으로 소리쳤다. 하지만 나는 물러서지 않았다. 나는 대답 대신 개발팀장이 앉아 있던 메인 콘솔의 키보드에 손을 올렸다.
모두의 시선이 내 등 뒤에 칼날처럼 박혔다. ‘네가 뭔데’, ‘QA 주제에 뭘 안다고’ 하는 불신과 짜증이 섞인 시선들. 하지만 그 시선들이 오히려 나를 차분하게 만들었다.
손끝이 차갑게 식었지만, 머릿속은 오히려 얼음처럼 투명하고 명료해졌다. 세상의 소음이 차단되고, 오직 모니터 속의 검은 화면과 나만이 남았다. 나는 눈을 가늘게 떴다. 이번엔 기억을 조작하기 위해서가 아니라, 데이터의 흐름을 읽기 위해서였다.
‘서버가 멈춘 게 아니야. 어딘가 꽉 막혀 있는 거야.’
나는 먼저 웹 서버(Nginx)의 상태부터 확인했다.
top
CPU 사용량은 높지 않았다. 하지만 Load Average가 비정상적으로 높았다. 무언가 처리를 기다리는 프로세스가 쌓여 있다는 뜻이다.
‘네트워크가 막혔나?’
netstat -n | grep :80 | wc -l
25,400
접속 대기 중인 연결이 2만 개가 넘었다. 일반적인 트래픽 증가가 아니었다. 특정 구간에서 병목이 발생해 모든 요청이 줄을 서고 있는 것이다.
“어떤 요청이 문제인지 찾겠습니다.”
나는 실시간 접근 로그를 열었다. 수천 줄씩 올라가는 로그 속에서 패턴을 찾아야 했다.
tail -f /var/log/nginx/access.log
눈으로 쫓기엔 너무 빨랐다. 나는 awk 명령어를 조합해 가장 많이 요청된 URL을 추출했다.
cat /var/log/nginx/access.log | grep "504" | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 5
결과가 모니터에 떴다.
15,200 /api/event/coupon/claim
“찾았습니다. 쿠폰 발급 API입니다.”
범인은 오픈 기념 쿠폰 발급 요청이었다. 하지만 단순히 사람이 몰린 게 아니었다.
“로그를 보세요. 동일한 IP에서 1초에 수십 번씩 요청이 들어오고 있습니다. 클라이언트 앱에서 타임아웃이 나면 즉시 재시도(Retry)를 하도록 로직이 잘못 짜인 것 같습니다. 실패한 요청이 다시 요청을 부르고, 그게 눈덩이처럼 불어나서 서버를 마비시킨 겁니다.”
“그… 그럼 어떻게 해야 하나?”
박 팀장이 떨리는 목소리로 물었다. 그의 눈빛이 흔들리고 있었다. 나를 향한 불신은 사라지고, 지푸라기라도 잡고 싶은 간절함만이 남아 있었다.
“웹이라면 재배포로 해결되겠지만, 앱은 스토어 심사를 거쳐야 해서 수정 배포에 며칠이 걸립니다. 클라이언트를 고칠 수 없으니 서버단에서 막아야 합니다.”
주변 개발자들이 머리를 감싸 쥐며 탄식했다.
“아, 맞다… 앱 심사! 젠장, 핫픽스 올려도 심사 통과될 때까지는 꼼짝없이 죽어있어야 하잖아!”
박 팀장의 입이 벌어졌다. ‘아차’ 하는 표정이었다. 서버 개발만 생각하느라, 사용자 손에 들린 앱(App)의 배포 특성을 깜빡하고 있었던 것이다. 자신의 무지에 대한 부끄러움과, QA인 내가 그 맹점을 꿰뚫어 보았다는 놀라움이 교차했다.
“막는다고? 고객들은 어쩌고!”
“차단이 아닙니다. 대기열을 만들어 우회시키겠습니다. 지금 들어오는 요청을 가벼운 정적 페이지(Static Page)로 돌려서, DB 부하를 없애고 고객들을 진정시켜야 합니다.”
옆에 있던 개발팀장이 반박했다.
“말이 되는 소리를 해! 대기열 시스템 개발에만 꼬박 일주일이 걸려. 당장 서버가 죽어가는데 소설 쓰고 있어?”
“팀장님.”
나는 그를 똑바로 바라보며 말했다.
“개발할 필요 없습니다. Nginx 설정만으로 트래픽을 정적 안내 페이지로 돌리면 그게 바로 대기실이 됩니다. 모든 고객이 사이트에 접속조차 못 하는 최악의 상황은 막아야 합니다. 핵심 기능인 주문이라도 살려야죠.”
내 목소리에는 흔들림이 없었다. 그것은 초능력자의 오만이 아니었다. 기술적인 확신과, 이 재난 상황을 책임지겠다는 인간 이한결의 결단이었다. 상황실의 모든 시선이 나에게 쏠렸다. 그들의 눈빛이 의심에서 기대로 바뀌는 찰나의 순간을, 나는 놓치지 않았다.
나는 망설임 없이 vi 에디터로 Nginx 설정 파일을 열었다.
“쿠폰 발급 API 요청을 대기 안내 페이지로 리다이렉트하겠습니다.”
location /api/event/coupon/claim {
# 트래픽을 가벼운 정적 대기 페이지로 우회 (DB 접속 차단)
rewrite ^ /static/waiting_room.html break;
}
설정을 저장하고 Nginx를 재시작했다.
service nginx reload
엔터키를 누르는 순간, 거짓말처럼 상황실의 경고음이 멈췄다.
치솟았던 로드(Load) 수치가 떨어지고, 빨간색이었던 대시보드 등들이 하나둘 초록색으로 돌아왔다. 쇼핑몰 메인 페이지가 다시 뜨기 시작했다.
“사… 살았다.”
누군가 탄성을 내뱉었다. 정적 뒤에 환호성이 터져 나왔다. 박 팀장이 나를 와락 끌어안았다.
“이한결! 네가 살렸어! 와, 진짜… 너 정체가 뭐야?”
나는 얼떨떨한 표정으로 모니터를 바라보았다. 화면에 비친 내 눈은 지쳐 있었지만, 그 어느 때보다 선명하게 빛나고 있었다.
보라색 광채는 없었다. 그저 밤새 모니터를 보느라 충혈된, 하지만 승리감에 젖은 평범한 갈색 눈동자뿐이었다.
초능력은 쓰지 않았다. 사람의 뇌를 조작하는 쉬운 길을 버리고, 0과 1의 세계에서 정면으로 부딪쳤다. 내가 쓴 건 단지 몇 줄의 설정 코드와, 포기하지 않고 원인을 파고든 집요함뿐이었다.
하지만 그 순간, 팀원들이 나를 바라보는 눈빛은 내가 초능력을 써서 억지로 심어놓은 가짜 존경과는 비교도 할 수 없었다.
그것은 진짜였다.
나라는 인간이, 오롯이 내 힘으로 쟁취해낸 ‘진짜 신뢰’였다.

다음 이야기 : https://thepin.tistory.com/212
소설001: 내 눈을 보면 안 돼 - 제10화. 눈빛의 끝, 사람의 시작
제10화. 눈빛의 끝, 사람의 시작계절이 두 번 바뀌었다.그날의 ‘30만 접속 대란’은 넥솔브 개발팀의 전설이 되었다. 우리는 그 위기를 넘긴 후에도 수많은 버그와 싸웠고, 크고 작은 장애를 겪
thepin.tistory.com
[부록 1] 한결이 사용한 'awk' 명령어, 쉽게 파헤쳐보기
장애 상황에서 한결은 다음 명령어를 사용해 가장 많이 요청된 URL을 찾아냈습니다.
tail -n 10000 /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 5
이 명령어는 여러 작은 명령어들을 파이프(|)로 연결해서 만든 '로그 분석 파이프라인'입니다. 각 부분의 역할을 쉽게 설명해 드릴게요.
tail -n 10000 /var/log/nginx/access.log:
/var/log/nginx/access.log파일의 마지막 10,000줄을 읽어옵니다. (최근 로그만 보겠다는 뜻)
awk '{print $7}':
awk는 텍스트 파일에서 특정 패턴을 찾고, 원하는 방식으로 출력하는 명령어입니다.'{print $7}'은 각 줄에서 7번째 항목(필드)만 출력하라는 뜻입니다. Nginx access log에서 7번째 필드는 요청한 URL입니다.
sort:
- 앞 단계에서 추출한 URL 목록을 알파벳 순서로 정렬합니다.
uniq -c:
- 정렬된 목록에서 각 URL이 몇 번 등장했는지 세어서 보여줍니다. (중복된 줄을 합쳐서 개수를 표시)
sort -rn:
uniq -c의 결과를 숫자(빈도수)를 기준으로 내림차순 정렬합니다. (-r: reverse,-n: numeric)
head -n 5:
- 정렬된 결과에서 가장 많이 등장한 상위 5개 URL만 보여줍니다.
이 모든 과정을 거치면, 로그 파일에서 "가장 많이 호출된 URL" 순서대로 결과를 얻을 수 있습니다. 한결은 이 결과를 보고 쿠폰 발급 API가 문제의 원인임을 알아낸 것이죠.
[부록 2] 실제 장애 대응 관점에서 본 한결의 조치
소설 속 한결이 내린 "쿠폰 API만 503 에러로 차단"하는 결정은, 실제 대규모 서비스 장애 상황에서 SRE(Site Reliability Engineering, 사이트 신뢰성 엔지니어링)가 선택하는 매우 현실적이고 현명한 응급조치입니다.
1. 피해 범위 최소화 (Blast Radius Reduction)
가장 중요한 원칙입니다. 당시 상황은 쿠폰 API 하나 때문에 쇼핑몰 전체가 마비된 상태였습니다.
- 조치 전: 고객은 상품 조회, 장바구니, 주문 등 아무것도 할 수 없습니다. 사이트 자체가 열리지 않으므로 100%의 고객이 100%의 불만을 겪습니다.
- 조치 후: 쿠폰 기능을 제외한 모든 핵심 기능(상품 조회, 주문, 결제)이 정상화됩니다. 이제 불편을 겪는 고객은 '전체 30만 명'이 아니라 '주문 직전 쿠폰을 적용하려던 일부 고객'으로 한정됩니다.
전체 시스템이 침몰하는 상황에서, 배의 가장 큰 구멍 하나를 급히 막아 침몰을 막고 나머지 승객이라도 살리는 것과 같은 원리입니다.
2. 핵심 기능 우선 복구 (Core Functionality First)
커머스 플랫폼의 가장 핵심적인 비즈니스 가치는 '주문과 결제'입니다. 쿠폰은 부가적인 마케팅 기능입니다.
한결이는 장애 대응의 제1원칙인 '핵심 기능부터 살린다'를 정확히 실행했습니다. 쿠폰을 못 써서 짜증이 나는 고객보다, 아예 주문 자체를 못 해서 떠나가는 고객이 비즈니스에 훨씬 치명적입니다. 한결이는 그 우선순위를 명확히 판단한 것입니다.
3. 시간 확보 (Buying Time)
서버단에서 특정 API를 차단하는 것은 Nginx 설정 변경만으로 수십 초 내에 가능합니다. 하지만 근본 원인인 '클라이언트 앱의 잘못된 재시도 로직'을 수정하려면 다음과 같은 복잡한 과정이 필요합니다.
- 앱 코드 수정
- QA 테스트
- 앱 스토어(구글/애플) 심사 요청 (수 시간 ~ 수 일 소요)
- 고객들이 앱을 업데이트하도록 유도
이 모든 과정에는 최소 몇 시간 이상이 걸립니다. 그 시간 동안 사이트 전체를 마비 상태로 둘 수는 없습니다. 한결이의 조치는 일단 급한 불을 꺼서, 근본 원인을 해결할 수 있는 귀중한 시간을 벌어준 것입니다.
요약하자면, 한결이의 조치는 완벽한 해결책이 아닌, 최악의 상황을 막기 위한 가장 빠르고 효과적인 응급조치(First Aid)이자 차선책(Trade-off)입니다. 이런 냉철한 판단력과 실행력이 바로 9화에서 한결이가 리더로서 팀원들에게 '진짜 신뢰'를 얻게 되는 결정적인 계기가 됩니다.
[부록 3] 한결이 구현한 초간단 대기열 시스템 예시
한결은 복잡한 개발 없이 Nginx 설정만으로 트래픽을 우회시켰습니다. 그 원리를 코드로 보면 다음과 같습니다.
1. Nginx 설정 (nginx.conf)
server {
listen 80;
server_name api.nexolve.com;
# 쿠폰 발급 API 요청이 들어오면
location /api/event/coupon/claim {
# DB를 타지 않는 가벼운 안내 페이지로 강제 이동 (302 Redirect)
rewrite ^ /static/waiting_room.html break;
}
}
2. 대기 안내 페이지 (waiting_room.html)
<!DOCTYPE html>
<html>
<body>
<h1>현재 접속자가 많아 대기 중입니다.</h1>
<p>잠시 후 다시 시도해주세요. (현재 주문 기능은 정상 이용 가능합니다.)</p>
<script>
// 10초마다 자동으로 새로고침하여 접속 시도
setTimeout(function () {
location.reload();
}, 10000);
</script>
</body>
</html>'VibeCoding > 글' 카테고리의 다른 글
| 소설001: 내 눈을 보면 안 돼 - 작가의 말 & 완결 후기 (0) | 2026.02.18 |
|---|---|
| 소설001: 내 눈을 보면 안 돼 - 제10화. 눈빛의 끝, 사람의 시작 (0) | 2026.02.18 |
| 소설001: 내 눈을 보면 안 돼 - 제8화. 리더의 눈빛 (0) | 2026.02.18 |
| 소설001: 내 눈을 보면 안 돼 - 제7화. 오해의 대가 (0) | 2026.02.18 |
| 소설001: 내 눈을 보면 안 돼 - 제6화. 불신의 그림자 (0) | 2026.02.18 |



