이 글에 대해서 - 요약
팀스파르타에서 운영하는 항해 플러스 백엔드 5기 과정에 합류했습니다. 문제를 정의하고, 분석하며, 최적의 방식으로 해결하는 능력을 키우는 것이 개발자로서의 성장이라고 생각해요. 10주 동안 다양한 실무 과제를 경험하면서 문제 해결 능력을 강화했어요. 이 글은 과정 5주 차까지의 회고 글입니다.
들어가는 말
10주 몰입으로, 기본기
탄탄한 백엔드 주니어로.
성장하기 위한 항해 플러스 백엔드 과정에 5기로 합류했다.
총 10주 간의 과정 중 이제 절반을 지나가는 시점에서 1~5주 차까지의 회고를 남겨본다.
왜?
시간과 비용을 쓴다는 것은 언제나 무언가를 포기하는 일이다. 그런 의미에서 늘 투자이다. 왜 항해 플러스에 내 미래를 투자했을까?
개발자로서의 성장에 대해서
질문을 던져본다.
개발자로서 성장한다는 것은 무엇일까. 개발자란 무엇일까.
나는 개발자를, 문제를 푸는 사람이라고 정의하고 싶다. 그렇다면 개발자로서의 성장이란 문제를 잘 풀게 되는 것을 의미하지 않을까?
그런데 그 문제란 무엇일까.
좁은 의미에서는 알고리즘이나 자료구조와 같은 정형화된 문제들이 있다. 이런 문제들은 대체로 명확한 답이 있다. 이를 효율적으로 해결하는 능력은 개발자의 기본기 중 하나이다. 하지만 현업에서의 문제는 훨씬 더 넓은 의미를 지닌다.
현업에서의 문제는 기술적인 구현일 수도 있고, 비즈니스의 가치 증진을 위한 과제 해결일 수도 있으며, 사람들과의 협업 문제일 수도 있다. 때로는 문제의 답이 명확하지 않기도 하며, 최선의 선택과 최적의 해결 방안을 찾아야 하는 경우도 많다.
따라서 개발자로서의 실제 문제를 잘 푼다는 것은 현실 세계에서 마주하는 문제를 잘 정의하고, 분석하고, 최적의 해결 방식을 찾아내는 역량을 의미한다. 또한, 문제를 해결하기 전과 후를 비교했을 때 더 나은 가치를 제공할 수 있는 능력을 의미한다.
1년 차+
항해 플러스 5기를 시작하기로 결심한 시기는 2024년 6월이었다. 그 시점의 나는 11개월 차 주니어였다. 직장을 다니고 있었고, 7월 중순 차기 프로젝트 배포를 앞두고 있어서 업무도 나름대로 바빴다.
그러던 중 이런 문구를 보았던 것 같다.
1년 차 업무를 10년 동안 하면 1년 차 같은 10년 차가 된다
어떻게 보면 '겨우' 1년 차였다. 경력의 많고 적음이 꼭 실력은 의미하지 않는다는 것쯤은 충분히 알고 있었다. 신입일 때 이 사실은 무기가 된다. 하지만 1년이라도 쌓이고 보니, 나는 어느덧 '나보다 더 못하는 사람을 가르칠 수 있는 못하는 사람'이 되고 있었다. 그 사실이 이상하게 불편했다.
경력이 쌓이면서 스멀스멀 올라오는 두려움이 있었다. 나는 1년차 같은 2년 차가 되는 것은 아닐까 하는. 성장에 대한 목마름은 1년 전이나 입사 후 1년이 된 지금이나 동일하다. 마치 달콤한 아이스크림처럼 먹으면 먹을수록 갈증이 계속됐다.
링크드인이나 깃허브, 블로그를 거닐다 보면 대단한 경험과 탁월한 사람들을 쉽게 접할 수 있다. 그들을 보며 나의 위치는 어디쯤일까를 끊임없이 비교하고 자신을 검열한다. 사람들과의 비교 어딜 가나 존재한다. 때로는 내 안에서부터 흘러나오기도 하고, 때로는 홍수처럼 바깥에서 들이닥치기도 한다. 그럴 때면 오래 앓아온 익숙한 통증처럼 성장 증후군에 걸려 누가 쫓아오는 것 마냥 나 자신을 몰아세우곤 했다. 하지만 성장하고 싶다는 순수한 갈망은 그저 단순히 문제를 좀 더 잘 풀고 싶었을 뿐이고, 단지 조금 더 중요한 사람이 되고 싶은 마음이다.
개발자라는 직업은 평생 공부해야 한다고들 한다. 나는 그 '평생 공부해야 한다는 사실'이 좋았다. 하지만 때로는 그 사실은 스스로를 끊임없는 성장 지옥에 빠트리는 무지막지한 폭력이 되기도 한다.
문과생으로서 개발자 일을 처음 시작하면서 인상 깊게 읽었던 It 회사에 간 문과 여자라는 책에는 다음과 같이 뼈때리는 구절들이 있다.
더 잘하고 싶은 마음은 병이 되기도 하고, 내가 괴물처럼 느껴지게 하기도 한다.
- 6p
나를 깎아내리려는 것 같은 이들 앞에서 끊임없이 증명하기 위해 바쁘게 지냈다.
- 109p
아무도 시키지 않았는데 성장과 고통은 뗄 수 없다며 스스로를 착취하는 나 자신과 동료들을 보게 된다.
- 193p
개발 일을 하는 사람이라면, 특히나 이제 막 기술을 익히고 집 - 회사 - 코딩 - 집 - 코딩
이라는 loop를 멀티 스레딩 환경에서 무한 가동하며 스스로를 그 반복문 안에 밀어 넣고 있는 사람이라면, 익숙할 말들이다.
그렇게 경계를 넘나들며 존엄한 삶을 매일 어느 정도 유보하고 있는 사람들에게, 특히 경계해야 할 것은 가면 증후군이다.
가면 증후군(Imposter Syndrome)이란, 자신의 능력을 의심하고, 주변 사람들을 속이고 있다는 느낌을 받는 심리 상태를 말한다. 능력이 있고 성과를 내고 있음에도 불구하고, 자신의 성공을 인정하지 못하고 "나는 실력이 없는데 운이 좋았을 뿐"이라는 생각을 하게 된다. 특히 주니어 개발자들 사이에서 흔히 나타난다고 한다. 자신을 과소평가하고 남들과 끊임없이 비교하면서 스스로를 채찍질하게 된다.
어느 정도의 질투와 욕심은 성장에 도움이 되지만, 그것이 지나치면 가면 증후군에 빠지게 된다. 때로는 낮은 자존감과 성장에 대한 압박 때문에 한참을 우울하게 보내기도 한다. 그런 걸 보면 마인드 컨트롤을 잘하는 역량 역시 성장을 이어가는 든든한 근육이 아닐까 싶다.
비교해야 할 것은 오로지 어제의 나다. 성장 자체에 몰입하지 않도록 노력해야 한다. 그럴 때 도움이 된 것은 다음과 같은 김연아 선수의 짤이었다.
김연아 선수가 경기 전, 스트레칭을 할 때 '무슨 생각하세요?'라는 질문에 '그냥 하는 거지'라고 대답한 이 장면이 자주 회자되는 것 같다. 😆
그러니까 중요한 건 꺾였는데도 그냥 하는 마음
.
그냥 한다는 건 정말 멋진 일인 것 같다. 꾸준히 계속 한다는 건.
이렇게 성장이라는 그림자를 아슬아슬하게 쫓아가며 1년 차 개발자라는 타이틀을 얻게 될 즈음, 항해 플러스를 알게 되었다.
무엇을?
항해 플러스 백엔드 과정은 실무 역량 증진에 중점을 둔 10주 과정이다. 실전에서 더욱 빛나는 개발자로 도약할 수 있는 경험을 추구한다고 쓰여 있었다.
앞서 정의한 것처럼, 나는 개발자로서의 문제 해결 역량의 본질은 현실 세계의 문제를 어떻게 그때그때 상황에 맞게 잘 풀 수 있는가에 좌우된다고 생각한다. 이를 달성하는 가장 확실한 방법은 엄청나게 많은 문제를 다양하게 풀어본 경험일 것이다. 그런데 그건 현실적이지 못했다. 왜냐하면 그러자면 10년이고 20년이고 필요할 테니까.
그래서 성장에 있어서의 치트키는 결국 탄탄한 기본기로서 절대적인 input을 주입하되, 이를 바탕으로 한 실전 문제를 풀어보는 것이다,라고 생각한다.
항해의 커리큘럼을 보았을 때 그게 가능하다고 생각했다. 10주는 짧지만 어쩌면 반복적인 업무만 하면서 보내는 n 년의 시간보다 더 밀도 있는 시간이 될 수 있다고.
간단하게 무엇을 다루는지 커리큘럼으로 살펴보면 다음과 같다.
커리큘럼 간단 소개
1~2주차: TDD와 클린 아키텍처 기반 서버 구축
- 대기업의 케이스 시나리오를 바탕으로 TDD(Test-Driven Development)와 클린 아키텍처 원칙을 적용하여 견고하고 유연한 서버를 구축한다.
- 이 기간 동안 TDD, 클린 아키텍처, 레이어드 아키텍처에 대해 배우고, Jest와 JUnit을 활용한 테스트를 진행한다.
3~5주차: 실무 서비스 개발
- 앞서 배운 TDD와 클린 아키텍처 원칙을 실제 서비스 개발에 적용한다.
- 대기업 케이스 시나리오를 기반으로 서버를 구축하고, 다양한 테스트 케이스를 설계 및 구현하여 핵심 기능을 개발한다.
6~9주차: 대용량 트래픽과 데이터 처리
- 대용량 트래픽 처리 및 데이터 정합성 확보를 위한 다양한 전략을 학습한다.
- Redis와 Kafka를 이용한 데이터 처리 및 캐싱 전략을 적용하여 실무에서의 데이터 처리 방안을 체험한다.
- 비동기 처리와 메시지 큐를 활용한 시나리오를 통해 실제 트래픽 처리 방법을 익힌다.
10주차: 장애 대응 훈련
- 실시간 모니터링 시스템을 통해 서버의 장애를 파악하고 대응하는 훈련을 진행한다.
- 고가용성 설계를 위한 SPOF 분석, 다중화, 로드밸런싱, 서킷브레이커 적용 등을 학습한다.
보다 자세한 내용은 항해 플러스 백엔드 랜딩 페이지에서 볼 수 있다! 👆🏻
콘서트 예약 서버와 대기열 시스템
1-2주 차는 기본기를 습득하기 위한 시간이다. TDD, 기본적인 동시성 제어, 테스트 코드, 아키텍처 및 구조 짜기 등을 연습한다.
3주 차에 향후 7주간 수행할 프로젝트를 선정한다. 나는 대기열을 이용한 예약 시스템 프로젝트를 선택했다.
대기열 기반 예약 시스템은 유량 제어 문제를 해결하는 것이 핵심이다.
사용자 요청을 대기열에 넣고 순서대로 처리하며 메인 서버에 유입되는 요청량을 제어한다. 시스템 설계 및 구현 과정에서는 대기열 관리를 통해 사용자 요청을 효율적으로 처리하고, 메인 서버의 부하를 줄이며, 성능 최적화와 데이터 일관성 유지를 목표로 한다. 주요 기능으로는 유저 토큰 발급 API, 예약 가능 날짜 및 좌석 조회 API, 좌석 예약 요청 API, 잔액 충전 및 조회 API, 결제 API 등을 갖는다.
현재 5주 차까지의 구현에서는 우선 정상적으로 돌아가는 프로그램을 만드는 것이 주요 과제였다.
지면상 간략하게 다이어그램과 시스템 아키텍처 구상한 그림으로 소개해본다.
시퀀스 다이어그램
아키텍처 구상도
3주 차에 작성한 그림이라 조금씩 달라진 부분은 있다. 또 프로젝트는 아직 진행 중이기 때문에 앞으로도 달라질 수도 있다. 하지만 큰 틀은 비슷하다!
나의 경우엔 Micro Service Architecture로 시스템을 구상했는데, 그 이유는 다음과 같았다.
그랬다!
이에 대해서, "마이크로 서비스에 대한 우선적인 신뢰보다는 시스템의 안정성을 높일 수 있는 기능 및 구성에 대해서 고려를 더 많이 해보면 좋을 것 같다"는 피드백을 받았다. 사실 제대로 된 마이크로서비스를 하는 회사가 별로 없다고...ㅋㅋㅋ
2주 안에 7개의 서비스를 쳐야 했는데 그때 진짜 후회했다. 내가 미친 짓을 시도했구나... 3일 휴가를 써서 미친 듯이 코딩을 했다. 그 주차에 찍은 학습시간이다.
개인적으로 이때 다시는 MSA로 개인 프로젝트를 하지 않으리... 다짐했는데... 🤑 정말 회사에서라면 n개월에 걸쳐할 일을 2주 만에 해버린 느낌이었다. 간략화된 버전이긴 하지만 확실히 실무에 준할 만큼의 과제 난이도와 양이었다고 생각한다.
어쨌든 우여곡절 끝에 목표했던 카프카 이벤트 통신까지 완수했고, 감사하게도 전부 통과를 받았다! ✊🏻
항해 플러스 100% 활용하기 = 물음표를 최대한 많이 쓰기
각 주차를 통과하면서 많은 궁금증을 가졌다. 다양한 채널을 통해 코치님들 및 동료 개발자들과 질문을 주고받을 수 있었다.
매주 1회씩 멘토님의 공식 발제와 Q&A 세션이 주어진다. 또한 주 1회씩 조별 공식 멘토링 시간이 주어진다. 그 외에도 아고라라는 채널을 통해 자신의 생각과 이슈를 공유할 수 있다. 무엇보다 코치님들과 1:1 DM을 통해서도 소통하며 답변을 받을 수 있다.
지난 5주 간 내가 했던 질문들 중 몇 가지를 추려보았다.
Q. 클린 코드의 기준은 무엇일까?
- 가독성이 좋아야 한다. 예를 들어 영어를 읽듯이 술술 읽혀야 한다.
- 테스트하기 좋아야 한다. 예를 들어 DI 하기 좋은 코드가 테스트하기 좋다. 왜 그럴까? 의존성 주입은 결합도를 낮춰 독립적인 테스트 환경 조성에 기여한다. 또한 mock과 stub 기능을 활용하여 격리된 환경에서 유연한 테스트를 구성할 수 있다. 또 한 가지 예는 SRP 원칙을 잘 지키는 코드가 테스트하기 좋다. 함수가 한 가지 기능만 하면 해당 기능에 집중하여 수월한 테스트가 가능하다.
Q. 동시성을 해결하기 위해 Service Layer에서 synchronized를 사용하여 thread-safe한 방법으로 해결을 하였는데, ReentrantLock와 동시성 컬렉션등 여러 다양한 방법으로 해결이 가능한 것으로 이해했습니다. 이때 실제 업무에 적용한다 하였을 때 일반적으로 권장하는 방식과, 분산 시스템에서 권장하는 방법은?
- 원인을 생각해 보자. 동시성 이슈는 여러 스레드가 동시에 같은 데이터에 접근하여 변경을 수행하려 할 때 발생한다. 이러한 상황에서 데이터의 무결성이 깨지거나 예기치 않은 결과가 발생할 수 있다.
- Synchronized & ReentrantLock :
- 메서드의 수행을 막는 행위: 애플리케이션 수준에서 동시에 메서드가 실행되는 것을 막는 것!
- 이는 모든 변경 작업이 동시에 처리되지 않도록 하는 것입니다.
- 예를 들어, row lock이나 table lock을 통해 동시 실행을 방지할 수 있다.
- 예시: 1초씩 걸리는 메서드가 100개의 요청을 받는다면? 각 요청은 100초!. 즉, 한 요청이 100초를 기다려야 하는 상황이 발생하는 문제
- ConcurrentHashMap 활용:
- Thread-safe 보장: ConcurrentHashMap을 사용하여 스레드 안전성을 보장한다
- 100개의 요청이 각각 다른 100명의 사용자로부터 온다면, 각각의 요청이 1초씩!
- 이것이 곧 분산락 개념으로 이어진다!
- Thread-safe 보장: ConcurrentHashMap을 사용하여 스레드 안전성을 보장한다
Q. 현업에서의 테스트 방법론
a. 회사에서 테스트 코드를 작성하는데 통합 테스트와 인수테스트가 100개 이상 넘어가면 돌아가는 시간이 오래 걸려서 고민입니다.
ci 자동화 과정에서 테스트를 빌드 전에 항상 돌리는데 이때 시간 소요가 오래 걸리는 것이 문제입니다.
혹시 관련해서 어떤 고민을 해보시고 어떻게 해결하셨는지 궁금합니다.
b. 마이크로서비스 간 종단 간 테스트에서, XXXX에서는 개발자의 테스트 범위를 어느 정도 수준까지 하고 있는지 궁금해요.
이벤트 컨슈밍 같이 서비스들이 함께 사용하는 자원들에 대해서도 테스트시에 어떻게 접근하고 관리하는지도 궁금합니다.
Q. 클린 + 레이어드 아키텍처의 단점은?
- 복잡성이 증대된다는 것이 단점인 것 같다. 실무에서 사용하면서 실제로 겪는 어려움은, 같은 레이어 간의 참조 관계가 필요한 경우에 명확한 규칙이 없어 애매해지는 경우가 있는 것 같고, dto 간의 변환에서 손이 많이 간다는 것 등이 있다.
Q. 폴링방식과 소켓 방식 사이의 고민
- 폴링 방식은 시간차가 있어도 문제없는 서비스에 적합하지만, 실시간성을 보장하지 못하고 요청 응답 순서가 보장되지 않는 단점이 있다. 소켓 방식은 실시간성과 요청 응답 순서 보장을 제공하지만, 항상 연결을 유지해야 하는 부담이 있다. 서버 관리 리소스 증가를 초래하며, 소켓 서버가 다운될 경우를 대비해 폴링 방식도 구현해야 한다다. 대기열을 도입하려는 목적은 부하를 방지하기 위해서이며, 정확한 순서를 알 필요 없는 서비스에서는 부정확한 계산을 적용할 수 있다.
Q. 동시성 처리에 대한 고민 -> 발생 가능한 동시성 포인트를 다음과 같이 예측해 보았습니다.
사용자의 여러 동시 요청을 처리하기 위해 멱등성을 보장하는 API를 구현해야 한다. 이는 동일 요청에 대해 항상 같은 결과를 반환해야 함을 의미한다. 구현 방법으로는 식별자를 이용한 분산락이나 데이터베이스 유니크 인덱스 사용을 고려할 수 있다. 예를 들어, 예약 정보 생성 및 결제 정보 생성 시 멱등성을 보장해야 한다. 이를 위해 데이터베이스에 유니크 인덱스를 설정하거나 Redis 캐시를 사용해 요청 정보를 관리한다. 또한, 다수 사용자의 동시 예약 요청에 대해서는 낙관적 락을 사용하여 경합을 줄일 수 있다. 낙관적 락은 상태 변경 시도 시 실패하면 예외를 발생시켜 충돌을 방지한다.
실제 업무를 할 때도 나는 함께 일하는 사람들에게 질문을 많이 하는 편인데, 항해에서도 정말 끊임없이 질문하고 질문했다.
코치님들 모두 물음표 살인마(?!)인 내게 흔쾌히 양질의 답변을 주셔서 정말 감사했다. 🙏🏻
기술적 고민들
항해 플러스에서는 주차 별로 매번 새로운 과제를 푼다. 토요일에 한주의 새로운 과제가 부여되고 다음 주 금요일 오전까지 제출하는 사이클을 갖고 있다.
그래서 매 주차가 다음과 같은 생명주기를 갖고 흘러간다.
While (항해+) {
If (목요일) {
밤샘 코딩 = true;
}
과제제출 = complete;
}
5주 간 과제를 해결하는 과정에서 가졌던 기술적 고민들을 모아보았다. 지면상 디테일한 내용은 링크로 연결해 두었다.
📌 동시성 이슈는 왜 발생하는 것일까?
멀티 스레딩 환경에서는 동시에 여러 요청이 처리되면서 동시성 문제가 발생한다. 예를 들어, 여러 스레드가 동시에 같은 자원에 접근하여 읽기와 쓰기를 수행할 때 데이터 불일치가 발생할 수 있다. 이러한 동시성 이슈를 해결하기 위해서는 락을 사용하거나 CAS(Compare-And-Swap) 알고리즘을 활용하는 등의 방법이 필요하다. 세부 내용 보기
📌 synchronized가 성능에 좋지 않은 이유
synchronized
키워드는 동시성 문제를 해결하는 데 사용되지만, 성능에 부정적인 영향을 미칠 수 있다. 첫째, 불필요하게 모든 접근을 차단하여 성능 저하를 초래한다. 둘째, 자바의 모니터 락은 클래스 수준에서 동작하여 불필요한 대기를 발생시킨다. 이러한 문제를 해결하기 위해서는 더 세분화된 락 관리나 락 프리 접근 방식을 고려해야 한다. 세부 내용 보기
📌 대체키 사용
특정 사용자의 정보 접근 시 식별자가 노출되는 문제를 해결하기 위해 대체키를 도입했다. 대체키를 UUID로 생성하여 클라이언트에 제공함으로써 보안을 강화했다. 데이터베이스에서 대체키를 사용하는 방법과 그로 인한 장단점, 그리고 내부 참조 문제에 대한 해결 방안을 고민했다. 세부 내용 보기
📌 파사드 레이어의 필요성에 대한 고찰
서비스 간 순환 참조 문제를 해결하기 위해 파사드 레이어를 도입했다. 파사드는 고수준 인터페이스를 제공하여 복잡한 시스템을 단순화하고, 서비스 간의 직접 참조를 방지하여 의존성을 관리한다. 파사드의 장점과 단점, 그리고 트랜잭션 관리 문제에 대해 고찰했다. 세부 내용 보기
📌 레이어드 아키텍처의 단점 및 클린 아키텍처의 필요성
레이어드 아키텍처의 의존성 문제와 OCP 위반을 극복하기 위해 클린 아키텍처를 도입했다. 클린 아키텍처는 도메인 중심 설계와 의존성 역전을 통해 유연성과 확장성을 높인다. 프로젝트에서의 패키지 구성 예시와 함께 레이어드 아키텍처와의 차이점을 분석했다. 세부 내용 보기
📌 사용자 요청 이력 저장하기 챌린지
특강 신청 서비스에서 모든 요청 이력을 저장하는 방법을 고민했다. 시도 이력은 비동기로, 성공 이력은 동기적으로 처리하여 데이터 일관성을 보장했다. 비동기 처리의 잠재적 이슈와 이를 해결하기 위한 트랜잭션 관리 방안을 탐구했다. 세부 내용 보기
📌 동시성 이슈 챌린지
동시 예약 요청 시 낙관적 락과 비관적 락을 사용하여 동시성 문제를 해결하려고 시도했다. 낙관적 락의 충돌 문제와 비관적 락의 데드락 문제를 경험하며, 트랜잭션 관리의 복잡성과 성능 문제를 해결하기 위한 다양한 접근 방식을 실험했다. 세부 내용 보기
이 이슈를 해결하면서 삽질을 참 많이 했고, 아고라에 다음과 같은 이슈를 공유했다.
📌 대기열 상태 인지 방법
대기열 상태를 유저에게 전달하는 방법에는 Polling, Server-Sent Events(SSE), WebSocket이 있다. Polling은 구현이 간단하지만 실시간성이 떨어지고, SSE는 서버 부하가 증가할 수 있으며, WebSocket은 양방향 통신을 지원하지만 구현이 복잡하다. 세부 내용 보기
📌 대기열 상태 업데이트 최적화
대기열 상태의 변화를 실시간으로 트래킹 하는 것은 서버에 큰 부하를 줄 수 있다. 이를 해결하기 위해 폴링 간격 최적화, 상태 변경 감지, 이벤트 배치 처리 등의 방법을 고려했다. 세부 내용 보기
📌 대기열 정보의 정확성
대기 순서를 정확히 전달할 필요가 있는지 고민했다. 대기열 데이터는 변동성이 크고 상대적인 정보가 중요하기 때문에, 대략적인 순서나 예상 대기 시간을 제공하는 방법을 고려했다. 세부 내용 보기
📌 콘서트 옵션과 좌석 생성 방식
콘서트 옵션 생성 시점에서 좌석을 생성할지, 예약 요청 시점에서 생성할지 고민했다. 각각의 방식은 성능과 데이터 일관성 측면에서 장단점이 있다. 세부 내용 보기
📌 임시 예약과 본 예약 테이블 분리
임시 예약과 본 예약을 하나의 테이블에서 상태 필드로 관리할지, 별도의 테이블로 분리할지 고민했다. 각 방법의 장단점을 비교한 결과, 테이블을 분리하여 관리하기로 결정했다. 세부 내용 보기
📌 잔액 서비스의 도메인 분리
MSA 아키텍처에서 잔액 서비스를 별도의 도메인 서비스로 분리할지 고민했다. 잔액은 독립적인 비즈니스 로직을 제공하지 않으므로, 현재 시스템에서는 결제 서비스와 통합하여 관리하기로 결정했다. 세부 내용 보기
📌 개략적 규모 측정
시스템의 총 유저 수, 대기열 진입 유저 수, 트래픽 분석, 유량 제어 메커니즘 등을 고려하여 시스템 규모를 측정했다. 이를 통해 예상되는 TPS 및 QPS를 산정하고 필요한 인스턴스 수를 계산했다. 세부 내용 보기
📌 메시지 알림 서비스 설계
비동기 예외 처리 및 알림 시스템을 설계했다. 슬랙을 통해 알림을 지원하며, 알림을 저장해 두고 폴링을 통해 순차적으로 발송하는 방식으로 설계했다. 세부 내용 보기
실험들
그리고 몇 가지 실험을 했다. 슬랙의 아고라를 통해 공유했다.
📌 실험 1: JPA 사용 시 성능 비교
JPA를 사용한 경우와 직접 SQL 쿼리를 사용한 경우의 성능을 비교했다. 테스트 환경은 MacBook M1, MySQL(Docker)로 설정했다.
1,000만 건의 데이터에 대해 Gatling을 사용해 테스트했으며, 결과는 평균 응답 시간이 40~60ms로 큰 차이가 없었다. 하지만 양쪽 모두에서 최대 응답 시간이 3,000ms 이상 기록되는 현상이 있었다. 이에 대한 원인이 참 오리무중이어서 도움을 요청했다.
📌 실험 2: 대기열 순번 조회 최적화
대기열 토큰 순번을 실시간으로 조회하는 방법을 최적화하기 위해 JSON과 AI 방식을 비교했다. JSON 방식은 성능 저하가 심해 실효성이 떨어졌고, AI 방식은 단건 조회 10ms, 10,000건 동시 요청 시 12초로 성능이 우수했다. RDB의 한계를 느껴 Redis와 Protobuf를 사용해 추가 실험을 차후로 기약했다.
📌 실험 3: 필터 로깅 성능 테스트
HTTP 요청과 응답의 파라미터를 필터로 로깅할 때 성능 저하 여부를 테스트했다. 서블릿 요청과 응답을 캐싱하여 로깅하는 방식을 사용했고, 10,000개의 문자를 포함한 요청을 50세트로 반복 테스트했다. 로깅 필터가 활성화된 상태와 비활성화된 상태에서 평균 응답 시간은 거의 동일했다(21ms). 따라서 파라미터 로깅으로 인한 성능 저하는 없다고 판단했다.
성취와 성장
그래서 5주간 나는 성장했을까? 🤔
몇 가지 포인트들을 다음과 같이 되짚어보았다.
테스트 코드
올해 초 ATDD 과정(회고 바로가기)을 들으면서 테스트 코드 작성에 대해 많은 것을 배웠고, 이를 바탕으로 실무에서 좋은 성과를 내기도 했다. 테스트 경험을 공유하기도 하고 회사에서 테스트 문화를 도입해 보려고 나름의 노력들도 했다.
https://renechoi.notion.site/2024-05-BDD-8-5-1000-5a0d6fd2bab647dd8d78b4977a24cd2c
그런데 1년 내내 테스트 짜는 사람은 나 혼자인 사실은 1년 전이나 지금이나 바뀐 게 없다. 그게 좀 외롭기도 했다.
항해 플러스 과정에서는 테스트 코드에 대해 자연스럽게 이야기할 수 있어서 그 자체만으로도 좋았다. '테스트는 당연히 짜는 것'이라는 자연스러운 바닥에서부터 무슨 논의든 시작할 수 있다는 건 마치 축구팀에서 '패스는 기본'을 당연하게 여기는 것처럼 편안함을 준다. 덕분에 테스트에 관련된 고민들에 대해서도 터놓고 이야기할 수 있었다.
특히, 허재 코치님의 답변이 인상 깊었는데 다음과 같이 질문했었다.
질문: "테스트 코드를 보여주셨는데, 모놀리틱 구조에서 여러 팀원이 하나의 프로젝트 소스를 공유할 때, 통합 테스트 시간이 길어지거나, 테스트를 CI에서 통합하여 사용한다면 다른 팀원의 테스트 코드로 인해 빌드가 실패하는 등의 문제가 발생할 수 있을 것 같습니다. 특히 시간이 길어지는 문제가 클 것 같은데, 실제로 어떻게 대처하고 계신지 궁금합니다."
허재 코치님의 답변: "저는 그건 문제가 되지 않는다고 생각합니다. 테스트 커버리지를 위해 많은 테스트 케이스를 구성하는 것은 오히려 안정적이라는 것을 의미합니다. 과연 여러분이 개발하는 서비스가 그 테스트 기간조차 못 기다릴 정도로 급하게 나가서 테스트 없는 불안정성으로 배포된다면 강점을 가질까요? 저는 아니라고 생각합니다. 운영 환경은 개발을 위한 테스트에 소요되는 시간보다 더 장기간 문제없이 유지되어야 합니다. 이때 그 테스트를 위한 시간이 과연 불필요한 시간인가?라는 관점으로 고민하는 편이며, 오히려 다른 사람의 테스트가 실패한 경우 안정성에 문제가 있다는 것을 간접적으로 알려주는 것과 동일하므로 '오히려 좋아'라는 개념으로 팀이 움직이는 게 맞는 방향이라고 생각합니다."
아,,, 정말 사이다. 😂
이 답변을 듣고 '내가 생각한 게 맞았구나' 하는 확신을 가질 수 있었다. 혼자가 아닌 팀으로서 함께 성장하는 것이 얼마나 중요한지 느낄 수 있었다.
좋은 구조와 좋은 코드 → 풍성한 열매를 맺기 위한 씨앗
코드는 중요한가? 중요하지 않은가? 코드를 잘 짜는 사람이 개발을 잘하는 사람은 아닐지라도, 개발을 잘하는 사람은 대체로 코드를 잘 짜는 것 같다.
좋은 코드에 대한 끝없는 고민은 계속된다. 최근에 인상 깊었던 배움은, 서비스 레이어에서 반복문과 if문을 지양하고, 읽기 좋은 코드를 작성하려는 노력이 필요하다는 점이었다. 예를 들어, 서비스 로직을 진행할 때 객체를 위임시켜서 각 역할이 명확하게 드러나도록 하는 것이 중요하다. 이런 접근 방식은 절차적인 코드보다 더 쉽고 직관적일 수 있다.
대기열 서비스를 다음과 같이 구현할 수 있다.
대기열 서비스 {
토큰을발급한다()
토큰을활성화한다()
토큰을만료시킨다()
}
@Transactional
public void active() {
val activeTokenCount = queueRepository.getActiveTokenCount(); // 2850
val 활성화할수있는카운트 = Queue.get남은활성카운트(activeTokenCount);
val waitingTokens = queueRepository.getOldestWaitingToken(활성화할수있는카운트);
waitingTokens
.map(token -> token.active())
.let(queueRepository::saveAll);
}
public void expire() {
val tokens = queueRepository.getActiveOver5min(); // 5분 지난 활성화된 토큰을 들고 온다.
tokens
.map(token -> token.expire())
.let(queueRepository::saveAll);
}
대기열 서비스는 토큰을 발급하고 활성화하며 만료시키는 기능을 수행한다. 만약 이 코드를 if 문과 반복문으로 범벅하면 어떤 절차지향적 코드가 될 수 있을까?
때로는 절차지향적 코드가 필요할 때도 있다고 생각한다. 예를 들어, 어떤 검증을 해야 할 때 지나친 추상화와 극단적인 객체화는 오히려 불편할 수 있다. 절차지향적 코드는 특정 로직을 명확히 표현하고 이해하기 쉬운 형태로 유지할 수 있다는 장점도 있다.
어쨌든 대부분의 경우에 우리가 자바나 코틀린 코드로 구현하는 서비스는 대부분 객체와 상호작용하며 역할과 책임을 적절히 분배할 때, 가독성이 좋고 깔끔한 코드가 된다. 객체지향이 은탄환은 아니다. 탁월한 목적을 성취하기 위한 탁월한 방법이기 때문에 익히고 사용하는 것일 뿐이다.
과제 성취율 → 올 통과! 블루 배지 획득!
항해 플러스의 배지 제도는 단계별 과제 해결을 통해 실력을 인증하는 시스템이다. 주차별 명확한 요구사항을 기반으로 과제를 해결하며, 시니어 코치의 리뷰와 동료들의 해결법을 통해 회고할 수 있다. 수료 시 성과에 따라 다음과 같은 배지를 수여한다.
- 블랙 배지: 상위 1%, 대기업 과제 전형을 무리 없이 통과할 수 있는 실력, 과제 90% 이상 통과
- 레드 배지: 상위 5%, 과제 80% 이상 통과
- 브라운 배지: 상위 15%, 과제 70% 이상 통과
- 퍼플 배지: 상위 30%, 과제 55% 이상 통과
- 블루 배지: 상위 50%, 과제 20% 이상 통과
- 화이트 배지: 상위 90%
지금까지 5번의 과제에서 모두 통과하여 블루 배지를 획득했다. 각 주차의 Best Practice로 선정되어 주어지는 "따봉👍🏻"도 두 번 받았다!
처음 제출한 과제에서 과분한 칭찬을 받았다. 사실 이런 칭찬을 받으면 짜릿하다. 😇
함께 오래갈 수 있는 힘에 대해서
성장은 단계별로 진행된다고 한다.
편안한 영역인 comfort zone에서 시작해, 불안과 두려움의 fear zone을 거쳐 새로운 역량을 확보하는 learning zone과 성취를 이루는 growth zone에 도달한다.
Comfort Zone은 안정성과 익숙함이 있는 공간으로, 개인이 자신감을 가지고 활동할 수 있는 영역이다. 하지만 장기적으로 머물면 성장의 기회를 놓치게 된다. 진정한 성장은 comfort zone을 벗어나 두려움과 불안이 있는 fear zone을 통해 이루어진다.
Fear Zone은 두려움과 불안이 있는 공간으로, 새로운 도전에 직면하게 되는 영역이다. 이 영역을 지나면 새로운 역량을 확보할 수 있는 learning zone에 도달하게 된다. 여기서는 새로운 기술과 지식을 습득하며 성장을 이룬다.
Growth Zone은 지속적인 학습과 자기 계발이 이루어지는 공간으로, 개인이 자신의 능력을 최대한 발휘할 수 있는 영역이다. 이 영역에서는 새로운 도전에 기꺼이 뛰어들고, 실패를 학습의 기회로 삼아 더 큰 성취를 이룰 수 있다.
특히 개발자 성장 곡선은 다음과 같다고 한다.
끊임없이 순항하다가 곤두박질치고 그러다가 또 어느새 보면 성장해 있다.
중요한 건 내가 아무것도 아닌 것 같은 순간 혹은 지금까지 해 온 게 모두 부정당하는 것 같은 순간, 그때 다시 일어설 수 있는 힘이 아닐까 한다.
항해 플러스 과정에서 무엇보다 좋았던 점은 내가 평소에 믿고 있던 개발자의 가치인 1+ 1 = N을 경험할 수 있게 해 준다는 점이다. 이 과정에서 동료들을 만난 것은 큰 힘이 되었다. 서로의 성장을 지켜보고 격려하는 동료가 생긴 것은 큰 축복이다.
매일 게더타운에서 모여 스터디하고 자주 대화하는 시간들을 보내고 있다. 물론 가끔은 잡담하고 노닥거린다! 하지만 말을 하고 듣는다는 것 자체가 개발자로서의 소프트 스킬 향상에 도움이 된다고 생각한다. 특히, 특히 목요일에서 금요일로 넘어가는 날, 과제 제출을 앞두고 새벽 4시가 되도록 불타오르는 게더 타운의 열기는 정말 인상적이고 도전적이다.
그리고 덕분에! 링크드인도 시작하게 되었다. 사실 어떻게 시작할지 막막했는데 함께 성장하는 개발자들 덕분에 할 수 있었다.
아직 5주 차를 통과하고 있어 결론을 내리기는 힘들지만, 항해를 하며 얻는 가장 큰 소득은 개발이라는 일을 함께 하는 좋은 사람들이 아닐까, 생각해 본다.
TBD
남은 5주 차에서는 본격적인 트러블 슈팅과 대규모 트래픽에 대응하는 방법들을 배운다.
남은 주제들을 보면
- 동시성 주제와 극복
- 적은 부하로 트래픽 처리하기
- 대용량 트래픽 & 데이터 처리
- 책임 분리를 통한 애플리케이션 설계
- 장애 대응
먼 길을 또 달려보자.
그리고 중요한 사실을 잊지 말자고 다짐해 본다.
'어제의 나보다만 잘하면 된다!'
항해 플러스 백엔드 과정에 대해 궁금하시거나 고민하신다면 커피챗 환영합니다 😊
현실 이야기 가감없이 전해드립니다...ㅋㅋㅋ
https://open.kakao.com/o/sTp2KQDg
그리고 등록시엔 아래 할인 코드를 사용하시면 20만 원 할인을 받으실 수 있습니다 👏🏻
→ HHPGS0270
'회고' 카테고리의 다른 글
MSA 사가 패턴에서 '사가'라는 용어에 대해서 (0) | 2024.08.11 |
---|---|
1년차 백엔드 개발자의 퇴사 회고 a.k.a 이직 후기 인트로 (3) | 2024.07.26 |
넥스트스텝 ATDD, 클린 코드 with Spring 8기 수료 회고 (1) | 2024.03.15 |
잘하기 보다 자라기 - 0년차 주니어 개발자의 2023년 회고 (0) | 2023.12.30 |
33살 문과 비전공자 국비지원 개발자 신입 취업 후기 (14) | 2023.12.29 |