Showing Posts From
프로덕션
- 06 Dec, 2025
회의 시간에 본 프로덕션 에러 로그의 공포
회의실에서 울린 경보음 회의 시작 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 한숨 쉰다. 내일도 출근이다.회의 중 프로덕션 에러는 개발자의 심장을 가장 빠르게 뛰게 만드는 알림이다. 그리고 가장 확실하게 널 체크의 중요성을 각인시킨다.