회의 시간에 본 프로덕션 에러 로그의 공포

회의 시간에 본 프로덕션 에러 로그의 공포

회의실에서 울린 경보음

회의 시작 10분 전. 커피 들고 회의실 들어갔다. 기획자 둘, PM 한 명, 나. 분기 계획 회의래. 한 시간 예정.

노트북 펼쳤다. 슬랙 꺼놨다. 방해 금지 모드. 그래야 집중한다고 핑계 댄다. 사실 회의 중에 코드 보려고.

근데 핸드폰은 안 끈다. 혹시 몰라서.

PM이 화면 공유 시작했다. 로드맵 어쩌고. 귀에 안 들어온다. 고개는 끄덕이는데 손은 펜 돌리고 있다.

15분 지났을 때. 핸드폰이 진동했다.

테이블 밑에서.

심장이 한 박자 뛴다. 이 시간에 알림이면 둘 중 하나다. 인사팀 공지 아니면 장애.

테이블 밑으로 슬쩍 핸드폰 꺼냈다. 화면 봤다.

Sentry: [Production] NullPointerException

좆됐다.

로그를 보는 순간

화면 밝기 최소로 줄였다. 슬랙 열었다.

알림 세 개. 아니, 다섯 개. 새로고침하니까 일곱 개.

[ERROR] UserService.getProfile() - NPE at line 247
[ERROR] UserService.getProfile() - NPE at line 247
[ERROR] UserService.getProfile() - NPE at line 247

같은 에러. 연속으로. 1분 사이에 23건.

UserService. 내가 짠 코드다. 지난주 배포한 거.

PM이 말하고 있다. “이번 분기 목표는 MAU 20% 증가인데요…”

한 귀로 듣고 한 귀로 흘린다. 눈은 핸드폰. 스크롤 내린다.

에러 로그 펼쳤다. 스택 트레이스 본다.

at com.company.service.UserService.getProfile(UserService.java:247)
at com.company.controller.UserController.getUser(UserController.java:89)

247번 줄. 어디였지. 기억 안 난다. 떠올려본다.

아. 그거. 캐시 체크하는 부분. Redis에서 유저 정보 가져오는 로직.

근데 왜 NPE가. 널 체크 분명 했는데.

아니다. 안 했다.

그냥 redisTemplate.opsForValue().get(key).getName() 이렇게 박았다.

왜 그랬지. 리뷰도 통과했는데. 아 맞다. 그날 금요일이었다. 다들 빨리 퇴근하고 싶어 했다.

심장이 빨라진다. 손에 땀 난다.

회의는 계속된다

“개발님 어떻게 생각하세요?”

“네? 아 네.”

뭘 물어봤는지 모른다. 그냥 긍정했다.

기획자가 웃는다. “그럼 이 기능 이번 달 안에 가능하다는 거죠?”

좆됐다. 뭐라고 대답해야 하지.

“일단… 검토해보고 말씀드리겠습니다.”

만능 답변. 검토해보겠습니다.

핸드폰 다시 본다. 알림 더 왔다. 12개. 20개. 숫자가 올라간다.

Sentry 대시보드 연다. 에러율 그래프 본다. 빨간 선이 수직 상승.

2:47PM부터. 지금 3:02PM. 15분간 148건.

유저 몇 명이나 영향받았지. 대시보드 스크롤. Affected Users: 37.

37명. 아직 괜찮다. 아니, 괜찮지 않다. 37명이면 엄청 많은 거다.

검색해본다. “redis get null check java”. 스택오버플로우.

String value = redisTemplate.opsForValue().get(key);
if (value != null) {
    return value.getName();
}

맞다. 이렇게 했어야 했다. 병신같이 왜 안 했지.

PM이 또 말한다. “그럼 다음 안건으로 넘어가겠습니다.”

넘어가. 빨리. 나가게 해줘.

참을 수 없는 20분

노트북 열었다. 척 코드 보는 척. 사실 핫픽스 생각 중.

UserService.java 파일 기억해낸다. 어떻게 고쳐야 하지.

간단하다. 널 체크 추가. if 문 하나. 3줄이면 끝.

근데 배포가 문제다. PR 올리고, 리뷰 받고, 머지하고, 빌드하고, 배포.

최소 30분. 급하면 15분. 근데 지금 회의 중.

회의는 아직 30분 남았다. 로드맵 다 안 끝났다.

핸드폰 진동. 또. DM 왔다. 운영팀.

“개발님 유저 문의 들어오는데요, 프로필 조회가 안 된다고…”

답장 친다. “확인 중입니다.”

확인 중. 맞다. 확인 중이다. 근데 고칠 수는 없다. 회의 중이니까.

손가락이 떨린다. 펜 더 빠르게 돌린다.

기획자가 화면 넘긴다. “다음 기능은 소셜 로그인인데요…”

소셜 로그인. 듣고 싶지 않다. 나가고 싶다. 노트북 들고 나가서 배포하고 싶다.

근데 나갈 수 없다. 이유가 필요하다. “화장실 갑니다” 하고 30분 안 들어오면 이상하다.

그냥 참는다. 20분만. 20분만 버티면 된다.

회의 끝, 그리고 전쟁 시작

3:28PM. PM이 말한다. “오늘은 여기까지 하겠습니다.”

노트북 닫는다. 아니, 안 닫는다. 들고 간다.

“먼저 가보겠습니다.”

뛰다시피 나간다. 자리 앉는다. 모니터 켠다.

IntelliJ 연다. UserService.java 찾는다. 247번 줄.

// 기존 코드
User user = redisTemplate.opsForValue().get(cacheKey);
return user.getName(); // 여기서 NPE

고친다.

// 수정 코드
User user = redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
    return fetchFromDB(userId); // DB에서 다시 조회
}
return user.getName();

3분 걸렸다. 커밋한다. “[HOTFIX] Add null check for Redis cache”. 푸시.

PR 올린다. 제목 “[HOTFIX] Fix NPE in UserService”. 설명 대충 쓴다. “Redis null 체크 누락”.

슬랙에 쓴다. “#dev 채널 @channel 핫픽스 PR 올렸습니다. 리뷰 부탁드립니다.”

시니어 개발자가 답한다. “확인했습니다. Approve.”

머지한다. Jenkins 빌드 시작. 2분 걸린다. 화면 본다. 초록불.

배포 스크립트 실행. ./deploy.sh production. 엔터.

Deploying to production...
Building Docker image...
Pushing to registry...
Rolling update started...

5분 기다린다. 핸드폰 본다. 손톱 물어뜯는다.

Deployment successful
Pods: 4/4 running

완료.

Sentry 새로고침한다. 에러 그래프 본다. 빨간 선이 멈췄다. 3:41PM부터 0건.

숨 쉰다. 제대로.

총 에러: 289건. 영향받은 유저: 73명.

운영팀한테 DM. “배포 완료했습니다. 확인 부탁드립니다.”

“넵 확인했어요! 감사합니다~”

자리에 앉아있다. 심장 진정된다. 손 떨림 멈춘다.

커피 마신다. 식었다. 그래도 마신다.

그날 저녁

퇴근길 지하철. 핸드폰 본다. Sentry. 에러 0건. 계속 0건.

집 도착. 현관문 열었다. 신발 벗는다.

아내가 묻는다. “오늘 어땠어?”

“그냥… 회의 있었어.”

“힘들었어?”

“응. 좀.”

저녁 먹는다. 치킨. 시켜먹었다. 배달의민족.

넷플릭스 켠다. 아무거나 튼다. 화면 안 본다. 천장 본다.

오늘 배운 거. 회의 전엔 배포하지 말 것. 금요일엔 더더욱.

그리고 널 체크. 무조건 널 체크.

Redis든 뭐든 외부에서 받은 건 다 의심. 믿지 말 것.

IDE 켠다. 노트북. 개인 프로젝트 아니고 회사 코드.

검색한다. “redisTemplate.opsForValue().get”. 프로젝트 전체.

결과 17개. 하나씩 연다. 널 체크 있는지 본다.

7개 없다. 다 고친다. 커밋한다. PR 올린다.

제목: “[REFACTOR] Add null checks for Redis operations”.

내일 아침에 리뷰 요청하면 된다.

지금은 잔다.

근데 잠이 안 온다. 눈 감으면 빨간 알림이 보인다.

[Production] NullPointerException

한숨 쉰다.

내일도 출근이다.


회의 중 프로덕션 에러는 개발자의 심장을 가장 빠르게 뛰게 만드는 알림이다. 그리고 가장 확실하게 널 체크의 중요성을 각인시킨다.