어떤 회귀는 발견되는 데 1달이 걸렸고, 발견된 뒤에도 진짜 원인을 찾는 데 한 시간이 더 걸렸다. 그 회귀를 만든 사람은 나였다.
들어가며
안녕하세요, SCG 장재원입니다.
오늘은 작업 중 코드가 이전 형상 (버전) 으로 돌아가게 된 계기와, 그에 대한 회고 및 반성을 주제로 글을 써볼까 합니다. 회고록 형식으로 쓴 글이어서 ‘~다’ 로 문장을 마무리한 점 양해 부탁드립니다!
그럼 시작!
이번 학기에 나는 종합설계프로젝트(전전 졸업평가 강좌)를 듣고 있었다. 어느 날 같이 팀 프로젝트를 하는 멤버들이 작년 작품을 참고하려고 전자전기공학부 졸업작품 전시회 페이지인 eeegrd.skku.edu에 접속했는데, 설문조사 페이지에서 이메일 인증이 안 되어 사이트에 들어가지 못한다고 알려왔다.

이상한 일이었다. 분명 작년 12월에 전전 행정실에서 같은 문제를 신고하셨고, 우리 쪽에서 처리 완료라는 회신까지 드렸기 때문이다. 메일 박스를 뒤져 그때의 흔적을 찾아봤다.


분명히 처리됐다는 메일이 남아 있다. 그런데 3개월이 지난 지금, 똑같은 증상이 나타나고 있다. 이게 어떻게 가능한 걸까.
결론부터 적어두면, 이 4개월짜리 회귀를 만든 사람은 다름아닌 나였다.
정확히는 2월 23일에 디스크 풀 장애를 대응하던 중에 만든 자해성 회귀였다.
이 글은 그 추적기다.
1차 진단: "그때 fix가 배포가 안 됐던 거구나"
GitHub 히스토리를 켰다. 12월 17일자로 master에 들어간 커밋 3개가 있었다.
4ab95c4 fix: 설문조사 패스
df7e8ac fix: initial login false 반환
ff6605a fix: initial login만 false 반환
코드를 읽어보니 설문조사 페이지를 우회시키는 로직이 분명히 들어 있다. 그런데 운영 환경에서는 그 로직이 적용되지 않은 동작을 한다.
"아, 머지는 됐는데 배포가 제대로 안 됐던 거구나."
이때 나는 그렇게 결론을 내렸다. 너무 빨랐다. 그 빠른 결론이 이 글의 본질이다.
그래서 다음과 같이 조치했다.
- 현재 master를 master-backup-2026-03-21 으로 백업
- 위 3개 커밋은 의도가 불분명하다고 판단해서 건너뛰고, 그 이후에 들어간 네이버 메일서버 인증 비밀 키 변경 커밋만 cherry-pick (인증메일 발송이 안 되던 것이 기존에 발송을 위한 메일서버 인증 비밀 키가 만료되서였다)
- 새로 정리된 master로 이미지를 빌드 (eee_gp:4) 후 재배포
배포 직후 사이트에 들어가니 설문조사 페이지가 없어졌다. 이슈가 해결됐다고 판단했다.
그리고 슬랙에 이렇게 적었다.
근데 저 3개 커밋은 좀 미스테리네요 설문조사를 없애달라는 요청이 있었던건지…
이 메시지를 쓰던 시점의 내가 가장 부끄럽다.
이전 작업자(이용욱 님)에게 슬며시 책임을 묻는 어조로 시작하고 있는데, 사실 그분의 작업은 처음부터 완벽하게 옳았다. 잘못된 건 내가 만든 회귀였다.
결정적 단서: 사라진 35회 배너
재배포 직후 사이트를 둘러보다 이상한 점을 하나 더 발견했다.
내가 재배포하기 전에는 메인 화면에 "제34회 졸업작품 전시회 — 2025학년도 1학기" 배너가 떠 있었다.
그런데 재배포 후에는 "제35회 — 2025학년도 2학기"로 자동으로 바뀌었다.
이 작업을 하던 시점은 2026학년도 1학기 중이었으므로 35회 (2025학년도 2학기)는 가장 최근 회차가 맞았다. 12월 5일에 35회 메뉴를 추가한 커밋이 master에 이미 들어가 있다.
7ab43c4 [FIX] 제35회 졸업작품발표회 메뉴 추가 (Dec 5, 2025)
그러니까, 12월 5일 이후로는 35회 배너가 떠 있어야 정상인데, 어제까지의 운영 환경에서는 34회 배너가 떠 있었다는 뜻이다.
12월 17일에 들어간 fix만 누락된 게 아니다. 12월 5일에 들어간 메뉴 업데이트도 누락되어 있었다. 두 개의 정상 머지가 한꺼번에 사라진 셈이다.
이 시점에 머릿속에서 가설이 바뀌었다. "배포가 안 된 게 아니라, 어디선가 누가 이미지를 갈아끼웠다."
시간을 거슬러: 12월에 실제로 있었던 일
마침 손형준 님이 슬랙에 답변을 남겨주셨다. 요약하면 이렇다.
- 12월 17일, 전전 행정실 담당자분이 인증 오류를 메일로 신고하셨고, 메일과 별도로 개인 휴대전화로 직접 연락을 주셨다.
- 원래 설문조사는 추첨용 데이터 수집 목적이었는데 더 이상 활용 목적이 없으니, 그냥 패스만 시켜달라는 요청이었다.
- 이용욱 님이 3개 커밋을 작성해서 patch를 만들었고, 손형준 님이 빌드/배포 후 본인 계정을 지웠다 재가입하는 테스트까지 마쳤다.
그리고 김현석 님이 더 거슬러 올라간 맥락도 보충해주셨다.
- 애초에 설문조사 및 이메일 인증을 만든 이유는 2024년 9월경 타 학교 학생의 표절 신고가 들어와서, 전자전기공학부 교수님들께서 졸업작품 전시회 페이지의 외부인 접근을 막아달라고 요청하셨기 때문.
- 이메일 인증에 SCG 자체 메일 서버 대신 scgskku@naver.com을 쓴 이유는, 당시 SCG 메일 서버에서 발송한 인증 메일이 inbox에 10–20분씩 늦게 도착하는 문제가 있어서 외부 메일 서버를 선호했던 것으로 추정.
자, 그러면 12월 17일에 분명히 배포까지 완료되었던 fix는, 지금은 왜 안 보이는가? 머지된 코드 위로는 오늘까지 새 커밋이 없다. 그러면 이미지가 어디선가 한 번 더 갈아끼워졌다는 얘기다.
진짜 원인: 2월 23일의 디스크 풀 사건
Portainer를 열어보니, 운영 중이던 컨테이너의 이미지는 eee_gp:2였다. 생성일은 2026년 2월 23일 14:12.

그 날, 그 시각. 정확히 기억난다. 내가 한 일이다.
그 날 시스템 헬스체크 봇이 eeegrd.skku.edu가 죽었다고 알람을 보냈다.

들어가 보니 /dev/sda1이 100%로 차 있었고, 원인은 물리 서버인 PM2에 로그 데이터만 21GB까지 적재된 거였다.
scg@scg:~$ du -sh ~/.pm2/logs
21G /home/scg/.pm2/logs
로그를 비우고 컨테이너를 재가동해야 하는데, 그날따라 Portainer 자체가 정상 작동을 안 했다(디스크 용량 문제와 연관된 것으로 추정된다).
그래서 서버에 SSH로 직접 접속해서 처리했다. 프로젝트 디렉토리로 가서 한 줄을 쳤다.
sudo docker-compose -f docker-compose.prod.yaml up -d
여기까지는 장애 대응으로서 합리적이었다. 문제는 이 한 줄 안에 두 개의 함정이 숨어 있었다는 점이다.
함정 1. 서버 로컬 소스 ≠ 최신 master
서버에 체크아웃되어 있던 소스코드는 자동 git pull이 도는 환경이 아니다. 지금 다시 확인해보니, 그 디렉토리의 가장 최근 커밋은 d1b4ff — 2025년 11월 18일자다. 12월 5일의 35회 메뉴 추가도, 12월 17일의 설문조사 패스도, 그 디렉토리에는 없었다.
서버 로컬 master: d1b4ff (2025-11-18) ← 내가 모르고 빌드한 코드
실제 GitHub master: ff6605a (2025-12-17) ← 운영에 떠 있어야 할 코드
함정 2. docker-compose.prod.yaml의 고정 이미지 태그
문제의 파일은 이렇게 생겼다.
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
image: eee_gp:2 # ← 여기
container_name: eee_gp_prod
ports:
- '8080:3000'
image: eee_gp:2. 이게 핵심이다.
docker-compose up -d를 호출하면 build 컨텍스트에서 이미지를 빌드하고, 그 결과물을 eee_gp:2라는 이름으로 태그한다.
즉, 11월 18일자 로컬 소스로 빌드된 이미지가 eee_gp:2라는 이름을 강제로 덮어쓴 것이다.
그 전에 12월 17일자 fix가 반영되어 잘 돌고 있던 진짜 eee_gp:2 이미지는 그 순간 사라졌다. 마침 컨테이너도 재기동되니, 새 (잘못된) eee_gp:2가 그대로 운영에 올라갔다.
전체 타임라인
일자 사건 운영 상태
| 2024-09 | 김현석 님, 이메일 인증/설문조사 기능 구현 (표절 신고 대응) | 정상 |
| 2025-11-18 | 서버 로컬 디렉토리의 마지막 git pull (추정) — d1b4ff | — |
| 2025-12-05 | 35회 메뉴 추가 머지 + 배포 | 정상 |
| 2025-12-17 | 행정실 인증 오류 신고 → 손형준 님 통화 → 이용욱 님 fix 3건 머지/배포/테스트 | 정상 |
| 2026-02-23 | 디스크 풀로 다운 → 내가 11월 18일자 로컬 코드로 eee_gp:2 재빌드 | 회귀 발생 |
| 2026-03-21 | 종설프 팀원 신고 → 1차 오진 → 결국 회귀 시점 발견 후 정상 복구 | 복구 |
내가 만든 회귀를, 1개월 동안 아무도 모르고 있었다.
왜 그 자리에서 알아채지 못했나
2월 23일 재배포 직후, 사이트가 다시 떠 있는 걸 확인하고 슬랙에 "소생 완료"라고 적었다. 화면 캡쳐도 같이 첨부했다.
그 캡쳐에는 너무도 당당하게 "제34회 졸업작품 전시회 — 2025학년도 1학기" 배너가 떠 있었다.
그 시점은 이미 12월이 두 달이나 지난 2월 말이었다.
지금 보면 너무 명백한 신호인데, 그때는 못 봤다. 이유는 단순하다. 내가 전전 졸업작품 전시회의 운영 사이클을 잘 몰랐다.
회차가 학기 단위로 올라간다는 것, 35회가 그 시점 최신이라는 것, 12월에 이미 35회로 갱신되어 있어야 한다는 것을 모르고 있었다.
"보이는 게 정상으로 보인다"는 기준으로 배포를 끝내면 안 됐다. 그 사이트의 도메인을 아는 사람이 한 명이라도 거쳤다면 바로 알아챘을 것이다.
이번 사건이 드러낸 구조적인 문제
기술적 원인은 위에서 다 풀었고, 사실 더 중요한 건 이번 사건이 드러낸 SCG의 운영상 구조 문제다. 4가지 정도로 정리해 보았다.
1. 단체 자산 계정이 개인 명의로 운영되고 있다
scgskku@naver.com. 이 계정으로 졸업작품 전시회의 인증 메일이 발송된다.
그런데 이 계정에 등록된 관리자 전화번호는 21기 선배의 개인 휴대폰이고, 현재 활동 중인 어떤 회원도 이 계정의 비밀번호나 복구 정보를 인계받은 적이 없다.
조직 차원에서 쓸 계정은 단체 계정으로 만들어야 한다. 당장은 귀찮더라도 3년 후 5년 후의 우리를 위해서다.
가능하면 scg@scg.skku.ac.kr 같은 통합 도메인 안에서 관리하는 게 좋다.
추가 조치로 SCG 네이버 계정으로 인증 메일을 발송하는 다른 서비스가 있는지 전수조사를 했고, 현재까지 확인된 건은 eeegrd 한 건 뿐이었다.
2. 각 프로젝트의 운영 지식을 책임지는 사람이 없다
15개가 넘는 서비스를 운영하면서, 지금까지는 회장이 모든 프로젝트의 모든 내용을 관여해왔다.
인원이 늘었으니 이제 분권화가 필요하다.
프로젝트별 TPM(Technical Project Manager) 역할이 있어야 한다고 생각한다.
- 그 프로젝트의 기술 스택과 운영 히스토리를 책임지는 사람
- 차기 TPM에게 반드시 인수인계
- 응급 상황에 대응할 최소한의 인프라 지식 보유 (TPM이 이 역할을 겸하면 이상적)
3. 전화/대면 업무 결과를 Slack에 남기지 않는다
12월 17일의 처리가 거의 다 통화로 이루어졌다는 점이 이번 사건의 추적을 어렵게 만들었다. 손형준 님이 행정실과 결정한 내용("설문조사 더 이상 필요 없음"), 빌드와 테스트 결과 — 이게 슬랙에 한 줄이라도 남아 있었다면 2월에 내가 디스크 풀 대응할 때 훨씬 더 신중하게 했을 것이다.
어떤 형태로든 외부와 처리한 업무 결과는 슬랙에 남겨주세요. 본인을 보호하는 증거도 됩니다.
우리가 슬랙을 도입한 지 3년 정도밖에 안 됐지만, 그 사이에 슬랙 히스토리 덕분에 살아난 건들이 정말 많다. 인수인계를 진행을 하지만 실제로 작업 디테일 하나하나 까지 인수인계 하기는 쉽지 않은 일이다. 또한 선배들과 접점이 있다 하더라도 1~3년 전 일을 선배님들도 하나하나 기억을 해서 해결을 하기에는 어려운 일이다.
4. 프로덕션 배포가 너무 조용하다 (silent deployment)
2월 23일 내가 한 재배포에 대해 슬랙에 짧게 한 줄 남기긴 했지만, 그건 장애 대응 스레드 안이었다.
만약 그 사이트를 평소 사용하는 다른 회원이 그 캡쳐를 봤다면, "어 근데 35회 배너가 왜 없지?"라고 말해줬을지도 모른다.
#org-상용배포 같은 채널을 신설해서, 프로젝트 무관, 프로덕션에 올라가는 모든 배포는 전체 활동회원을 @ 태그하여 알리는 정책을 도입했다. 일종의 릴리즈 노트 개념이다. 실무에서 silent deployment는 있을 수 없는 일이다. 중요한 배포는 비개발 직군까지 대기를 시키는 경우도 있다.

마치며
3월 21일에 처음 그 메시지를 받았을 때, 나는 너무 빠르게 답을 만들고 싶었던 것 같다. "아, 이 커밋들이 배포가 안 됐구나" 하고 단정 짓고는 백업 브랜치까지 만들어서 master를 갈아엎었다. 정작 진짜 원인은 4개월 전 내가 한 디스크 풀 대응에 있었는데도.
부끄럽지만 솔직하게 적어둔다. 회귀를 만들고, 그 사실을 1달 동안 모르고, 발견했을 때는 이전 담당자 탓을 먼저 했다. 그게 이번 사건에서 내가 한 일이다.
이 글을 남기는 이유는 두 가지다.
하나는 위의 4가지 구조적인 문제를 실제로 SCG의 운영 방식에 반영하기 위해서. 그냥 이렇게 끝내면 다음 사람이 똑같은 함정에 빠진다.
다른 하나는 일종의 자가 처벌이다. 다음에 비슷한 상황에서 다른 사람을 의심하기 전에, 이 글을 다시 읽으면서 한 번 멈춰 서기 위해.
다음 사람을 위한 짧은 메모
- docker-compose.prod.yaml에서 image: name:tag가 고정으로 박혀 있고 변동이 없는 형태는 회귀 위험이 크다. 운영 환경에서는 name:${GIT_SHA} 처럼 매 배포마다 unique한 태그를 쓰는 게 안전하다.
- 어떤 이미지가 어떤 커밋으로 빌드됐는지 추적할 수 있도록, Dockerfile에 build arg로 git commit hash를 넣어두는 패턴이 유용하다.
- 서버 로컬 디렉토리에 체크아웃된 코드는 항상 최신이 아니다. 배포 전에 반드시 git fetch && git status로 확인하자
- 그리고 배포 후에는, 도메인 지식을 가진 사람이 한 번 더 확인해 줄 수 있게 알려라.
그리고 이건 여담이지만 긴급한 이슈를 임시방편으로 해결한 후에는 이후에 잊지 않고 시간을 들여서라도 근본 원인 (root cause) 를 찾는 것이 필요하다.
이 일에서도 결국 네이버 이메일 발송 서버의 인증이 만료된 것으로 드러난 것은, 12월 17일이 아니라, 이 문제를 다시 보게 된 3월 21일의 일이었다.
'Troubleshooting' 카테고리의 다른 글
| 8년 된 레거시 시스템의 에러를 추적한 과정 - 로깅 한 줄의 힘 (0) | 2026.03.22 |
|---|