입사 2개월차 백엔드 개발자의 우당탕탕 개발 성장기

Eunji Shin
당근 테크 블로그
17 min readMar 15, 2024

안녕하세요, 당근 광고실에서 백엔드 인턴을 하고 있는 Woody예요. 출퇴근 피크 타임의 9호선도 달갑게 느껴지던 첫 출근일이 엊그제 같은데, 벌써 당근과 함께한 시간이 2개월을 넘어서고 있네요!

아직은 뉴비 타이틀을 벗지 못했지만, 2개월 동안 참 많은 것들을 경험한 것 같아요. 난생 처음으로 쏟아지는 트래픽을 받아내보고, 장애 대응을 위해 모니터링 대시보드를 뚫어져라 쳐다보기도 했죠. 동료들에게 내가 하고 있는 일, 또는 고민 중인 일을 어떻게 해야 잘 전달할 수 있을까 고민하기도 했어요. 그리고 고민한 만큼, 제 나름대로의 깨달음도 얻을 수 있었구요.

이 경험으로부터 배운 것들을 제가 잊지 않고 되새기기 위해서, 그리고 당근에서의 인턴 생활이 궁금한 누군가에게 공유하기 위해서 블로그를 작성해보아요. 😎

📝 꼼꼼할수록 빨라지는 개발 속도

우당탕탕 온보딩 기간이 지나고, 본격적으로 개발 프로젝트를 맡게 되었어요🤩 당근의 각종 페이지에 내보낼 여러 외부 매체의 광고를 내부 정책에 따라 혼합하여 노출하는 프로젝트로, 유저에게 더 적절한 광고를 추천하는 것을 목표로 기획된 기능이에요.

현재 운영 배포가 완료되어, 당근 어플에서 확인하실 수 있어요!

언뜻 보면 간단해 보이는 기능이지만, 당근 내부의 정책과 외부 매체의 정책 모두를 고려해야 하기에 생각보다 까다로운 작업이었어요. 또 이전에 구현되었던 기능을 활용해야 하기에 도메인 지식과 레거시 코드의 파악도 필요했구요. (설상가상으로 저는 팀의 메인 기술 스택인 Kotlin도 익숙하지 않은 상황이었어요. 😅)

혹시라도 일정을 맞추지 못할까 마음이 급해진 저는, 기능 정리 미팅 이후 유저 시나리오를 기반으로 TO-DO 리스트를 만들고 후다닥 개발을 시작했어요.

하지만 좋지 못한 선택이었다..! 🔫 분명 해야 할 일들을 몽땅 정리해두고 개발을 시작했는데, 하면 할수록 계속 새로운 할 일이 생겼어요. 직접 코드를 짜면서 보니, 요구사항에 정의되지 않은 엣지 케이스도 여럿 보였죠. 새로운 요구사항이 이전 레거시 코드의 동작과 충돌하는 경우도 있었어요.

분명 TO-DO 리스트를 꼼꼼히 짜두고 개발을 시작했는데, 왜 이런 일이 발생했을까요? 제가 내린 결론은, 무엇을 해야 하는가에 대한 계획만 세웠지 그 TO-DO를 어떻게 구현할 것인가에 대한 고민이 부족했다는 것이었어요.

예를 들어, 여러 외부 매체에 광고를 요청한다는 TO-DO를 정했다고 가정할게요. 그럼 실제로 코드를 짤 때는 이런 것들도 고민해보아야 할 거예요.

  • 각 매체에 요청을 동시에 보낼 것인지, 순차적으로 보낼 것인지
  • 요청을 보낸 매체 중 일부에서 에러가 발생하면 어떻게 처리할 것인지
  • 응답받은 광고를 유저에게 어떻게 노출할 것인지 — UI 템플릿, 노출 가능한 최소 개수 제한 등

이런 수많은 경우의 수들을 매번 코드를 짤 때마다 고민하다 보니, 진도가 쭉쭉 나가지 않고 찔끔찔끔 나가게 되었던 것이죠.

그럼 어떻게 해결할 수 있을까요?

저는 테크 스펙 문서와 테크 싱크 미팅을 백분 활용하여 이 문제를 해결해보았어요. 테크 스펙이란 기능을 어떻게 구현할 것인지 기술적으로 풀어서 설명하는 기능 구현서인데요. 당근은 엔지니어들의 테크 스펙 작성을 권장하며, 예시 템플릿도 공유하고 있어요.

테크 스펙을 작성하니 할 일을 구체적으로 파악할 수 있는 것은 물론, 기능 개발을 시작하기 전에 동료들과 구현 방향을 함께 고민해볼 수 있었어요 👍

전체적인 코드 구조(포트-어댑터 등), 클래스 관계 등 코드 리뷰 단계에서 피드백을 받으면 너무 많이 수정해야해서 다음 티켓으로 미루는 상황, 한번쯤 겪어보시지 않으셨나요? 구조 관련 설계, 기능 구현에 사용할 기술 등 큼지막한 요소들은 테크 스펙 단계에서 미리 구성원들과 논의하고 의사 결정을 진행해두면 이후 크게 수정할 가능성을 줄일 수 있어요. 이를 통해 업무 효율성도 높일 수 있고, 일정도 관리할 수 있었답니다. 😎

광고실 뉴비인 제가 도메인 이해도가 부족해 반례를 놓치더라도, 테크 스펙을 통해 동료들이 확인해 줄 수 있다는 점도 장점이에요. 테크 스펙… 정말로 든든했습니다…

사실 처음에는 빨리 코드 작업에 들어가고 싶다는 조바심에 테크 스펙을 크게 활용하지 못했어요. 하지만 돌이켜보니, 코드 작업 중 우왕좌왕하며 시간을 허비하느라 결국 실제 개발 시간은 크게 차이가 나지 않더군요..! 오히려 테크 스펙을 꼼꼼히 쓰면서 구현 방향성에 대해 고민해보고, 더 좋은 방법을 찾고, 동료들과 의견을 나누는 것이 훨씬 생산성을 높여주었어요. 훨씬 더 꼼꼼히 살펴보았는데 오히려 속도가 빨라졌다니, 신기하지 않나요? 🤩

만약 요구사항이 정리되었는데도 개발 시간이 길어지거나, 코드 작업 도중 이런저런 고민이 많이 생기는 편이라면 테크 스펙을 작성하며 대략적인 개발 사항을 설계해보고, 팀원들과 싱크를 맞춰보는 것을 추천드려요.

⚠️ 물론 실제로 코드를 작성하다 보면, 어쩔 수 없이 문서와 싱크가 맞지 않는 경우도 왕왕 발생해요. 따라서 문서화에 너무 집착하거나 과도하게 많은 시간을 소비하지 않도록 경계하는 태도도 필요합니다.

🔎 사람을 믿지 말고 데이터로 검증하자

테크 스펙을 통해 설계하고, 그를 바탕으로 잘 동작하는 코드를 만들었다면 이제 실제 사용자가 있는 운영 환경으로 배포해야겠죠? 하지만 보통은 바로 배포하지 않아요. 코드가 컴파일을 통과하고 어플리케이션이 실행에 성공하는 것으로는 이 기능들이 정상적으로 동작하는지 확인할 수 없기 때문이죠. 그래서 당근은 프로덕션 배포 이전에 프로젝트 관계자들과 QA를 거치고 있어요.

그런데, QA에서 아무 문제가 없었다고 그게 안전한 코드일까요? 저는 QA를 충분히 꼼꼼하게 한다면 모든 기능 동작과 예외 상황을 테스트할 수 있을 것이라고 생각했어요. 하지만 현실은 무자비했습니다..

당근 UI상, 화면에는 상품을 판매하는 마켓 이름만 표시하고, 해당 마켓을 중개하는 플랫폼 이름은 표시하지 않는 매체도 존재하기 때문에 섞이면 안 되는 케이스를 모두 걸러내고 있는지 확인하기 힘든 문제가 생겼어요. 또 이후로는 광고를 매번 보여주는 것은 아니기에, 기대하는 결과를 확인하기가 힘들었죠. 운영 환경과 다르게 QA 상황에선 request 수와 데이터 양이 적어 원하는 시나리오를 재현할 수 없었어요. 🤔

또한 기능의 동작 여부는 사람이 확인할 수 있으나, 서버의 성능은 사람이 검증하기 어려웠어요. 1초도 순식간에 지나가는데 ms의 단위의 시간은 말할 것도 없죠. 그래서 저희는 사람의 확인을 믿기보다는, 데이터를 통해 정량적인 수치를 기반으로 객관적인 검증을 하기로 했어요.

가장 객관적인 감시자, 테스트 코드

여러분은 테스트 코드를 좋아하시나요? 저는.. 당근에 오고 나서 너무나도 좋아하게 되었습니다.. 이전에는 일정을 지키는게 우선이니까, 테스트 코드 없어도 동작하는 거 눈으로 확인하면 되니까 하며 스스로를 합리화하곤 했어요. 하지만 기능이 복잡해지고 규모가 커지니 얘기가 달라졌어요. (휴먼 컴파일, 하지만 빨랐죠 불가능) 안전하지 못한 코드를 일정 내에 내보내더라도 그게 의미가 있을까? 에 대한 생각도 하게 되었구요.

당근에 합류하고 수십명의 개발자가 기여한 레포지토리에서 개발을 하며, 시스템이 커질수록, 코드 양이 많아질수록 테스트 코드는 필수로 작성해야 한다는 것을 체감할 수 있었어요. 새로운 코드를 작성한 후, 또는 기존 코드를 수정한 후 직접 하나하나 실행해 볼 필요 없이 테스트를 돌리는 것만으로도 결과를 확인할 수 있어 신뢰가 생기기 때문이에요. (물론 테스트에 통과하더라도 직접 결과를 확인하는 과정이 필요해요 👀)

또, 테스트 코드는 사람의 실수를 줄여준다는 점에서도 유용하다고 생각해요. 예를 들어, 심각한 장애가 발생해서 급하게 hotfix 배포가 필요하다고 가정해볼게요. 실시간으로 슬랙 채널에 알림이 수십 개씩 날아오고, 매출이 급락하는게 보이고, 주변에서 헉! 아! 안돼! 하고 있다면 과연 집중이 될까요..?! 빨리 해결해야겠다는 생각에 허둥지둥 눈으로 코드를 확인하다보면 분명 실수하고, 분명 놓치는 것이 생겨요. 하지만 테스트 코드가 이미 작성되어 있다면, 개발자의 멘탈과 무관하게 컴퓨터가 안정적으로 모든 케이스를 검증해주죠. 🖥️

테스트 코드의 제일 큰 장점은 문제에 대한 검증을 시스템적으로 풀어나갈 수 있다는 점인 것 같아요. CI를 통해 매번 테스트를 강제하도록 자동화할 수도 있고, 테스트에 실패하면 배포를 못 하도록 안전 장치를 마련할 수도 있죠. 개발자가 매번 코드를 실행하고 이리저리 디버깅 할 필요 없이, 한번 테스트 코드만 잘 짜두면 팀 구성원 모두가 테스트 코드가 제공하는 편리함을 누릴 수 있어요.

친절하게 메일도 보내주는 github action

이렇게 중요한 테스트, 혹시 기능을 개발하면서 무작정 작성하고 있진 않으신가요? 얼마 전까지의 제가 그랬습니다 😇 기능을 먼저 구현한 다음, 유저 시나리오 바탕으로 바로 유닛 테스트 코드를 작성하곤 했죠. 하지만 계획 없이 테스트 코드를 작성하다보니, 기능이 커질 수록 테스트할 항목이 너무 많고 복잡해져 모든 케이스를 커버한 것인지 확신이 서지 않았어요. 그러다 보니 분명 테스트를 성공했는데도 이후 누락된 케이스에서 예외가 발생하는 슬픈 상황도 생겼죠.

위 경험에서 단순 기능 요구사항뿐 아니라, 제 코드가 동작하는 과정에서 발생할 것으로 예상되는 상황들에 대해서도 테스트가 필요하다는 것을 배울 수 있었어요. 기능이 돌아간다! 에서 만족하는 것이 아니라, 제 코드가 광고 도메인 정책, 당근 내부 정책을 어기거나 영향을 주고 있지는 않은지에 대해서도 고민해보아야 해요. 또한, 지연시간이 길어지는 것을 방지하기 위해 time-out 옵션을 필수로 설정하고 있기에 타임아웃 관련 테스트도 필요하죠.

이런 상황들을 꼼꼼히 테스트하기 위해, 테스트 시나리오를 성공과 실패로 나누어 각각의 케이스를 구체화하는 과정이 필요했어요.

그래서 테크 스펙 단계에서 동료들과 함께 작성한 유저 시나리오 프로세스를 보며, 발생 가능한 상황들을 먼저 정리해보았어요. 이후 각 상황에서 위에서 구성한 프로세스가 돌아간다면 각각의 단계에서 어떤 예외가 발생할 수 있을지에 대해 고민해보았죠.

이렇게 비즈니스 로직과 관련된 반례들을 정리한 다음에는 코드에 대한 엣지 케이스도 찾아보아야 해요. 위에서 언급한 타임아웃 이슈와 같은, 코드 구조상에서 발생할 수 있는 문제들을 고민하며 테스트 시나리오를 추가했어요.

이렇게 시나리오를 구성하고 테스트 코드를 작성하니, QA 완성도가 높아지는 것은 물론, 기능 완성도도 높일 수 있었어요 🤩 고민하는 과정에서 누락된 예외 처리와 코드 레벨의 개선점을 찾을 수 있었기 때문이죠. 장하다 테스트 코드!

그럼 이제 정말 끝인가요? 아니오! 모니터링 해야 해요!

이렇게까지 열심히 확인했으니 이제는 문제 없겠지?! 🤩 행복한 마음과 자신감으로 가득차서 운영에 롤아웃 배포를 하는 순간 신나게 울려버리는 alerts 채널… 2024년 들어 제일 무서웠던 순간이었어요.

‘다른 이슈인가?’ 할 건덕지도 남겨주지 않고, 딱 롤아웃으로 배포한 서버 두 대에서만 리소스 지표가 미친듯이 뛰었어요. 분명히 내 배포가 원인이다… 일단 빠르게 롤백 한 후, 코드를 다시 한 번 뜯어보았어요. 하지만 눈으로 보았을 땐 대체 어디서 리소스를 차지하는 건지 확인하기 힘들었어요. 심지어 코드 결과 자체는 의도한대로 동작하는 게 맞았기에 디버깅도 소용이 없었죠.

제일 우측의 우뚝 솟은 파란색 그래프… 보이시나요 😇

정말 슬픈 사실은, 혹시 이런 상황이 발생할까봐 알파에서 이미 부하 테스트를 진행했었다는 점이에요 🥲 피크 타임때의 트래픽과 그때의 pod 수를 이용해 pod 한 대당 처리해야 할 트래픽 수를 계산하여 k6 시나리오를 구성하고 테스트를 진행했어요.

p95도 타임아웃 범주 안이고, 실패한 요청도 거의 없었기에 문제가 없다고 판단했죠. 하지만 운영에 배포하니 장렬하게 패배..

왜 이런 일이 발생했을까요? alpha 서버와 production 서버는 동일한 환경을 갖춘 것처럼 보여도 실제로는 다르기 때문이에요. 프로덕션은 실제 살아있는 사용자가 끊임없이, 다양한 방식으로 요청을 보내죠. 알파 서버에서 프로덕션이 받는 부하를 가장하여 부하 테스트를 진행하더라도, 실제 환경과 유사한 패턴의 시나리오를 구성하지 못했다면 동일한 코드라도 프로덕션에선 장애가 발생할 수 있어요.

또한, 제가 입사한 후 당근이 무럭무럭 자라난 영향도 있었어요. MAU 1800만의 당근은 2024년에도 꾸준히 성장하며 어제보다 오늘 더 더욱 많은 트래픽을 경험하고 있어요. 다르게 말하면 pod 한 대가 처리해야 하는 할당량이 더 많아졌다는 의미죠 🤔 (서비스가 성장한 것을 이렇게 느끼고 싶지는 않았는데..) 따라서 pod 한대의 처리량이 이미 아슬아슬한 상황이었다는 점도 고려해야 했어요.

요청이 쭉쭉 밀리는 그래프

모니터링을 해보니 CPU가 튄다는 점, 그리고 요청이 밀리며 타임아웃 양이 급증했다는 점알 수 있었어요. 이를 기준으로 디버깅하여 원인을 찾을 수 있었어요. 범인은 바로 코루틴이었습니다 😇

여러 외부 매체에 광고를 요청 (= HTTP request)하고 응답을 대기하는 과정이 동기적으로 실행되면 그만큼 전체 레이턴시가 길어지므로 코루틴을 이용해 비동기로 요청을 병렬적으로 처리하려 했어요. 하지만 각각의 코루틴을 서로 다른 스레드에 배정하였기에 특정 매체에서 time out이 발생해도 예상과 다르게 자동으로 요청이 cancel되지 않았죠. 따라서 커넥션 타임이 길어지고 리소스 점유율도 높아지는 이슈가 발생했어요.

로직을 수정하고, 스레드풀과 파드 수를 조정하고 나니, 와 🎊 피크 타임에도 안정적인 리소스 추이를 확인할 수 있었어요!

보기만 해도 평온해지는 그린 그래프

이 경험을 통해 로그와 메트릭 수집을 통한 모니터링이 얼마나 중요한지, 꼼꼼한 alert 설정은 또 얼마나 중요한지 배울 수 있었어요. 배포한 직후 알림이 왔으니 바로 롤백할 수 있었지, 알림이 오지 않았다면 그대로 롤아웃 100% 를 진행하며 장애 규모가 훨씬 커졌을 거에요.

또, 그라파나 등을 활용해 리소스 모니터링 시스템을 구성해두지 않았다면 어디가 문제인지, 어딜 고쳐야할지 찾는데 더 오랜 시간이 걸렸겠죠. 지금까지는 배포 후 에러 알림은 없는지 정도만 봤다면, 이제는 JVM 등 다양한 리소스 추이를 시간 잡고 길게 길게 모니터링하는 습관이 생겼답니다 💪

새삼 도와주신 광고실 동료분들께 무한..감사..! 🫶

✨ 성장을 위한 원동력, 회고하기

이렇게 하나의 프로젝트를 개발하고, 배포하고, 장애를 해결하기까지 했지만 여기서 끝이 아니에요. 이 경험을 돌아보며 우리가 무엇 때문에 실수했고, 무엇 덕분에 헤매는 시간을 줄일 수 있었는지에 대해 고민해야 다음에 비슷한 실수를 반복하지 않을거에요.

당근에서 인상 깊었던 점은 회고하고 개선하려는 문화가 잘 자리 잡았다는 것이었어요. 구성원들은 회의를 하고, 결론을 내는 것에서 만족하지 않고 이번 논의에서 무엇이 문제였는지, 이를 개선하기 위해 다음에 어떤 action을 취하면 좋을지에 대해 의견을 나눠요. 그걸 함께 지켜보며 참 좋은 문화라고 생각했어요 👍 그런 의미로 제가 당근에서 경험한 회고 경험 두 가지를 소개할게요.

장애 일지, 그리고 장애 회고

당근은 운영 환경에서 장애가 발생한 경우, 장애를 해결한 후 장애 일지를 통해 구성원들에게 원인과 해결 방법을 공유하고 있어요. 그리고 개인적으로 당근 장애 일지의 꽃은 바로 5 Whys라고 생각해요.

5 Whys란 이 장애가 왜 발생한 것인지 5가지의 why를 통해 하나하나 되짚으며 문제를 리마인드하고, 그로부터 장애 핵심 원인을 찾아가는 과정이에요. 저는 일지를 작성하며 저만의 5 Whys를 적고, 장애 회고 미팅에서 그걸 팀원들과 공유했어요. 이후 팀원들도 각자의 5 Whys를 공유하며 서로가 놓친 점, 팀 단위에서 시도해야 할 점 등을 정리해요.

원인에 대한 파악과 논의가 완료되었다면, 장애 악화, 장애 영향도 감소, 장애를 통해 배운 것들에 대해 의견을 나눠요. 여기서 앞으로 고민해야 할 점, 시스템적으로 개선해야 할 점, 팀 내 새로운 컨벤션 등에 대해 의견을 나누고 다음 경험에 적용하고 있어요.

이렇게 장애 일지를 작성하는 과정을 통해 장애 원인에 대해 다각도에서 접근하고 분석할 수 있었어요. 또 주니어 개발자의 입장에선 서버 개발을 할 때 신경써야 할 점들에 대해서도 배울 수도 있었죠. 장애는 물론 쓴 경험이었으나 🥲 이 경험을 통해 배운 것들은 분명 값진 것이라 생각해요. 서로에 대한 믿음을 키우며 더 단단한 팀이 된 것 또한 자랑스럽구요 💪

이번 달은 우리 어땠을까? 파트 회고하기

다음으로 공유할 회고 문화는 저희 파트가 매달 진행하는 파트 회고에요. 제가 소속된 광고추천팀 서빙파트는 매달 회고를 통해 개발/개발 외적으로 팀을 돌아보고 있어요. 아래 사진처럼 Good/Bad/Start/Stop 총 4가지 항목으로 각자 회고를 진행한 후, 그를 바탕으로 다음 달의 Action Item들을 정해보고 있죠.

모자이크 안 된 부분에 주목해주세요 🥰

이렇게 서로가 느낀 점에 대해 툭 터놓고 이야기하며 서로에게 공감하고, 친근한 분위기를 형성할 수 있었어요. 업무 시간에 이런 미팅을 진행하는 게, 어찌보면 워킹 아워를 줄이고 업무에 집중할 수 있는 시간을 빼앗는 것처럼 느껴질 수 있어요. 하지만 이 시간을 적극적으로 활용하여, 개인 그리고 팀의 성장을 일궈내는 것은 팀워크를 높이는 중요한 원동력이라고 생각해요.

그리고 이 파트 회고에서 파생된 새로운 팀 문화가 하나 더 있는데요, 바로 서빙파트 개발 이야기 문화입니다 🥳

팀 회고와 별개로, 팀 그리고 나아가 당근 전체의 목표를 달성하기 위해, 또는 평소 일하는 방식을 개선하기 위해 개발자로서 무엇을 할 수 있을지 고민하는 시간이에요. 이런 이야기를 나누면서 서로의 업무를 공유하기도 하고, 더 효율적인 개발 환경을 만들기 위해 새로운 액션을 취하기도 하고 있어요.

이렇게 적극적으로 회고하고 주도적으로 일하면서, 우리가 개발하는 서비스를 보다 깊게 이해할 수 있었고, 동시에 유저에게 더 질 좋은 광고를 제공하고자 하는 목표를 세울 수 있었어요 💪

이렇게 제가 인턴 기간 중 겪었던 경험 일부를 소개해드렸는데, 어떠셨나요? 어찌 보면 너무나 초보적인 실수들이 가득해보일 수 있지만, 제 경험을 솔직하게 공유하며 배운 것들을 나누고 싶었어요.

겨우 2달밖에 지나지 않았지만, 지금의 저와 입사 전의 저를 비교해보면 일하는 방식도, 생각하는 방식도 많이 달라진 것 같아요. 제가 입사한 후 티타임 때 버디가 해주셨던 말이 있어요. (놀랍게도 다른 팀원분도 비슷한 말을 해주셨구요!)

모든 사람에겐 처음이 있고, 항상 처음엔 실수할 수밖에 없다.
중요한 것은 실수에서 배우는 것이다.

아직까지도 제일 기억에 남는 말이에요. 이 글의 제목을 우당탕탕 성장기로 정한 것은, 정말 매일매일이 실수 투성이고 정신 없지만, 그 속에서 성장하는 자신을 마주할 수 있었기 때문이랍니다.

처음엔 실수할 때마다 큰 일이 난 것 같고, 머리가 새하얘지고, 망했다는 생각만 들었어요. 하지만 요즘은 그럴 수 있지, 대신 똑같은 실수를 다시는 반복하지는 말자는 생각을 가지고 다음 액션을 취하고 있어요.

제가 준비한 이야기는 여기까지예요.

저와 비슷한 연차, 또는 개발자가 되기를 희망하는 분들께 이 글이 어떤 모티베이션이 되었다면 무엇보다 기쁠 거예요.

그럼 글을 마치며, 이 글을 읽는 모든 분들의 실수와, 그 실수에서 만들어 갈 성장을 응원할게요. 기회가 된다면 다음 포스팅에서 다시 만나요.

읽어주셔서 감사해요! 👋

--

--