새 팀원이 실수할 때마다 드는 생각

새 팀원이 실수할 때마다 드는 생각

새 팀원이 실수할 때마다 드는 생각 오늘도 PR이 날아왔다 알림이 울렸다. 신입 민수의 PR이다. 제목부터 불안하다. "로그인 기능 수정". 뭘 수정했길래. 코드를 열었다. 첫 줄부터 눈에 띈다. if (password == user.getPassword())문자열 비교를 ==로 했다. 자바 기본 중의 기본이다. 한숨이 나온다. 근데 참는다. 스크롤을 내린다. 더 있다. try { // 200줄의 로직 } catch (Exception e) { System.out.println("에러남"); }Exception을 그냥 잡아서 출력만 한다. 로그도 없다. 키보드에 손을 올렸다가 뗀다. 뭐라고 써야 하나. "이건 왜 이렇게 하셨나요?"는 너무 공격적이다. "equals() 써야 합니다"는 너무 교과서적이다. 결국 이렇게 쓴다. "문자열 비교는 equals() 메서드를 사용해주세요. == 연산자는 객체 참조를 비교해서 예상과 다르게 동작할 수 있습니다." 친절하게 썼다고 생각한다. 근데 속으로는 욕이 나온다. 이건 3년 차면 당연히 알아야 하는 거다. 아니, 신입도 알아야 한다.3년 전 내 코드가 떠오른다 근데 코멘트를 달다가 멈췄다. 문득 3년 전 내가 쓴 코드가 생각났다. 당시 팀장님이 내 PR에 코멘트를 달았었다. "N+1 쿼리 문제가 있을 것 같은데요. fetch join 고려해보시겠어요?" 그때 난 몰랐다. N+1이 뭔지도. 검색해서 알았다. 창피했다. 이런 기초도 모르고 있었다니. 근데 팀장님은 그냥 "고려해보시겠어요?"라고 물었다. 명령이 아니었다. 그래서 나도 찾아봤다. 공부했다. 고쳤다. 만약 팀장님이 "이것도 모르세요?"라고 했으면 어땠을까. 아마 위축됐을 거다. 질문도 못 했을 거다. 민수의 코드를 다시 본다. 완벽하진 않다. 근데 노력은 보인다. 변수명도 신경 썼고, 주석도 달았다. 틀렸지만. 코멘트를 수정한다. "equals()를 쓰면 더 안전합니다. 참고: [자바 문자열 비교 docs 링크]" 이 정도면 될 거다.당연한 게 어디 있나 점심시간이다. 민수가 옆자리에 온다. "형, 코멘트 확인했어요. 제가 equals()를 써야 하는 이유를 몰랐네요." 솔직하다. 좋다. "응, == 연산자는 객체 주소를 비교하거든. 그래서 내용이 같아도 false가 나올 수 있어." "아... 그럼 항상 equals()를 써야 하나요?" 질문이 나왔다. 좋은 신호다. "문자열은 그래. 근데 equals()도 null 체크를 해야 해. 아니면 Objects.equals()를 쓰거나." 민수가 고개를 끄덕인다. 노트에 적는다. 예전엔 이런 게 답답했다. '이 정도는 당연한 거 아닌가?' 근데 지금은 안다. 당연한 게 없다는 걸. 나도 7년 차지만 모르는 게 수두룩하다. 어제도 코틀린 코루틴 관련해서 검색했다. 3시간 걸렸다. 리액트 개발자한테 물어봤다. "이거 당연한 건가요?" 했더니 웃었다. "저도 처음엔 헷갈렸어요. 당연한 거 없어요." 그 말이 위로가 됐다. 민수한테도 그래야겠다고 생각한다. "당연한 거 없어. 다 배우는 거야."실수를 바라보는 법 민수의 두 번째 PR이 왔다. 이번엔 equals()를 썼다. 그런데. if (user.getName().equals(null))null을 뒤에 뒀어야 했는데 앞에 뒀다. NPE 터진다. 또 한숨이 나온다. 근데 참는다. 코멘트를 단다. "null 체크는 null을 뒤에 두면 안전합니다. 혹은 Objects.equals() 추천드려요." 이번엔 왜냐고 안 물었다. 예시 코드를 같이 올렸다. // NPE 위험 if (user.getName().equals(null))// 안전 if (null == user.getName()) if (Objects.equals(user.getName(), null))30분 뒤에 답이 왔다. "아 이런 방법이! 감사합니다!" 수정 커밋이 올라왔다. Objects.equals()를 썼다. approve를 눌렀다. 실수는 반복된다. 어쩔 수 없다. 나도 지금 실수한다. 어제 배포에서 env 파일 빠뜨렸다. 30분 롤백했다. 팀장님이 뭐라고 했나. "괜찮아요. 다음엔 체크리스트 만들어봐요." 혼내지 않았다. 해결 방법을 알려줬다. 그래서 나도 민수한테 그렇게 한다. "틀렸네요"가 아니라 "이렇게 하면 더 좋아요"로. "이것도 몰라요?"가 아니라 "저도 헷갈렸어요"로. 시간이 오래 걸린다. 설명하는 게 직접 고치는 것보다 3배는 느리다. 근데 이게 맞는 거 같다. 민수가 스스로 고치고, 이해하고, 다음엔 안 틀리는 게 낫다. 내가 고쳐주면 당장은 빠르다. 근데 민수는 배우지 못한다. 그럼 똑같은 실수를 반복한다. 결국 더 느리다. 가르치는 게 배우는 거다 리뷰를 하다 보니 내가 더 배운다. 왜 equals()를 써야 하는지 설명하려면 내부 동작을 알아야 한다. 그래서 다시 찾아봤다. String pool이랑 intern() 메서드까지. 7년 차인데 제대로 몰랐다. 그냥 쓰기만 했다. 민수한테 설명하려고 정리했다. 내가 더 명확해졌다. null 체크도 그렇다. "왜 null을 뒤에 둬요?" 물어봤을 때 대답을 못 했다. 그냥 그렇게 하라고 배웠으니까. 이유는 몰랐다. 찾아봤다. Yoda notation이라는 게 있었다. 스타워즈 요다에서 따온 이름이다. "null is name" 이런 식으로 읽힌다고. 재밌다. 근데 요즘엔 Objects.equals()를 더 권장한다는 것도 알았다. 민수 덕분에 배웠다. 예전 팀장님 말이 생각난다. "가르치는 게 제일 공부 많이 돼요." 맞는 말이다. 혼자 코드 짤 땐 대충 넘어간다. '일단 되네, 됐어.' 근데 누군가한테 설명하려면 제대로 알아야 한다. 그래서 찾아본다. 정리한다. 내 것이 된다. 민수가 물어볼 때마다 귀찮다. 솔직히 그렇다. 근데 동시에 고맙다. 내가 더 성장한다. 인내심이 필요하다 오늘 민수가 세 번째 실수를 했다. git commit에 node_modules를 올렸다. 200MB다. 한숨이 나온다. 또. ".gitignore에 추가해주세요"라고 썼다. 근데 민수가 ".gitignore가 뭐예요?"라고 물었다. 순간 멈췄다. 이것까지 모르나. 화가 났다. 솔직히 말하면. '깃도 제대로 모르고 어떻게 입사했지?' 근데 참았다. 심호흡했다. 예전 내가 생각났다. 첫 회사 들어가서 깃 쓸 줄 몰랐다. SVN만 써봤다. merge conflict 나면 패닉했다. 선배가 옆에 앉아서 30분 동안 알려줬다. 짜증 한 번 안 냈다. 그 선배 덕분에 깃을 배웠다. 지금도 감사하다. 그래서 민수한테 답했다. ".gitignore는 git에 올리지 않을 파일을 정의하는 거예요. 같이 볼까요?" 화면 공유했다. 10분 설명했다. "아~ 이래서 템플릿이 있었구나. 감사합니다!" 민수가 이해했다. 다음 커밋엔 제대로 했다. 인내심이 필요하다. 매일. 실수는 반복된다. 다른 실수지만. 그때마다 설명한다. 참는다. 기다린다. 3개월 뒤면 민수도 달라질 거다. 6개월 뒤면 더. 1년 뒤엔 혼자 할 수 있을 거다. 그럼 나도 편해진다. 지금 투자하는 시간이 나중을 위한 거다. 그렇게 생각하면 견딜 만하다. 완벽한 사람은 없다 퇴근 전에 슬랙 메시지가 왔다. 팀장님이다. "개발님, 오늘 배포 건 확인 부탁드려요." 내가 짠 코드다. 테스트 다 통과했다. 배포했다. 10분 뒤에 알림이 울렸다. 에러다. 로그를 봤다. NullPointerException. 내 코드에서 났다. null 체크를 안 했다. 아까 민수한테 null 체크하라고 했는데. 나도 안 했다. 창피했다. 얼굴이 뜨겁다. 급하게 고쳤다. 롤백하고 재배포했다. 팀장님한테 보고했다. "제가 null 체크를 빠뜨렸습니다. 죄송합니다." "괜찮아요. 급한 건 아니었으니까. 다음부턴 조심하시면 돼요." 혼나지 않았다. 오히려 위로받았다. 그제야 깨달았다. 나도 실수한다. 매일. 7년 차도 이런데 3개월 차는 오죽할까. 민수의 실수가 이해됐다. 완전히. 당연한 게 없다. 누구한테도. 시니어도 틀린다. 주니어는 더 틀린다. 당연하다. 그래서 서로 봐줘야 한다. 코드 리뷰로. 페어 프로그래밍으로. 혼자선 못 본다. 같이 봐야 한다. 결국 팀이다 민수가 한 달 지났다. 실수는 여전히 한다. 근데 줄었다. 같은 실수는 안 한다. 새로운 실수를 한다. 그게 성장이다. 어제 민수가 내 코드에 코멘트를 달았다. "형, 이 부분 Optional 쓰면 더 안전할 것 같아요." 맞는 말이다. 내가 놓쳤다. approve 눌렀다. "좋은 지적이에요. 고맙습니다." 민수가 웃는 이모지를 보냈다. 이게 팀이다. 서로 실수하고, 서로 고쳐주고, 서로 배운다. 완벽한 사람은 없다. 완벽한 코드도 없다. 그래서 같이 한다. 리뷰하고, 논의하고, 개선한다. 민수의 실수를 볼 때마다 짜증났다. 솔직히. 근데 지금은 안다. 그게 과정이라는 걸. 나도 그 과정을 거쳤다. 지금도 거치고 있다. 앞으로도 실수할 거다. 민수도, 나도. 그때마다 서로 알려주면 된다. "이렇게 하면 더 좋아요." "저도 헷갈렸어요." "같이 볼까요?" 그게 시니어가 하는 일이다. 코드를 잘 짜는 것만이 아니다. 팀을 키우는 것도. 민수가 1년 뒤엔 또 달라질 거다. 2년 뒤엔 더. 그럼 나도 편해진다. 민수한테 맡길 수 있다. 그리고 민수도 후배를 가르칠 거다. "당연한 거 없어. 다 배우는 거야." 내가 했던 말을.내일도 민수의 PR이 올 거다. 또 실수가 있을 거다. 그래도 괜찮다. 인내심 가지고 본다.

월요일 아침 회의는 왜 그렇게 길까

월요일 아침 회의는 왜 그렇게 길까

월요일 아침 회의는 왜 그렇게 길까 9시 30분, 시작 월요일이다. 알람이 울렸다. 주말 동안 밀린 드라마 3편 봤다. 잠은 4시간. 출근길 지하철에서 슬랙 확인했다. 금요일 오후에 올라온 메시지 37개. 다 읽지 않았다. 회사 도착. 9시 20분. 커피 뽑고 자리 앉았다. 모니터 켰다. "9시 30분 주간회의"라는 캘린더 알림이 떴다. 아직 10분 남았다. 코드 좀 보려고 했다. "회의 시작할게요~" 9시 28분이다. 아직 2분 남았는데.10시, 아직 진행 중 회의실이다. 8명 앉았다. 팀장이 화면 공유했다. 지난주 이슈 리스트. "하나씩 짚고 넘어가죠." 첫 번째 이슈. DB 인덱스 문제. 내가 금요일에 해결했다. 이미 머지했다. "아 그거요, 금요일에 끝났어요." "아~ 그렇구나. 그럼 다음." 두 번째 이슈. API 응답 속도 개선. 프론트 개발자가 말한다. "이거 백엔드에서 처리해주시면..." 내 일이다. 또. 세 번째 이슈. 기획 변경 건. "사실 이건 아직 확정은 아닌데요..." 그럼 왜 회의 안건에 올렸나. 10시 15분. 아직 안건 절반도 못 갔다. 노트북 열었다. 코드 보는 척했다. 실제로는 어제 본 야구 경기 하이라이트 검색 중.10시 45분, 주간 계획 "자, 그럼 이번 주 각자 할 일 공유해볼까요?" 프론트 개발자부터. 3분. 디자이너. 5분. 새 컴포넌트 시스템 설명을 왜 여기서. PM. 10분. 로드맵 설명인데 내용은 지난주랑 똑같다. 내 차례. "저는 API 리팩토링이랑 성능 개선 작업 하겠습니다." 30초 만에 끝냈다. 팀장이 말한다. "좀 더 구체적으로 말해줄래요?" 구체적으로. 뭘 더. "음... 쿼리 최적화하고, 캐시 레이어 추가하고, 불필요한 API 호출 줄이는 작업이요." "소요 시간은요?" "3일?" "2일 안에 가능할까요?" 가능하면 처음부터 2일이라고 했다. "...해보겠습니다."11시 20분, 드디어 "마지막으로 공지사항 있으신 분?" 침묵. 5초. 누가 말 꺼낸다. "아 참, 다음 주 목요일 워크샵..." 11시 30분. 회의 끝났다. 2시간. 월요일 오전이 사라졌다. 자리로 돌아왔다. 슬랙 미확인 메시지 15개. 회의 중에 쌓인 거다. 메일도 8통. 점심시간까지 1시간 반. 이제 일 시작이다. 월요일 오전은 없다. 왜 길까 생각해봤다. 왜 월요일 회의는 긴가. 첫째, 주말이 있었다. 금요일 오후부터 월요일 아침까지. 3일. 그 사이 모두의 맥락이 리셋됐다. 다시 싱크 맞추려면 시간이 걸린다. 둘째, 안건이 많다. 주말 동안 쌓인 것들. 금요일에 못 끝낸 것들. 월요일에 새로 생긴 것들. 전부 회의로 간다. 셋째, 사람이 많다. 8명이 앉았다. 8명이 각자 5분씩만 말해도 40분. 거기에 질의응답, 논쟁, 잡담. 2시간은 기본이다. 넷째, 명확한 결론이 없다. "일단 검토해보고..." "다음에 다시 얘기해요." 결론 없는 회의는 길다. 끝이 보이지 않으니까. 월요일 오후 점심 먹었다. 김치찌개. 사장님이 물었다. "오늘 피곤해 보이네요?" "회의했어요." "아~ 월요일." 알아준다. 오후 2시. 본격적으로 일 시작. API 리팩토링 코드 열었다. 레거시 코드다. 작성자는 2년 전 퇴사. 주석은 없다. 변수명은 a, b, c. 한숨 나왔다. 3시. 슬랙 알림. "개발님, 아까 회의에서 말씀하신 건 언제쯤 가능할까요?" 아까가 언제. 2시간짜리 회의였는데. "어떤 건지 구체적으로 말씀해주시면..." "API 응답 속도요." "3일 정도요." "2일 안에 부탁드려요." 회의 때 들었던 말이다. 또 들었다. 4시. 코드 리뷰 요청 3개 왔다. 후배들 PR이다. 봤다. 수정 필요한 부분 7군데. 코멘트 달았다. 30분 걸렸다. 5시. 내 작업 진행률 20%. 오전에 회의 안 했으면 60%는 갔다. 월요일이 아까웠다. 다음 월요일 다음 주도 똑같다. 다다음 주도. 월요일 9시 30분이면 회의다. 팀장한테 말했다. 한 번. "회의 시간 좀 줄일 수 없을까요?" "필요한 얘기들이잖아." "그럼 안건 미리 정리해서 공유하면..." "회의에서 직접 듣는 게 낫지." 포기했다. 대신 나만의 규칙 만들었다. 월요일 오전은 원래 없다고 생각한다. 실제 작업은 오후부터. 일정 잡을 때도 월요일 오전은 비운다. 이미 회의가 차지하니까. 월요일 아침 회의. 왜 그렇게 긴가. 답은 간단하다. 월요일이니까. 그래도 회의 끝나고 나면 좋은 점도 있다. 일주일 계획이 정리된다. 어쨌든. 뭘 해야 하는지 명확해진다. 팀원들 상황도 파악된다. 그리고 월요일 오후는 조용하다. 다들 회의로 지쳐서 조용히 일한다. 슬랙도 한산하다. 이때가 집중하기 좋다. 어제 선배한테 물었다. "월요일 회의, 언제까지 이럴까요?" "글쎄. 나 입사했을 때도 그랬으니까." 선배 경력 12년이다. 12년 동안 월요일 회의. 체념했다. 월요일은 원래 그런 거다. 주말과 평일 사이. 회의로 채워지는 시간. 다음 주 월요일도 9시 30분 회의다. 캘린더에 이미 잡혀있다. 반복 일정이니까.오늘도 월요일 회의 2시간. 다음 주도 똑같겠지.

데이터베이스 마이그레이션: 계획과 현실의 괴리

데이터베이스 마이그레이션: 계획과 현실의 괴리

데이터베이스 마이그레이션: 계획과 현실의 괴리 수요일 오전, 계획 "이번 마이그레이션은 여유 있게 잡았습니다." 회의실에서 내가 말했다. 프로젝터에는 깔끔한 타임라인이 떠 있었다. 목요일 오후 2시 시작, 6시 완료. 4시간. 넉넉하다고 생각했다. 기획자가 물었다. "혹시 문제 생기면요?" "백업 있고, 롤백 절차도 문서화했습니다. 최악의 경우 1시간 추가면 됩니다." 팀장이 고개를 끄덕였다. "좋네요. 그럼 목요일 저녁 회식 예약할게요." 나는 대답했다. "네, 7시면 충분합니다."거짓말이었다. 아니, 그땐 진심이었다. 지난 3번의 마이그레이션도 비슷했다. 계획은 4시간, 실제는 9시간. 계획은 6시간, 실제는 새벽 2시. 계획은 "간단한 작업", 실제는 주말 출근. 그런데 이번엔 다르다고 믿었다. 체크리스트를 만들었다. 리허설도 했다. 개발 서버에서 세 번이나 연습했다. 펜을 돌리면서 생각했다. '이번엔 된다.' 목요일 오후 1시 30분, 준비 점심은 거르기로 했다. 김치찌개집 사장님이 섭섭해하셨다. "오늘 안 오세요?" "죄송해요, 오늘 중요한 일 있어서요." "그럼 저녁에 보겠네요?" "아마도요." 커피를 세 번째 마셨다. 아이스. 손이 약간 떨렸는데 카페인 때문인지 긴장 때문인지 모르겠다. 슬랙에 공지를 올렸다. "14:00부터 DB 마이그레이션 시작합니다. 서비스 일시 중단됩니다. 예상 완료 시간 18:00." CS팀에서 답장이 왔다. "고객 공지 올렸습니다. 파이팅!" 파이팅. 그래. 체크리스트를 다시 확인했다. 백업 생성 서비스 중단 공지 read-only 모드 전환 스키마 변경 실행 데이터 검증 인덱스 재생성 서비스 재개 모니터링8개 항목. 각각 30분씩 잡았다. 여유 시간까지 합치면 4시간 반. 후배 민수가 물었다. "형, 제가 도와드릴 거 있나요?" "아니, 괜찮아. 그냥 대기하고 있어. 혹시 모르니까." "네, 슬랙 켜놓을게요."시계를 봤다. 1시 55분. 5분 남았다. 오후 2시, 시작 정각에 시작했다. -- 백업 시작 mysqldump -u root -p production_db > backup_20240116_1400.sql진행 표시줄이 천천히 움직였다. 10%... 20%... 30%... 예상 시간 15분. 실제로는 20분 걸렸다. 데이터가 생각보다 많았다. 괜찮다. 5분 차이. 2시 20분. read-only 모드로 전환했다. SET GLOBAL read_only = ON;슬랙에 알림이 왔다. CS팀. "고객들 문의 들어옵니다. 언제 복구되나요?" "예정대로 6시입니다." "알겠습니다." 스키마 변경을 시작했다. ALTER TABLE users ADD COLUMN last_login_device VARCHAR(50); ALTER TABLE orders ADD INDEX idx_created_at (created_at); ALTER TABLE products MODIFY COLUMN description TEXT;세 개 테이블. 간단하다고 생각했다. 첫 번째는 2분 만에 끝났다. users 테이블은 10만 건. 문제없다. 두 번째가 시작됐다. orders 테이블. 300만 건. 진행 표시줄이 1%에서 멈췄다. 기다렸다. 5분. 10분. 15분. 2%로 올라갔다. 계산했다. 2%에 15분이면 100%는... 750분. 12시간 반. "뭐야." 소리 내서 말했다. 옆자리 동료가 쳐다봤다. 오후 3시, 현실 민수한테 슬랙을 보냈다. "orders 인덱스 생성이 너무 오래 걸려." "개발 서버에선 괜찮았잖아요?" "개발 서버 데이터는 10만 건이었어. 실서버는 300만 건이야." "아..." 아. 그래. 아. 리허설을 제대로 안 한 거다. 데이터 볼륨을 고려 안 했다. 팀장한테 보고했다. "예상보다 시간이 걸립니다." "얼마나요?" "아직 모르겠습니다." "회식은요?" "글쎄요." "일단 8시로 미뤄놓을게요." 8시. 가능할까.구글에서 검색했다. "mysql alter table slow large table" 스택오버플로우가 떴다. 누군가 5년 전에 똑같은 질문을 했다. 답변: "pt-online-schema-change 쓰세요." 지금 설치할 순 없다. 이미 시작했다. 다른 답변: "그냥 기다리세요. 중단하지 마세요." 기다린다. 할 수 있는 게 그것뿐이다. 모니터를 보면서 펜을 돌렸다. 3%... 4%... 5%... 한 시간에 3%. 33시간 걸린다는 계산이 나왔다. "미쳤나." 오후 5시, 결정 팀장이 자리로 왔다. "어때요?" "안 좋습니다." "어느 정도?" "지금 진행률 8%. 예상 완료 시간은... 내일 오전입니다." "내일요?" "네." 팀장이 한숨을 쉬었다. "롤백하고 다시 계획 세울까요?" 그 말에 화가 났다. 나한테가 아니라 나 자신한테. "롤백하면 다시 백업 복구하는 데 1시간 걸립니다. 이미 3시간 썼고요. 지금 취소하면 3시간이 날아갑니다." "그럼?" "계속합니다. 끝까지 합니다." "언제까지?" "모르겠습니다. 하지만 멈추는 것보단 낫습니다." 팀장이 고개를 끄덕였다. "알겠어요. 저녁은 제가 시킬게요. 뭐 먹을래요?" "아무거나요." "치킨?" "네." 민수한테 말했다. "너는 퇴근해." "저도 남을게요." "왜?" "혼자 두기 싫어서요." 고맙긴 한데 미안했다. 내 삽질에 후배까지 붙잡는 거다. 오후 7시, 야근 치킨이 왔다. 먹었다. 맛은 모르겠다. 진행률은 15%. 앞으로 10시간 남았다는 계산. 아내한테 문자를 보냈다. "오늘 늦을 것 같아." "또?" "응. 미안." "저녁은?" "회사에서 먹어." "알았어. 조심히 와." 조심히. 뭘 조심해. 키보드 두드리는 거를. 슬랙을 확인했다. CS팀에서 메시지. "고객들 화나셨어요. 6시에 복구된다고 했는데." "죄송합니다. 예상보다 오래 걸립니다. 자정까지는 완료됩니다." 자정. 말하고 보니 진짜 자정까지 걸릴 것 같다. 회사는 조용했다. 다들 퇴근했다. 불 켜진 자리는 우리 팀뿐. 민수가 커피를 가져다줬다. "형, 아아요." "고마워." "제 잘못도 있는 것 같아요. 제가 테스트 데이터 더 넣어볼 걸 그랬어요." "아니야. 내가 확인 안 한 거야." "하지만..." "됐어. 그냥 조용히 있어줘." 뭐라고 말해야 할지 몰랐다. 위로도 싫고 변명도 싫었다. 오후 10시, 대기 25%. 6시간 남았다. 회의실 바닥에 누워서 천장을 봤다. 형광등이 깜빡였다. '다음엔 더 여유 있게 일정 잡자.' 항상 하는 다짐. 항상 지켜지지 않는 다짐. 지난번 마이그레이션도 그랬다. 4시간 계획이 10시간 걸렸다. 그때도 똑같이 다짐했다. '다음엔 이틀 잡자. 롤백 시간까지 포함하자.' 근데 이번엔 또 4시간을 잡았다. 왜? 기억하지 못해서? 아니면 기억하고 싶지 않아서? 아니면 그냥 희망회로 때문에. '이번엔 다를 거야.' 민수가 과자를 내밀었다. "형, 이거라도 드세요." "너 집 가." "형 혼자 두기 싫다니까요." "나 괜찮아." "안 괜찮아 보이는데요." 솔직히 괜찮지 않았다. 짜증났다. 화났다. 나한테. 왜 매번 같은 실수를 반복할까. 왜 매번 '이번엔 다르다'고 믿을까. 7년차인데 아직도 이러고 있다. 선배들은 다 이렇게 사나. 자정, 완료 35%. 민수는 회의실 소파에서 잤다. 깨우지 않았다. 커피를 또 마셨다. 몇 번째인지 세지 않았다. 슬랙에 팀장 메시지. "아직도 있어요?" "네." "고생하네요. 내일 오전 출근 안 해도 돼요." "알겠습니다." 오전에 안 나가봤자 오후엔 나가야 한다. 똑같다. 새벽 2시. 50%. 창밖을 봤다. 건물 불은 다 꺼졌다. 우리 사무실만 불이 켜져 있었다. 새벽 4시. 75%. 눈이 따가웠다. 안약을 넣었다. 새벽 5시 30분. 95%. 거의 다 왔다. 새벽 6시 10분. Query OK, 3000000 rows affected (14 hours 50 min 23 sec)끝났다. 인덱스가 생성됐다. 이제 세 번째 테이블. products. 5만 건. 5분이면 된다. 실제로는 3분 걸렸다. 데이터 검증을 했다. 문제없다. read-only 모드를 해제했다. SET GLOBAL read_only = OFF;서비스가 복구됐다. 모니터링 대시보드를 확인했다. 정상. 슬랙에 공지를 올렸다. "마이그레이션 완료했습니다. 서비스 정상화됐습니다." CS팀에서 답장. "고생하셨습니다!" 답장을 안 했다. 민수를 깨웠다. "끝났어. 가자." "벌써요? 아, 벌써 아침이네." "응." 그다음 날 오후, 회고 점심때 출근했다. 김치찌개집 사장님이 놀라셨다. "어제 저녁엔 어디 갔어요?" "야근했어요." "힘들겠네. 오늘은 서비스로 계란 하나 더 드릴게요." "감사합니다." 계란을 먹으면서 생각했다. 이게 위로가 되나. 팀 회의가 있었다. 회고 시간. 팀장이 물었다. "이번에 뭐가 문제였죠?" 내가 말했다. "데이터 볼륨을 고려 안 했습니다. 리허설 환경이 실서버와 달랐습니다." "다음엔 어떻게 할 건가요?" "실서버 스냅샷으로 테스트하겠습니다. 그리고 일정을 두 배로 잡겠습니다." "좋네요." 좋긴 뭐가 좋아. 회의가 끝나고 민수가 물었다. "형, 진짜 다음엔 두 배로 잡을 거예요?" "응." "그럼 8시간?" "아니, 16시간." "하루 종일이네요." "주말에 하는 게 낫겠지." "그것도 그렇네요." 거짓말이다. 다음에도 또 4시간을 잡을 것이다. 그리고 또 10시간 넘게 걸릴 것이다. 왜냐면 16시간을 일정에 잡으면 기획팀이 거부할 테니까. "왜 이렇게 오래 걸려요? 간단한 작업 아닌가요?" 그럼 나는 대답할 것이다. "아닙니다. 복잡합니다." 근데 결국 일정은 4시간으로 줄어들 것이다. 회의 끝에. 협의 끝에. 타협 끝에. 그리고 나는 또 믿을 것이다. '이번엔 된다.' 금요일 저녁, 퇴근 6시에 퇴근했다. 정시 퇴근이 이렇게 어색한지 몰랐다. 민수한테 말했다. "너도 가." "네, 형도 푹 쉬세요." "응." 지하철에서 아내한테 문자를 보냈다. "집 가는 중." "진짜? 일찍 오네." "응. 저녁 뭐 먹을래?" "치킨 말고 다른 거." "알았어." 집에 도착했다. 현관문을 열었다. 아내가 맥주를 내밀었다. "고생했어." "응." 맥주를 마셨다. 소파에 앉았다. 넷플릭스를 켰다. 아내가 물었다. "이번엔 왜 늦었어?" "데이터베이스 마이그레이션." "또?" "응, 또." "계획은 몇 시간이었는데?" "4시간." "실제로는?" "16시간." 아내가 웃었다. "매번 그러네." "그러게." "다음엔 더 여유 있게 잡아." "응, 그럴게." 거짓말이다. 다음에도 똑같을 것이다. 계획은 낙관적으로, 현실은 비관적으로. 그게 개발자 인생이니까.다음 마이그레이션은 다음 달이다. 벌써 일정이 잡혔다. 3시간. 웃긴다.

API 응답 시간 최적화로 밤새 격렬한 논쟁

API 응답 시간 최적화로 밤새 격렬한 논쟁

화요일 밤 10시 배포가 끝났다. 그런데 퇴근을 못 한다. 민수가 슬랙에 글을 올렸다. "API 응답 속도 개선안 공유합니다." PDF 파일 하나. 23페이지다. 10시에 이걸 올리나. 열어봤다. 캐싱 전략을 Redis에서 로컬 메모리로 바꾸자는 내용이다. 응답 시간을 300ms에서 50ms로 줄일 수 있다고 한다. 벤치마크 결과까지 첨부했다. 성실하긴 하다. 문제는 이게 아니다. "민수야, 잠깐." "네 형님." "이거 좋긴 한데, 메모리 동기화 이슈는?" "Caffeine 쓰면 괜찮을 것 같은데요." 시작됐다.250ms의 의미 우리 API는 평균 300ms다. 나쁘지 않다. 대부분의 유저는 느끼지 못한다. 그런데 민수는 이게 문제라고 한다. 모바일에서 체감된다고. 특히 3G 환경에서. "형님, 요즘 누가 3G 써요." "통계 봐. 아직 12%야." "그 12%를 위해 아키텍처를 바꿔요?" 할 말은 없다. 그래도 12%는 유저다. 민수의 안은 이렇다. Redis 대신 로컬 메모리 캐시를 쓴다. Caffeine 라이브러리로 TTL 5분. 서버 3대 각각 독립적으로 캐시를 가진다. 장점은 명확하다. 네트워크 홉이 사라진다. Redis까지 왕복 50ms가 없어진다. 데이터 직렬화도 필요 없다. 객체를 그냥 메모리에 둔다. 단점도 명확하다. 캐시 불일치. 서버 A에서 업데이트해도 서버 B는 모른다. 최대 5분간 다른 데이터를 준다. "그 정도는 괜찮지 않아요? Eventually Consistent면 되는데." "민수야, 우리 결제 API도 이거 쓰는데." "아." 침묵. 밤 11시, 화이트보드 회의실로 갔다. 화이트보드를 꺼냈다. 민수가 왼쪽에 그렸다. 현재 아키텍처. WAS 3대, Redis 1대. 모든 요청이 Redis를 거친다. 단순하다. 데이터 일관성도 보장된다. 내가 오른쪽에 그렸다. 민수의 안. WAS 3대, 각각 로컬 캐시. Redis는 그대로 두되 폴백용. 복잡하다. "형님, Redis는 왜 남겨둬요?" "세션이랑 Rate Limiting은 어떻게 할 건데." "아, 맞다." 우리는 30분간 화이트보드를 채웠다. 장단점을 표로 만들었다. 로컬 캐시 장점:응답 속도 250ms 단축 Redis 부하 70% 감소 네트워크 장애 영향 최소화로컬 캐시 단점:캐시 불일치 최대 5분 메모리 사용량 서버당 2GB 증가 배포 시 캐시 워밍업 필요 모니터링 복잡도 증가민수가 펜을 내려놨다. "그래도 해볼 만한데요." 나도 펜을 내려놨다. "어떤 API부터?"데이터를 보자 감이 아니라 숫자로 얘기하기로 했다. Grafana를 켰다. 지난 한 달 API 통계. 상품 조회 API일 호출 500만 건 평균 응답 320ms 데이터 변경 주기: 10분에 1번 캐시 히트율: 89%장바구니 API일 호출 120만 건 평균 응답 280ms 데이터 변경 주기: 실시간 캐시 히트율: 45%주문 내역 API일 호출 80만 건 평균 응답 350ms 데이터 변경 주기: 하루 1~2번 캐시 히트율: 92%민수가 화면을 가리켰다. "이거 보세요. 상품 조회는 10분에 한 번만 바뀌잖아요. 5분 불일치도 괜찮아요." 맞는 말이다. 상품 가격이나 재고가 5분 늦게 반영돼도 큰 문제는 없다. "주문 내역도요. 하루에 한두 번만 바뀌는데 5분이 뭐가 중요해요." 이것도 맞다. 이미 완료된 주문은 거의 안 바뀐다. "그럼 장바구니는?" "이건 Redis 그대로 쓰죠." 합리적이다. 자정 넘어서 계산을 해봤다. 현재 비용:EC2 t3.large 3대: 월 50만원 ElastiCache r6g.large 1대: 월 35만원 총 85만원로컬 캐시 적용 시:EC2 t3.xlarge 3대 필요 (메모리 증가): 월 95만원 ElastiCache 그대로: 월 35만원 총 130만원월 45만원 증가. 연 540만원. "형님, 근데 Redis 스케일 아웃 안 해도 되잖아요." 민수 말대로 현재 Redis CPU가 60%다. 트래픽 더 늘면 스케일 아웃 해야 한다. r6g.xlarge로 올리면 월 70만원. 35만원 증가. 결국 비슷하다. "그럼 속도 개선만 남네." "네. 250ms요." "250ms에 얼마를 지불할 건데." 민수가 계산기를 두드렸다. "개발 2주, 테스트 1주, 모니터링 구축 1주. 한 달이요." 한 달이면 내 인건비 500만원, 민수 인건비 400만원. 총 900만원. "첫 해에 1500만원 쓰는 거네." "네." "250ms를 위해." 침묵이 길어졌다.기획팀 메일 그때 메일이 왔다. 기획팀장이다. "개발팀 귀띔 하나 해드려요. 다음 분기에 모바일 앱 리뉴얼 들어갑니다. API 속도 개선 필요하면 지금 하세요. 예산 나와요." 민수가 웃었다. "타이밍 좋네요." 나도 웃었다. "그러게." 예산이 나온다. 그럼 얘기가 다르다. "민수야, 그럼 이렇게 하자." "네." "1단계는 상품 조회만. 리스크 낮으니까." "좋아요." "2단계는 주문 내역. 한 달 모니터링 후에." "네." "장바구니는 그대로." "알겠습니다." 계획이 나왔다. 단계적 적용. 리스크 분산. 새벽 1시 민수가 일어났다. "형님, 그럼 내일부터 시작할게요." "아니, 모레부터. 내일은 쉬어." "괜찮은데요." "민수야, 지금 몇 시야." "아, 맞다." 민수가 가방을 챙겼다. 나도 컴퓨터를 껐다. 엘리베이터에서 민수가 물었다. "형님, 제 안이 맞았어요?" "반은." "반만요?" "기술적으론 맞았어. 근데 비용 계산을 안 했잖아." 민수가 고개를 끄덕였다. "그게 시니어의 차이군요." "뭔 시니어. 나도 3년 전엔 너처럼 했어." "진짜요?" "응. 캐시를 Memcached에서 Redis로 바꾸자고 했다가 팀장한테 혼났지." 엘리베이터가 1층에 도착했다. 최적화의 함정 집에 가는 택시 안에서 생각했다. 개발자는 최적화를 좋아한다. 300ms를 50ms로 만드는 게 즐겁다. 코드가 빨라지면 기분이 좋다. 근데 회사는 다르다. 비용을 본다. 1500만원으로 250ms를 사는 게 합리적인지 묻는다. 답은 없다. 상황마다 다르다. 트래픽이 많으면 최적화가 돈이 된다. 서버 한 대를 줄일 수 있으면 연 1200만원이 남는다. 그럼 해야 한다. 근데 우리는 아직 그 단계가 아니다. 트래픽은 안정적이다. 서버를 줄일 수준도 아니다. 그래도 하기로 했다. 예산이 나오니까. 결국 타이밍이다. 수요일 아침 출근했다. 민수가 먼저 와 있다. "형님, 어제 생각해봤는데요." "응." "1단계 전에 PoC 먼저 돌려보면 어때요?" "좋지." 민수가 노트북을 열었다. 이미 로컬 환경에 구축했다. Caffeine 설정, TTL 5분, 최대 10000개 캐시. "JMeter로 부하 테스트 돌렸어요." "결과는?" "300ms에서 48ms로 떨어졌어요." 오. 벤치마크보다 좋다. "근데요." "뭔데." "CPU가 15% 올라갔어요. GC 때문에." 역시. 로컬 메모리는 GC 압력을 높인다. 객체가 많아지면 Stop-the-World 시간이 늘어난다. "JVM 옵션 튜닝 필요하겠네." "G1GC를 ZGC로 바꿔볼까요?" "Java 버전은?" "11이요." "그럼 안 돼. 17로 올려야 하는데." 또 일이 늘었다. 결국 민수와 나는 계획을 다시 짰다. 1차: 준비 (2주)Java 17 업그레이드 JVM 옵션 튜닝 PoC 환경 구축 모니터링 대시보드2차: 적용 (2주)상품 조회 API 로컬 캐시 적용 카나리 배포 (트래픽 10%부터) 에러율, 응답시간 모니터링3차: 확장 (4주)주문 내역 API 적용 전체 트래픽 전환 성능 리포트 작성총 8주. 2개월이다. 민수가 Jira 티켓을 만들었다. 나는 팀장에게 보고 메일을 썼다. 기술 부채와 개선의 경계 점심을 먹으면서 민수에게 물었다. "어제 힘들었지?" "괜찮았어요. 재밌었어요." "뭐가?" "형님이랑 같이 고민하는 거요." 민수는 솔직하다. "민수야, 근데 하나 물어볼게." "네." "왜 이 최적화를 하고 싶었어?" 민수가 잠깐 생각했다. "느린 게 싫었어요. 우리가 만든 API가 300ms씩 걸리는 게." "유저는 못 느끼는데?" "그래도요. 더 빠르게 할 수 있잖아요." 이해한다. 나도 7년 전엔 그랬다. "근데 형님은 왜 반대하셨어요?" "반대한 게 아니라, 계산을 하자는 거였어." "비용이요?" "응. 시간, 돈, 리스크." 민수가 고개를 끄덕였다. "시니어가 되면 그런 거 자동으로 생각돼요?" "아니. 실수 많이 해봐야 돼." 나는 김치찌개를 한 숟가락 떴다. 논쟁의 가치 어제 밤은 소모적이지 않았다. 민수는 비용 개념을 배웠다. 나는 새로운 기술을 배웠다. Caffeine 라이브러리는 몰랐다. ZGC 얘기도 민수가 꺼냈다. 좋은 논쟁이었다. 나쁜 논쟁도 있다. 자존심 싸움. 누가 옳은지 증명하려는 싸움. 그런 건 시간 낭비다. 좋은 논쟁은 다르다. 서로 배운다. 더 나은 답을 찾는다. 완벽한 답은 없어도 합리적인 답은 나온다. 어제 우리는 합리적인 답을 찾았다. 로컬 캐시를 쓰되, 단계적으로. 비용을 쓰되, 예산 범위 안에서. 리스크를 지되, 최소화하면서. 목요일 오후 민수가 PR을 올렸다. Java 17 업그레이드 작업. 리뷰했다. Gradle 버전도 올렸다. 라이브러리 호환성도 체크했다. 꼼꼼하다. "Approve." 민수가 머지했다. CI/CD가 돌아간다. 테스트 통과. 빌드 성공. "형님, 개발 서버에 배포할게요." "ㄱㄱ." 10분 후. 슬랙 알람. "개발 서버 배포 완료. Java 17 정상 동작." 시작이다. 2주 후 PoC가 끝났다. 결과는 좋았다. 상품 조회 API (로컬 캐시 적용)평균 응답: 45ms (기존 320ms) CPU 증가: 12% (예상 15%) 메모리 증가: 1.8GB (예상 2GB) 캐시 히트율: 94% (기존 89%) 에러율: 변화 없음민수가 보고서를 만들었다. 그래프도 예뻤다. 기획팀에 보냈다. 답장이 왔다. "좋습니다. 프로덕션 일정 잡아주세요." 금요일 저녁 회식이다. 민수 제안으로 고깃집. 고기를 굽는다. 민수가 소주를 땄다. "형님, 건배." "응." 잔을 부딪쳤다. "형님, 고마워요." "뭐가." "어제 밤에요. 같이 고민해줘서." 나는 소주를 마셨다. "민수야, 나도 고마워." "네?" "좋은 아이디어였어. 덕분에 나도 배웠어." 민수가 웃었다. 우리는 고기를 먹었다. 야근 얘기, 레거시 얘기, 신입 얘기. 개발자들이 모이면 하는 얘기. 편했다. 최적화의 끝 최적화는 끝이 없다. 45ms를 20ms로 만들 수도 있다. CDN을 쓰면 된다. 10ms로 만들 수도 있다. Edge Computing을 쓰면 된다. 근데 안 한다. 비용 때문이다. 어디선가 멈춰야 한다. 합리적인 지점에서. 45ms면 충분하다. 유저도 못 느낀다. 비용도 적당하다. 더 빠르게 할 수 있지만, 하지 않는다. 그게 어른이 되는 거다. 민수도 곧 알게 될 거다. 월요일 이번 주에 프로덕션 배포다. 월요일 밤 10시. 카나리 배포. 트래픽 10%. 화요일 새벽 2시. 모니터링 체크. 수요일 오후. 50%로 확대. 목요일 오전. 100% 전환. 계획은 완벽하다. 근데 계획대로 안 될 거다. 항상 그랬으니까. 괜찮다. 우리는 준비했다. 롤백 플랜도 있다. 모니터링도 빡빡하다. 민수가 물었다. "형님, 떨려요." "나도." "진짜요?" "응. 매번 떨려." 배포는 항상 떨린다. 7년 차도 마찬가지다. 근데 해야 한다. 안 하면 아무것도 안 바뀐다.결국 둘 다 옳았다. 민수의 기술안도, 내 비용 계산도. 그래서 절충했다. 개발은 원래 그런 거다. 완벽한 답은 없고 합리적인 타협만 있다. 어제 밤 논쟁은 시간 낭비가 아니었다. 더 나은 방향을 찾았으니까. 다음 주엔 배포다. 또 떨리겠지.

PR 리뷰 요청이 계속 들어오는 금요일 오후의 진실

PR 리뷰 요청이 계속 들어오는 금요일 오후의 진실

PR 리뷰 요청이 계속 들어오는 금요일 오후의 진실 금요일 2시 슬랙 알림 소리가 울렸다. 뭔가 불길하다. "김개발님, PR 올렸습니다! 봐주세요~" 시계를 본다. 금요일 오후 2시 17분. 배포는 5시까지다. PR 링크를 클릭했다. 변경된 파일 23개. 추가 라인 +847, 삭제 라인 -124. 한숨이 나왔다. 이게 뭔데 금요일 2시에 올리는 건데.제목을 읽는다. "feat: 사용자 권한 체크 로직 개선". 개선이라는 단어가 들어가면 십중팔구 리팩토링이다. 그리고 리팩토링은 십중팔구 기존 로직을 다 뜯어고친 것이다. 커밋 히스토리를 본다. 커밋 하나. "권한 체크 수정". 설명이 전혀 없다. "이거 왜 바꾼 거예요?" 답장이 왔다. "기존 코드가 복잡해서요." 복잡하다는 건 네가 이해 못 했다는 얘기구나. 첫 번째 리뷰 코드를 읽기 시작했다. public boolean hasPermission(User user, Resource resource) { return user.getRoles().stream() .filter(role -> role.isActive()) .anyMatch(role -> resource.getAllowedRoles().contains(role)); }기존 코드다. 그렇게 복잡하지 않다. public boolean hasPermission(User user, Resource resource) { if (user == null) return false; if (resource == null) return false; List<Role> userRoles = user.getRoles(); if (userRoles == null || userRoles.isEmpty()) return false; List<Role> allowedRoles = resource.getAllowedRoles(); if (allowedRoles == null || allowedRoles.isEmpty()) return false; for (Role userRole : userRoles) { if (!userRole.isActive()) continue; if (allowedRoles.contains(userRole)) return true; } return false; }바뀐 코드다. 3배로 길어졌다. null 체크를 추가했다고 한다. 방어 코드래. 문제는 user랑 resource가 이미 @NonNull 어노테이션이 붙어 있다는 거다. "user랑 resource는 null일 수 없는데요." "혹시 모르잖아요." 혹시 모르면 에러가 터져야 정상이다. null을 숨기면 나중에 더 이상한 데서 터진다.코멘트를 달기 시작했다. "1. null 체크 불필요 (이미 @NonNull)" "2. Stream API 제거한 이유?" "3. 테스트 케이스 추가 필요" 그리고 핵심 질문. "4. 기존 로직이랑 뭐가 달라진 건가요?" 사실 아무것도 안 달라졌다. 그냥 코드 스타일만 바꿨다. 두 번째 PR 리뷰를 마치고 자리에서 일어섰다. 커피가 필요하다. 자리로 돌아오는데 슬랙이 또 울렸다. "개발님, PR 올렸어요!" 또다른 후배다. 시계를 본다. 3시 12분. 링크를 클릭했다. 변경 파일 7개. +342, -89. 제목이 "fix: 버그 수정"이다. 어떤 버그인지 안 써놨다. 커밋 메시지를 본다. "버그 수정", "오류 해결", "코드 정리". 도대체 뭘 고친 건지 알 수가 없다. PR 설명란을 본다. 비어있다. "어떤 버그 수정한 거예요?" "아 네트워크 에러요." 어떤 네트워크 에러인데. "어떤 상황에서 발생하는 에러죠?" "음... 가끔요." 가끔이 제일 무섭다. 금요일의 법칙 금요일 오후에는 법칙이 있다.큰 PR은 금요일 2시 이후에 온다. 설명은 없고 "급해요"만 있다. 테스트는 "로컬에서 돌려봤어요"가 전부다. "월요일에 보면 안 될까요?"는 통하지 않는다.왜냐고? "월요일에 배포해야 해서요." 그걸 지금 말하나.세 번째 알림이 왔다. "개발님 이것도 봐주세요 ㅠㅠ" 변경 파일 31개. 시계를 본다. 4시 03분. 배포까지 한 시간. 커피를 한 모금 마셨다. 식었다. 리뷰 전쟁 세 개의 PR을 번갈아 본다. 첫 번째 PR. 후배가 수정을 올렸다. null 체크는 지웠는데 테스트는 안 올렸다. "테스트요?" "아 깜빡했어요. 지금 올릴게요!" 두 번째 PR. 코드를 읽어봤는데 버그 수정이 아니라 기능 변경이다. "이거 기획 확인은 했어요?" "...해야 하나요?" 당연하지. 세 번째 PR. 코드는 괜찮은데 마이그레이션 스크립트가 없다. "DB 스키마 바꿨는데 마이그레이션은요?" "아 그거요. 제가 직접 실행하려고요." 금요일 저녁에 수동으로 SQL 돌린다고? "롤백 계획은?" "...?" 없구나. 4시 47분 배포까지 13분 남았다. 첫 번째 PR은 테스트 추가해서 approve 했다. 두 번째 PR은 기획 확인하고 월요일에 다시 올리래. 세 번째 PR은 마이그레이션 스크립트 만들고 있다. 슬랙에서 CTO가 물었다. "오늘 배포 건들 뭐죠?" 목록을 보낸다. "셋 다 급한가요?" 하나도 안 급하다. 그런데 후배들은 급하다고 한다. "첫 번째 것만 하죠." 후배한테 말한다. "나머지 두 개는 월요일에." "네... 죄송해요." 죄송하면 다음 주에도 금요일 2시에 올릴 거 아니야. 배포가 시작됐다. 5시 03분. 배포 후 배포는 무사히 끝났다. 로그를 확인한다. 에러 없다. 모니터링 대시보드를 본다. 정상이다. 후배한테 메시지를 보낸다. "배포 완료. 다음엔 미리 올려요." "네! 감사합니다!" 감사는 무슨. 내 금요일 저녁을 돌려달라고. 자리에서 일어났다. 6시 15분. 다른 팀원들은 벌써 퇴근했다. 사무실이 조용하다. 모니터를 끈다. 가방을 챙긴다. 엘리베이터를 기다리는데 핸드폰이 울렸다. 슬랙 알림. "개발님, 내일 출근하시나요? 급한 게 생겼어요..." 토요일에는 안 한다. 알림을 끈다. 월요일이 되면 월요일 아침에 출근한다. 후배가 물어본다. "저번 주 금요일 PR 어땠어요?" "금요일 오후엔 올리지 마." "아 네... 근데 이번 주도 금요일에 배포인데요." 그럼 목요일에 올려. "목요일은 회의가 많아서요." 그럼 수요일에. "수요일은 개발하기 바빠서요." 그럼 화요일에. "화요일은... 생각을 정리하는 날이라서요." 결국 금요일 오후에 올린다는 얘기구나. 커피를 마신다. 아아. 차갑다. 다음 금요일도 똑같을 거다. 후배들은 금요일 2시에 PR을 올릴 거고. 나는 그걸 리뷰할 거고. 배포는 6시 넘어서 끝날 거고. 주말은 짧아질 거다. 그게 시니어 개발자의 금요일이다.다음 금요일에도 슬랙 알림은 2시에 울릴 거다. 그리고 나는 또 리뷰할 거다. 뭐.