Showing Posts From

코드리뷰

테크 리드인데 직책이 없는 개발자의 슬픈 현실

테크 리드인데 직책이 없는 개발자의 슬픈 현실

테크 리드인데 직책이 없는 개발자의 슬픈 현실 월요일 아침 9시. 출근해서 첫 번째 할 일은 슬랙 확인이다. 주말에 올라온 PR이 3개. 후배들이 기다리고 있다. 내가 안 보면 머지가 안 된다. 그들은 나를 기술 리드라고 부르고, 난 그렇게 행동한다. 근데 직책은? 개발자. 그냥. 개발자. 커피를 마신다. 첫 번째다. 아 그거요, 하면서 PR을 본다. 주니어가 짠 코드다. 구조가 이상하다. 리팩토링이 필요하다. 근데 내가 코드를 다시 짜줄 수는 없으니까 "이 부분에서 의존성이 뭔지 생각해봐요"라고 댓글을 단다. 그게 메멘토링이다. 멘토링이 아니라 메멘토링. 내 시간을 쪼개서 하는. 3년 다음에 이직한 친구는 시니어 개발자다. 직책도 있고 연봉도 7천대라고 했다. 나? 6500만원. 올해도 200만원 인상되고 끝.업무 현황: 나는 누구인가 사실 나는 테크 리드다. 팀의 기술 방향을 정한다. 새 프레임워크 도입할 때 나한테 물어본다. 레거시 코드 얘기 나올 때 다들 날 쳐다본다. 마이그레이션 프로젝트? 내가 계획 짠다. 아키텍처 리뷰? 내 책임이다. 그런데 직책은 개발자다. 팀 회의에서 내 발언이 '의견'이 아니라 '지시'로 들린다는 걸 안다. 근데 공식적으로는 선임이 아니니까 누가 책임질 건 애매하다. "그렇게 했어요"라고 하면 "그거 누가 결정했어?"라고 물어본다. 나다. 내가 했다. 근데 나는 개발자고 선임은 아니고. 후배 성과평가? 내가 쓴다. 근데 평가 논의할 때 나는 반영만 된다. 경영진은 직급이 뭐라고 물어본다. 아, 개발자다. 그럼 의견만 듣겠습니다. 라고 한다. 의견만. 페이 밴드는? 개발자 페이 밴드다. 시니어 밴드까지 가려면 1년이 더 필요하다고 HR이 말했다. 그 1년 동안 나는 테크 리드고 지금과 같이 일한다. 이게 3년이다. 커피를 마신다. 두 번째다. 토요일의 업무 "김개발님, 이거 어떻게 생각해요?" 토요일 오후 2시. 쉬는 날인데 슬랙이 울린다. 선임 개발자다. 새 프로젝트 기획이 나왔대. 아키텍처 검토 해달래. 내가 봐야 한다. 안 보면 월요일에 틀린 방향으로 간다. 3명이 틀린 길을 1주일 간다. 그럼 사이클 터진다. 그럼 내 일이 거기서 끝나지 않는다. 주말 오후 2시에 설계 문서를 본다. 칠판을 꺼낸다. 아니 모니터 앞에 앉는다. 생각한다. 펜을 돌린다. 1시간 후 슬랙에 장문의 메시지를 쓴다. "데이터베이스 스키마는 이렇게 가고, 캐시 레이어는 Redis로..." 장황하게 쓴다. 근데 꼭 필요한 내용이다. 없으면 틀린다. 선임: "역시 김개발님이네요. 감사합니다." 감사합니다. 그 말이다. 월급에 포함되지 않는 일이 한 번씩 감사합니다로 끝난다. 그래도 필요하니까 한다. 아내는 뭐해, 라고 묻지 않는다. 나도 답하지 않는다. 토요일이라는 걸 둘 다 잊는다.면접 준비할 마음 이직할 거야? 하는 질문 많다. 동기들 톡방에도 올라온다. "요즘 대기업 시니어 연봉 9천대라던데?" 나도 봤다. 2달마다 본다. 그리고 다시 슬랙을 켠다. 이직할 마음이 있다. 진짜 있다. 그런데 마음이 없다. 면접을 봐야 한다. 포트폴리오를 정리해야 한다. 깃허브를 봐야 한다. 사이드 프로젝트를 하나 띄워야 한다. 혹은 과거 프로젝트를 정리해야 한다. 그걸 할 에너지가 없다. 일과가 끝나면 넷플릭스를 킨다. 뭘 보든 상관없다. 그냥 킨다. 생각을 안 하려고. 주말에 일어나서 면접을 준비할까, 할 생각을 하다가 라면을 끓인다. 라면을 먹으면서 자기 전까지 폰을 본다. 페이스북에서 동기들 사진을 본다. 근데 게시물에 좋아요는 안 누른다. 그리고 월요일 오전 9시가 된다. 아 그거요. 후배를 가르친다는 게 "이 부분에서 왜 싱글톤 패턴을 썼어요?" 새로 온 후배다. 2년차. 내가 물어본다. 그러면 그 질문이 시작이다. 그 질문 하나로 30분이 간다. 나는 가르쳐야 한다. 안 가르치면 내일 다시 틀린다. 모레도 틀린다. 그럼 내가 고친다. 시간이 2배가 된다. 그래서 가르친다. 설명한다. 왜 싱글톤이 문제인지. 스레드 안전성이 뭔지. 테스트 불가능성이 뭔지. 그럼 대안이 뭔지. 후배가 이해했다. "아, 그렇군요. 감사합니다." 30분이 사라졌다. 내 일은 30분 밀렸다. 그 30분은 퇴근 후에 한다. 근데 이게 계속 반복되니까 하루에 2시간이 간다. 5일이니까 10시간이 간다. 한 주에 10시간. 한 달이면 40시간. 그게 내 시간이다. "선임 개발자니까 이 정도는 당연하지" 라고 누구도 말하지 않는다. 근데 그렇게 생각한다. 월급에 후배 교육비가 포함되나? 아니다. 근데 포함된다. 매달 40시간. 월급을 시급으로 나누면 얼마지. 계산은 안 해도 알 수 있다. 담배 피는 척하고 베란다에 나간다. 생각한다. 뭘? 아무것도. 그냥 5분을 버린다.'간단하죠?'의 무게 기획자가 온다. 회의실에 10명이 앉아 있다. 나를 포함해서. "이거 사실 간단하니까 한 3일이면 되겠죠?" 간단하면 너가 해. 내가 말하진 않는다. 그냥 생각한다. 입으로는 "어, 알겠습니다"라고 한다. 간단하다. 정말 간단하다. 단순하게 따지면. 버튼을 누르면 데이터가 저장되게 하면 된다. 그게 간단한 거다. 기획자 입장에선. 그런데 데이터 검증은? 권한 체크는? 동시성 문제는? 롤백은? 로깅은? 모니터링은? 테스트는? 3일이 10일이 된다. 10일이 끝나면 QA에서 버그를 찾는다. 3일치 버그를. 왜냐면 3일에 되게 하려고 했으니까. 야간에 고친다. 기획자는 "역시 세밀하네요"라고 한다. 세밀해서가 아니라 기본을 했을 뿐인데. 근데 이게 반복되니까 의심한다. 내가 느린 걸까? 다른 회사의 개발자들은 3일에 하나? 아니다. 나는 안다. 다들 이렇다. 근데 말 안 할 뿐이다. 그래서 회의실에서 "네, 간단하겠습니다"라고 한다. 나오면서 한숨 쉬고, 책상에 앉아서 모니터를 봐도 계획서가 눈에 안 들어온다. 펜을 돌린다. 회의가 끝난 지 1시간이 지났는데도 펜을 돌린다. 레거시와의 전쟁 "이거 왜 이렇게 짜놨어?" 3년 전 코드다. 내가 짠 게 아니다. 그 전 선임이 짠 거다. 그런데 그 선임은 회사에 없다. 구조가 이상하다. 디자인 패턴이라고 할 수 없다. 그냥... 동작한다. 그게 전부다. 고쳐야 한다. 근데 안 고친다. 왜? 시간이 없다. 새 기능을 해야 하고, PR 리뷰를 해야 하고, 후배를 봐야 하고, 토요일 오후의 아키텍처 설계를 해야 한다. 레거시는 여기에 있다. 계속 동작한다. 버그는 가끔 난다. 그럼 패치한다. 근본은 못 본다. 3년 후배가 와서 묻는다. "이거 왜 이렇게 짜놨어요?" 내가 답한다. "아, 그거요..." 나도 모른다고 할 수도 있다. 근데 말하지 않는다. 왜냐면 나는 시니어 개발자처럼 일하니까 알아야 한다고 생각한다. 그래서 "비즈니스 요구사항이 이랬거든요"라고 말한다. 거짓이 아니다. 근데 전부도 아니다. 밤 11시. 배포 전 마지막 체크다. 레거시 코드를 본다. 여전히 이상하다. 근데 동작한다. 배포한다. 집에 가는 길에 "언젠가 고쳐야지"라고 생각한다. 근데 그 날은 안 온다. 슬랙 알림, 나의 적 알림 음소거. 늘 켜져 있다. 주말인데도. 저녁 8시인데도. 밤 11시인데도. 음소거 상태로. 빨간 숫자가 쌓인다. 1, 5, 12, 27. 27개의 알림. 다 나한테다. 아니 일부는 전사 공지다. 그래도 20개 이상은 내 책임이다. 기획이 변했다. 리뷰 요청이 3개 왔다. 후배가 막혔다고 한다. 선임이 의견을 물었다. 그리고 또 뭔가. 이게 밤 11시 30분의 내 상황이다. 주말 오후라고 생각할 때도 그렇다. 슬랙이 울릴까봐 음소거에서 볼륨을 내렸다가 다시 음소거로 한다. 확인할까봐. 확인하면 일한다. 일하고 싶지 않다. 근데 일 생각이 나 이상하게 괜찮다. 자는 것보다. 그래서 음소거를 푼다. 알림을 본다. 30분이 일로 간다. 그리고 또 음소거를 한다. 근데 마음은 음소거가 안 된다. 아내와의 저녁 "오늘 뭐했어?" 8시 반. 저녁 시간이다. "일했어. 후배 코드 리뷰하고." "토요일인데?" "응." 대화가 끝난다. 아내도 야근한 날이 많다. UI 디자이너. 리뷰가 많다. 근데 코드 리뷰는 내 몫이다. 내가 아니면 못 본다. "내일은 쉴 거야?" "배포일인데..." "또?" 네. 또다. 라면을 끓인다. 아내는 회사 일로 정신없다. 나도 회사 일로 정신없다. 근데 라면은 끓인다. 어제도 라면이었나? 모르겠다. 기억이 안 난다. 아내가 소파에 누웠다. 노트북 위에. 나도 소파에 누웠다. 폰 위에. 같은 공간에 있는데 다른 세상에 있다. 이게 저녁이다. 밤 10시가 되면 자야 한다. 내일 아침은 7시 반에 일어난다. 그리고 또 커피. 동기들과의 거리 "너 시니어 됐어?" 카톡에서 묻는다. 3년 전 후배다. 지금은 내 동료가 아니다. 다른 회사다. "아직." "3년인데?" "응." 뭐라고 설명할까? 테크 리드인데 직책이 없다고? 그럼 왜 안 옮기냐고 물을 게 뻔하다. 나도 그 대답을 못 한다. "연봉도 못 올렸어?" "200만원." "어? 나는 1년에 600만원 올랐는데?" 모르겠다. 그 친구는 이직했다. 시니어로. 6개월 만에 연봉이 8천대가 됐다고 했다. 근데 야근이 많댔다. 번아웃 위험이라고도 했다. 그리고 3개월 후에는 연락이 없었다. 근데 내가 덜 바쁜 건가? 아니다. 내가 더 바쁘다. 근데 시니어로 안 불린다. 후배들 앞에서는 테크 리드처럼 행동하고 경영진 앞에서는 개발자다. 둘 다 아니고 둘 다다. 연락을 받은 지 3시간 후에 답한다. "화이팅." 그게 다다. 오늘도 커피 세 번째 커피를 마신다. 오후 3시. 어제 아침에 일어난 지 24시간도 안 됐는데 벌써 세 번째다. 첫 번째는 출근 직후. 깨어나기 위해. 두 번째는 회의 전. 정신 차리기 위해. 세 번째는 지금. 이유는 없다. 그냥 마신다. 커피가 없으면 이 업무가 안 된다. 근데 커피가 있어도 된다. 모순이다. 근데 마신다. 펜을 돌린다. 모니터를 본다. 코드를 본다. 문제가 있다. 해결해야 한다. 내일도 같은 일을 한다. 모레도. 그 다음날도. 그리고 어느 날 슬랙에서 "김개발님 시니어 됐대요"라는 말이 나올 거다. 근데 그때도 일은 같다.내일은 좀 나아지겠지. 아마도.

사이드 프로젝트 3번 접은 개발자가 배운 것

사이드 프로젝트 3번 접은 개발자가 배운 것

사이드 프로젝트 3번 접은 개발자가 배운 것 출근했다. 월요일이다. 슬랙을 켰다. 금요일부터 쌓인 메시지 47개. 레거시 시스템 버그, 배포 관련 문의, 후배 코드 리뷰. 일단 무시하고 커피를 마신다. 첫 번째다. 어제 밤, 침대에서 다시 생각했다. 사이드 프로젝트 얘기다. 정확히는 사이드 프로젝트 '접은 걸' 생각했다. 3번 접었다. 3번 다. 회사 일 외에 뭔가 만들고 싶은 게 없는 게 아니다. 문제는 그게 아니다. 시작하고 2주 뒤 현실이 와서 꺾여버린다. 아내가 "너 또 안 할 거다"라고 할 때, 그게 가장 싫다. 왜냐하면 맞으니까.프로젝트 1번: 주식 앱 (1주일 반 버틴 것) 작년 2월이었다. 회사에서 Redis 사용 경험이 필요했다. 그러면 사이드로 만들면서 배우자고 생각했다. 주제는 주식이었다. 실시간 시세를 캐싱하고, 사용자 포트폴리오를 추적하고, 매매 알림을 주는 앱. "이번엔 다르다"고 다짐했다. 첫 주말은 열정이었다. ERD를 그렸다. API 설계했다. Redis 아키텍처도 다 구상했다. GitHub에 저장소까지 만들었다. 커밋 메시지는 "initial commit: stock tracking app with real-time cache strategy". 멋있었다. 월요일이 왔다. 회사에서 Redis 관련 긴급 버그가 터졌다. 프로덕션 캐시 일관성 문제. 밤 10시까지 붙들었다. 화요일은 출근해서 그 문제를 추적했다. 수요일엔 후배가 물어봤다. "시니어, 이 쿼리 왜 느려요?" 30분을 설명했다. 목요일에 사이드 프로젝트를 다시 봤다. 코드가 낡아 보였다. 뭔가 잘못된 느낌. Redis 설정을 다시 봤다. 생각을 정리하려고 또 다른 설계를 했다. 금요일엔 아무것도 못 했다. 주말엔 침대. 월요일에 GitHub에 들어갔다가 나왔다. 더 이상 하기 싫었다.프로젝트 2번: 개인 블로그 플랫폼 (10일) 4월이었다. 트위터에서 개발자들이 개인 블로그 갖는 게 중요하다고 했다. 그러면 플랫폼을 만들자고 생각했다. Medium 같은 서비스. 마크다운 에디터, 태그 시스템, 댓글, SEO 최적화. 대단했다. "이번엔 정말 다르다"고 했다. 진짜 다를 줄 알았다. 첫 3일은 정말 빨랐다. Spring Boot 프로젝트 세팅, JPA 엔티티 설계, 마크다운 파싱 라이브러리 통합. 생각보다 쉬웠다. "아, 이게 진짜 가능하겠네"라고 생각했다. 그 생각이 문제였다. 4일차: 마크다운 렌더링에서 보안 이슈가 보였다. XSS 방지. 10가지 경우의 수를 고민했다. 하나 구현했다. 나머지 9개는 다음에 하기로. 5일차: 데이터베이스 마이그레이션을 생각했다. 유저가 블로그 이동할 때 데이터를 어떻게 옮길까. 너무 복잡했다. 다음 버전에 미뤘다. 6일차: 아내가 물어봤다. "블로그 만들어?" "응, 만들고 있어." "언제 오픈?" 그 순간 마음이 내려앉았다. 언제냐고. 모르잖아. 7일차: 코드를 다시 봤다. 마크다운 파싱 로직이 마음에 안 들었다. 전부 다시 짰다. 사실 필요 없었다. 8일차: 8일차에 뭘 했는지 모른다. GitHub 커밋 기록도 없다. 9일차: GitHub에 들어갔다가 나왔다. 10일차: 프로젝트를 삭제했다. 아니, 삭제 안 했고 그냥 방치했다. 지금도 있다. Private 저장소로. 패배의 증거.프로젝트 3번: 자동화 도구 (2주) 7월이었다. 회사에서 매번 배포할 때마다 같은 작업을 반복했다. 체크리스트 확인, 데이터베이스 마이그레이션 스크립트 검증, 알림 전송. 3번 접은 개발자가 배웠어야 할 것: 작은 게 최고다. 근데 이번엔 작게 시작했다. 진짜. 배포 자동화 도구. CLI 하나면 끝. 아무것도 웹 없이. 아무것도 복잡하게 없이. 단순함. 첫 주: 전부 다 했다. Python으로 CLI 도구. 8시간 안에 뼈대 완성. 회사에서 써봤다. 먹혔다. 동료들이 물어봤다. "이거 언제 만들었어?" "어제 밤." "진짜?" 뿌듯했다. 둘째 주: 추가 기능을 생각했다. 롤백 기능, 배포 로그 기록, 실패했을 때 자동 알림. 한 가지씩 추가하다 보니까 복잡해졌다. 코드가 길어졌다. 테스트 케이스도 많아야 했다. 처음 간단함은 어디갔나. 그러던 와중에, 회사에서 새로운 배포 시스템 도입 공지가 났다. 외부 솔루션. 3개월 뒤면 우리 도구 필요 없어진다. 그걸 봤을 때, 손에서 힘이 빠졌다. 왜 하고 있지. 이 느낌 또 왔다. GitHub에 들어갔다가 나왔다. 3번 다다.배운 것들 (솔직하게) 교훈 같은 건 안 가르칠 거다. 인터넷에 넘쳐난다. "작게 시작하세요", "꾸준함이 중요합니다" 같은 거. 그런 건 다 알고 있었다. 근데 못 했다. 첫 번째: 완성 중독이 있다는 것. 프로젝트를 시작하면 '다 만들어서 공개해야지'가 머릿속 기본값이 된다. 그렇게 되면 나머지 모든 것들은 완성도가 낮으면 공개 못 한다고 생각한다. 주식 앱도 그랬다. "아직 알고리즘이 부족하니까", "UI가 못생겼으니까". 이유는 많다. 근데 공개 안 하면 뭐가 다른가. 그냥 개인 폴더에서 썩는 거다. 그게 낫나. 두 번째: 번아웃이 일찍 온다는 것. 회사에서 7시간 일하고, 퇴근 후 2시간을 사이드 프로젝트에 쓴다고 하자. 그럼 뇌는 코딩만 12시간 한다는 거다. 주말엔? 더 하고 싶은 마음이 안 생긴다. 그냥 침대가 끌린다. 근데 계속 자책한다. "왜 못 하지. 다들 하는데." 다들 안 한다. 그냥 SNS에서 자랑하는 사람들만 보인다. 세 번째: 공동작업이 없으면 쉽게 포기한다는 것. 회사 프로젝트는 스팩 변경되면 짜증나도 한다. 왜냐면 누군가 기다리니까. 근데 사이드 프로젝트는? 오직 자기만을 위한 거. 오직 자기만이 유일한 스테이크홀더다. 자기 마음만 중요하면 사실 중요한 게 아니다. 스스로에겐 자비롭기 쉽다. 네 번째: 진짜 만들고 싶은 건 따로 있다는 것. 3개 프로젝트 다 "이거 배우고 싶어서" 또는 "포트폴리오 될 것 같아서" 시작했다. 정말 그 앱이 필요해서가 아니었다. 내가 주식을 몰라서가 아니라 Redis를 배우고 싶어서. 블로깅이 하고 싶어서가 아니라 마크다운 파싱이 하고 싶어서. 이건 말이 안 된다. 뭔가를 잘 만들려면 그걸 정말 원해야 한다.지금 지금은 뭘 하고 있나. 사이드 프로젝트? 안 한다. 필요 없다고 생각했나. 아니다. 밤 10시에 누워 있으면서 생각한다. 앱 만드는 거. 블로그 쓰는 거. 뭔가를 만드는 거. 근데 아침이 되면 출근한다. 회사에선 할 게 많다. 퇴근하면 피곤하다. 주말엔 쉰다. 이게 계획된 결과인가. 아니다. 그냥 그런 거다. 에너지 관리의 실패. 혹은 진짜 필요 없는 걸 만들려고 했던 거고, 그래서 자연스럽게 포기된 거다. 그럼 이제 뭘 해야 하나. 아내가 말했다. "넌 회사 일로 충분히 바빠. 왜 더 해." 맞다고 생각했다. 근데 자책은 계속 된다. 왜인지는 모르겠다. 아마도 개발자라는 직업이 계속 뭔가 배우고 빌드해야 한다고 강요하는 것 같다. 그게 나쁜 건 아니다. 근데 그게 회사에서 충분하다면, 사이드는 뭐 하는 거지. 최근에 생각을 바꿨다. 사이드 프로젝트는 재미 없으면 버린다. 배우기 위해서가 아니라, 그냥 만드는 게 재미있어야 한다. 근데 일단 3번 실패한 개발자 입장에선, 4번째를 어떻게 믿고 시작하나. 그게 문제다.근데 뭔가 배운 건 있다 프로젝트를 접으면서 배운 코드들은 남았다. Redis 아키텍처 방식, 마크다운 파싱 로직, Python CLI 도구 구조. 그건 버려진 게 아니다. 회사에서 쓸 때 나온다. "아, 저 방식 어디서 봤는데" 하면서. 아, 그렇구나. 사이드 프로젝트가 완성돼야 의미 있는 게 아니다. 배운 것들이 다른 데서 쓰일 때 의미가 있는 거다. 그럼 3번 접은 게 완전 손해는 아니네. 다만, 시간이 아까운 건 사실이다. 4개월이 그냥 사라졌다. 대신 뭘 배웠나면, 배웠다는 것 자체를 증명하기 힘들다. 코드도 없고, 완성된 앱도 없다. 그냥 "배웠어"라고 말할 수밖에. 근데 그게 현실이다. 개발자의 삶은 많은 부분이 그렇다. 배웠는지 모르고, 못 했는지 안 했는지도 모른다. 그냥 계속한다. 정해진 시간에 출근해서, 정해진 일을 하고, 퇴근한다. 사이드 프로젝트는 그걸 벗어나려는 시도다. 하지만 결국 마찬가지다. 번아웃이 올 때까지 한다. 그리고 멈춘다. 다음 번엔 다를 거라고 생각하면서. 이건 교훈이 아니다. 그냥 현실이다.커피를 마신다. 두 번째다. 회사에선 지금 배포 준비 중이다. 우리 팀이 만든 기능이 나간다. 후배가 물어봤다. "프로덕션에서 Redis 캐시 문제 없을까요?" 내가 답했다. "몰라. 나가고 봐." 아, 그거 사이드에서 배웠던 거다. 문제는 나가야 본다.[IMAGE_4] [IMAGE_5]결국 다음 사이드 프로젝트도 접할 것 같다.

Redis 캐시 전략으로 밤새 디버깅한 날

Redis 캐시 전략으로 밤새 디버깅한 날

Redis 캐시 전략으로 밤새 디버깅한 날 그건 화요일 저녁 6시의 일이었다 일반적으로 배포는 금요일 오후 3시에 한다. 근데 이번엔 달랐다. 기획팀이 수요일 자정까지 급하게 처리해야 할 기능이 있다고 했고, 우리 팀은 당연히 전쟁 준비를 했다. 사실 나는 지난주부터 Redis 캐시 도입을 준비하고 있었다. 회사 서비스가 요즘 느리다는 컴플레인이 계속 들어오고 있었다. 사용자가 늘어나면서 데이터베이스 쿼리가 답답해 지기 시작한 거다. 데이터는 자주 바뀌지 않는데 매번 디비에서 갱신하고 있었으니까 말이다. 그래서 내가 제안했다. "Redis 캐시 레이어 추가하면 어때요?" 팀장은 "좋지, 김개발이가 해봐" 라고 했다. 요즘 팀장은 항상 그렇다. 뭔가 새로운 기술이 나오면 나한테 건넨다. 직책도 없으면서. 아무튼. 나는 지난주 내내 Redis 연결, TTL 설정, 캐시 무효화 로직을 짰다. 로컬에서는 완벽했다. DB 응답 시간이 200ms에서 20ms로 뚝 떨어졌다. 이거야말로 진짜 최적화다. 나는 자신감에 차 있었다. 화요일 저녁 6시, 배포가 시작되었다. 테스트 서버부터. 로그를 봤다. 모든 게 정상이었다. 프로덕션 배포. 여전히 정상. "좋네. 이제 집 갈까" 라고 생각했다. 아내한테 카톡했다. "늦게 먹을 거 같아" 라고. 아내는 "알겠어 조심해" 라고 했다. 근데 밤 10시. 슬랙에서 핑이 울렸다. 안 좋은 예감이 들었다. 모니터가 시커먼 밤 10시의 공포 [Alert] User ID 12345 - 잘못된 데이터 제공 반복 이 메시지가 모니터에 떴을 때, 나는 한숨을 쉬면서 생각했다. "또 시작이네." 기획팀에서 온 리포트였다. 몇몇 사용자가 캐시된 데이터가 맞지 않는다는 피드백을 주었다는 거다. 사용자 A의 정보를 조회할 때 사용자 B의 정보가 나온다는 식. 이건 내가 가장 싫어하는 케이스였다. "대체 뭐가 문제지?" 하고 한참 고민해야 하는 상황 말이다. 로컬에선 진짜 문제가 없었는데. 나는 일단 프로덕션 서버의 로그부터 뒤지기 시작했다. CloudWatch 대시보드를 열었다. 레이턴시는 확실히 내려갔다. 그건 좋은 신호였다. 그럼 캐시 정책이 작동한다는 뜻인데... 오류 로그를 보니 패턴이 보였다. 2024-01-17 22:10:45 - Cache HIT for user:12345 - result from Redis 2024-01-17 22:10:50 - user:12345 updated in DB 2024-01-17 22:11:02 - Cache HIT for user:12345 - result from Redis (STALE DATA)아. 이거다. 내가 짠 캐시 무효화 로직이 DB 업데이트를 감지하지 못하고 있었다.나는 잠깐, 내가 뭘 놨더라 하면서 코드를 다시 봤다. 캐시 무효화 로직은 이렇게 짜여 있었다: @Transactional public void updateUser(User user) { userRepository.save(user); redisTemplate.delete("user:" + user.getId()); }당연하지. 이렇게 간단할 리가 없다. 문제는 우리 시스템이 마이크로서비스 아키텍처로 되어 있다는 거였다. 사용자 정보는 User Service에서 관리하지만, 프로필 이미지 같은 건 Content Service에서 관리했다. 그리고 두 서비스는 다른 데이터베이스를 쓰고 있었다. 즉, 내 무효화 로직은 User Service의 캐시만 날리고 있었다. Content Service에서 업데이트된 데이터는 Redis에 남아있었던 거다. "아 그거요" 라고 중얼거렸다. 이제 문제를 알았으니까 고칠 수 있다. 근데 문제는... 10시가 넘었다. 새벽 3시, 커피가 식어가는 시간 나는 일단 핫픽스를 생각했다. Redis의 모든 관련 캐시를 무조건 날려버리는 방식으로. 그럼 성능이 또 떨어질 텐데, 일단 버그는 없을 거다. @Transactional public void updateUser(User user) { userRepository.save(user); // 일단 관련된 모든 캐시 다 날려버림 redisTemplate.delete("user:" + user.getId()); redisTemplate.delete("profile:" + user.getId()); redisTemplate.delete("content:" + user.getId()); // ... 기타등등 }팀 슬랙에 메시지를 띄웠다. "일단 핫픽스 할게요. 내일 제대로 리팩토링합시다." 배포는 밤 11시에 끝났다. 버그는 사라졌다. 근데 나는 계속 앉아 있었다. 뭔가 불안했다. "이게 진짜 완벽한 해결책인가?" 밤 12시, 커피를 또 마셨다. 셋째 잔. 나는 비즈니스 로직을 다시 생각했다. 지금 우리 시스템은 여러 마이크로서비스가 동일한 사용자 정보를 캐시하고 있었다. 어느 한 서비스에서 업데이트되면, 다른 서비스도 알아야 한다. 근데 어떻게? 메시지 큐? Kafka? 그건 복잡할 것 같은데. 아니면 그냥 캐시 TTL을 짧게 가져가? 그럼 매번 다 조회해야 하는데 결국 성능 개선이 없는 거잖아. 나는 다시 생각했다. "근본적인 문제가 뭐지?" 아. 여러 서비스가 같은 데이터를 각각 캐시하고 있다는 게 문제다. 그럼 캐시를 중앙화해야 한다. 모든 데이터 조회는 하나의 서비스를 거쳐서, 그 서비스가 Redis를 관리하도록. 밤 2시. 나는 구조를 다시 그렸다. 손으로 노트에다가. 종이와 펜이 제일 빠르다. User Service → [Redis Cache Manager Service] → Redis Content Service ↓ Profile Service ↓이렇게 하면 어떻게 되지? 음... 네트워크 호출이 하나 더 늘어난다. 하지만 모든 캐시가 일관성 있게 유지된다. 밤 3시, 슬랙에 긴 스레드를 남겼다.팀장님, 내일 아침에 얘기하고 싶은데, 현재 캐시 설계에 근본적인 문제가 있는 것 같습니다. 마이크로서비스 간 캐시 무효화가 제대로 안 되고 있어요. 긴급 핫픽스는 했는데, 장기적으로는 아래처럼 개선이 필요할 것 같습니다:Redis Cache Manager 서비스 신설 모든 캐시 쓰기/삭제 요청을 여기로 집중 TTL 정책 중앙화 ...그리고 나는 커피를 마셨다. 넷째 잔. (셋째가 아니라) 컴퓨터는 여전히 켜져 있었고, 모니터에는 코드가 떠 있었다. 나는 다시 생각했다. 혹시 내가 놓친 게 있나? 아. 하나 더 있었다. Cache Invalidation Pattern이었다. 우리는 지금 TTL-based invalidation을 쓰고 있었는데, 이건 보장되지 않는다. 업데이트가 되고 나서 TTL이 끝날 때까지의 그 시간 동안, 잘못된 데이터가 나간다는 뜻이다. 그럼 Event-driven invalidation은? 업데이트가 발생하면 즉시 이벤트를 발행하고, 그걸 듣는 모든 캐시 레이어가 반응한다. Kafka 같은 메시지 큐를 쓰면 돼. 근데 이건 더 복잡하다. 그리고 비용도 는다. 나는 한숨을 쉬었다. 개발이 정말 쉬워 보이기만 하지, 실제로는 이렇게 트레이드오프를 계속 해야 한다. 밤 4시, 나는 침대로 갔다. 결국 아내 옆에 누웠다. 아내는 자고 있었다. 새벽 5시, 깨어나면서 든 생각 꿈을 봤는데, 뭔지는 모르겠다. 그냥 코드를 계속 보고 있는 느낌이었다. 눈을 떴을 때 시간이 5시였다. 휴... 밤 11시에서 4시까지 거의 5시간을 일한 거다. 그런데 뭔가 좋은 느낌이 들었다. 문제를 찾았으니까. 그리고 해결 방법도 여러 개 생각했으니까. 근데 동시에 깨달았다. 이 문제는 사실 처음부터 예측할 수 있었어야 했다는 거다. 마이크로서비스 아키텍처에서 캐시를 도입할 때는 항상 무효화 전략을 먼저 생각해야 한다. 성능 개선은 나중이다. 나는 다시 한 번 생각했다. "내가 면접 준비를 해야 하나? 다른 회사에서는 이런 실수가 안 나나?" 아니다. 어디든 있다. 다만 시간에 여유가 있는 회사는 이런 문제를 천천히 해결한다. 우리처럼 급하게 배포하는 회사에서는 항상 이런 문제가 터진다. 아내가 깨어났다. "뭐 해?" "일." "밤에?" "응. 어제 배포에서 버그가 났어." 아내는 한숨을 쉬었다. "내일 출근 안 하면 되지?" "그럼 누가 고칠?" 아내는 다시 누웠다. 우리 부부는 서로의 일에 대해 깊이 있게 물어보지 않는다. 그냥 있다는 걸 알고 있을 뿐이다.아침 8시, 회의실에서의 대화 나는 2시간 반을 더 잤다. 당연히 피곤했다. 사무실 도착 시간은 오전 8시 30분. 평소보다 30분 빨랐다. 팀장에게 내가 쓴 스레드를 보여주고 싶었다. 팀장은 커피를 마시고 있었다. "팀장, 어제 그 버그 이야기 들으셨어요?" "응. 잘 고쳤네. 고마워." "네, 근데 이건 임시방편이고요. 우리 캐시 아키텍처에 근본적인 문제가..." 팀장이 손을 들었다. "알겠어. 문서화해서 보여줘. 그 다음에 우리 얘기할 거 있고." 나는 알았다. 진짜 얘기는 나중에 나온다는 뜻이다. 퀵스탠드업 미팅이 끝나고, 팀장이 나를 불렀다. "어제 밤을 새웠네." "...네. 문제를 찾아야 해서." "잘 했어. 근데 이런 식으로 일하면 안 돼. 네가 팀에서 가장 시니어인데, 뭔가 도움이 필요하면 후배들한테 물어봐. 너 혼자 다 해야 하는 건 아니야." 나는 대답을 못 했다. 팀장 말이 맞는데, 내 일의 성격상 그게 쉽지 않거든. 후배들한테 "이거 이상한데 뭐 같아?" 라고 물어보면, 결국 내가 다시 다 설명해야 한다. 그럼 시간이 더 든다. 하지만 입으로는 "알겠습니다" 라고만 했다. 이제는 무엇을 배울 것인가 지금 오후 2시다. 나는 캐시 무효화 로직을 다시 작성하고 있다. 이번엔 다르다. Event-driven 방식은 아니지만, 최소한 여러 서비스 간의 캐시 일관성을 보장하도록 했다. 간단한 로직이다: // User Service에서 업데이트 발생 @Transactional public void updateUser(User user) { userRepository.save(user); // 자신의 캐시만 날리지 말고, 다른 서비스에도 알려주기 cacheInvalidationService.invalidateUserCache(user.getId()); }// CacheInvalidationService public void invalidateUserCache(Long userId) { // 모든 관련 캐시 패턴을 한 곳에서 관리 List<String> patterns = getCachePatterns(userId); redisTemplate.delete(patterns); // 나중에는 메시지 큐로 다른 서비스에도 전파 }사실 이것도 완벽한 해결책은 아니다. 하지만 나는 이제 알았다. 완벽한 해결책은 없다. 있는 건 상황에 맞는 적절한 트레이드오프일 뿐이다. 귀가 길에, 나는 커피숍에 들어갔다. 커피 한 잔 더. 다섯째 잔. 바리스타가 내 얼굴을 봤다. 아마도 피곤해 보였을 거다. "힘든 하루였어요?" "네. 밤을 새웠어요." "또 배포 날씸?" "아뇨. 버그 때문에요." 바리스타는 웃었다. "개발자는 항상 뭔가 때문에 밤을 새네요." 나도 웃었다. "그래서 정신이 올빠르지." 집에 도착했을 때, 아내가 있었다. 보통 이 시간엔 없는데. "조금 일찍 나왔어?" "응. 넌 뭐해? 피곤해 보여." 나는 아내에게 어제 밤 일을 설명했다. Redis, 캐시 무효화, 마이크로서비스... 아내는 대부분 못 알아듣겠지만, 그냥 들어주는 것만으로도 충분했다. "고생했네. 밥 먹고 쉬자. 내가 뭐 사 올까?" "김치찌개." "또?" "점심으로 먹었어도 저녁으로 먹고 싶어." 아내는 한숨을 쉬고 나갔다. 나는 소파에 누웠다. 모니터는 꺼졌다. 슬랙도 무음. 일단 이 순간은 나의 것이다. 이제 깨달았다. 완벽한 코드를 짜려다 밤을 새는 것보다, 괜찮은 코드를 짜고 시간을 자기는 게 훨씬 나낫다는 걸. 적어도 내 정신 건강을 위해선. 내일부터는 어떻게 할까? 유사한 문제가 또 나오면? 나는 아마 또 밤을 새우겠지. 왜냐하면 내가 팀의 일인당 전문가니까. 근데 적어도 이번엔 알았다. 혼자 다 하려고 하지 말고, 처음부터 아키텍처를 제대로 생각해야 한다는 걸. 그리고 또 하나. 우리 팀은 이제 Redis 캐시 가이드 문서가 필요하다. 마이크로서비스 환경에서의 캐시 무효화 패턴, TTL 설정 기준, 모니터링 방법 등등. 나는 내일부터 그걸 작성해야 한다. 그럼 또 다른 후배가 같은 실수를 하지 않겠지. 아내가 돌아왔다. 김치찌개 냄새가 풍겼다. "먹자."결국 버그는 나를 성장시켰고, 밤샘은 나를 피로하게 했지만, 그 둘의 조합이 내를 개발자로 만들었다.