차가운 개발자 채용 시장
이직을 시작하기 전부터 많은 개발자분들에게 피드백을 받으며 이력서를 어느 정도 완성단계에 있었기 때문에 이력서는 금방 정리가 되었다. 그래서 일단 채용 시장이 어느 정도 난이도인지 몸소 체감해 보기 위해 괜찮다고 생각되는 기업을 골라 수많은 기업들을 지원했었다. (혁신의 숲, 블라인드, 잡플래닛 등을 활용)
채용시장은 예상대로 차가웠고 원티드 25건, 프로그래머스 8건, 리멤버 8건, 기타 3건 등을 포함하여 총 44건의 지원 중에 단 6곳정도밖에 서류를 통과하지 못했다. '6건이라도 통과한 것이 어디야?'라고 생각할 수도 있겠지만 생각했었던 서류 합격률의 약 절반 정도인 13.64% 되지 않았기에 어떤 점이 문제인지 가설을 세워보기로 분석하기로 했다.
첫 번째 자격 요건의 맞지 않은 조건
위의 내용들은 GPT-4o를 통해 내가 가고 싶은 또는 지원했었던 공고들의 자격내용들을 추려서 정리한 내용들이다. 여기서 내가 충족하지 못한 내용들을 뽑으라고 한다면 아래와 같이 뽑을 수 있을 것이다.
충족하지 못한 자격 요건
- NoSQL 사용 경험
- MSA 운영 경험
- Kubernetes 사용 경험
- Iac 활용 경험
- Python, Node.js, Go 등을 활용하여 백엔드 애플리케이션을 운영한 경험(Java와 Ruby 경험만 보유)
- Event Driven Architecture 경험
- 컴퓨터공학 전공
위에 조건을 제외하고는 다른 조건들은 충족하고 있었기에 자신감 넘치게 여기저기 지원해 봤지만 결과는 참패였다. 여기서 내가 깨달은 점 중 하나는 아래 조건을 충족하지 않은 공고는 다른 공고에 비해 빠른 불합격 소식을 들었다는 것이다.
- 현재 회사 기술스택의 경험이 없는 공고(Python, Node.js, Go 등)
- 마이크로서비스를 구축하거나 설계한 경험을 요구하는 공고(EDA, Kubernetes 등)
- 경력이 맞지 않은 공고(5년 이상, 6년 이상)
- 회사에서 원하는 직무 경험이 없는 경우 (AI, 검색 엔진, DevOps, 블록체인 등)
당연한 결과라고 생각할 수 있겠지만 IT 기업들은 예전처럼 개발 경험만 가지고 있는 개발자를 바로 채용하지 않는 상황이다. 기업들은 이제는 인재를 채용했을 때 들어가는 비용은 최소화하길 바라기 때문에 현재 회사 기술 스택에 익숙하거나 현재 필요한 업무의 관련 직무 경험을 가지고 있는 개발자들이 아니면 서류 단계에서 많이 탈락시키는 추세를 보이고 있다.
IT 호황기에 기업 인사팀은 많은 개발 인력 확보가 곧 성과였지만 지금은 효율적인 인력 운영을 위해 최소 비용으로 바로 실무에 투입할 수 있는 인재를 확보하는 것이 성과로 바뀌었다. 그리고 스타트업 생태계에서는 한명의 개발자에게 포지션의 역활을 수행할 수 있는 능력뿐만 아니라 관련 도메인 경험, 데이터 분석 능력, 풀스택 개발, 인프라 운영 등 다양한 역량을 요구하고 있는 상황이다.
두 번째 부족한 메타인지
위의 자기소개는 나의 이력서에서 나 자신을 소개하는 내용이다. 어떤 사람은 잘 썼다고 생각할 수 있겠지만 어떤 사람은 이런 강점은 우리가 찾고 있는 인재상과 멀다고 생각할 수 있다. 위의 자기소개를 자세히 살펴보면 "운영, 업무 프로세스, 이슈 대응 등 다양한 측면에서 효율화", "문서 중심 커뮤니케이션 문화 도입", "지표 수집 자동화" 등 4년 차 백엔드 개발자의 필요한 역량이라고 보기에는 어렵다고 볼 수 있다고 이야기할 수 있다.
기업 입장에서는 4년 차 백엔드 개발자에게 요구하는 역량은 프로그래밍 언어와 프레임워크에 대한 깊은 이해, 문제 해결 능력, 구현 능력, 프로젝트 관리 능력, 커뮤니케이션 능력, 시스템 디자인 등의 능력들을 더 필요로 하지만 나의 자기소개는 그런 역량들보다는 오히려 리더나 Manager가 갖춰야 하는 역량에 가까웠고 이런 부분이 내 이력서에 대한 흥미를 떨어트린 것 같다.
연차별 역량 참고
https://blog.naver.com/jhc9639/223521476080
부족한 근거
원티드의 이력서 챌린지를 진행하던 중에 강사님이 현재 채용시장에 대해 분석하신 데이터를 근거로 이야기를 해주셨는데 소개해주신 지표와 말씀을 듣고 머리를 크게 한 대 맞은 듯했다. 요약하자면 다음과 같다. "현재 채용 인원은 줄어들고 지원서는 많아진 상황이다. 그래서 인사팀의 KPI는 채용 인원을 늘리고 근속 연수 길게 만드는 것에서 채용 비용을 줄이고 채용하는데 드는 시간과 리소스를 줄이는 것이 목표가 되었다. 그래서 이제는 2차에 올라가지 못할 것 같은 사람들은 바로 떨어트리고 있다."
그 뒤에 강사님은 자기의 이력서나 경험들을 증명해야 하고 근거에 논리의 뼈대가 없다면 떨어질 것이라고 이야기를 해주셨다. 이처럼 나의 이력서와 경력기술서에는 "내가 이런 경험을 했다."라는 내용만 있지 이 경험을 뒷받침해 주는 근거도 부족하고 어떤 시행착오를 했는지 알기 매우 어려웠었다. 이런 단점들이 내 이력과 커리어의 의구심을 키웠을 것이고 애매하다고 생각하는 담당자분들은 탈락을 시켰을 것이다.
면접 복기를 통한 회고
혼자 코딩하고 동료들과 이야기할 때는 이야기꾼처럼 술술나오던 지식들이 면접장 앞에서는 아무것도 모르는 바보가 되어버리고 마는 것 같다. 그래도 일단 다시 복기해 보면서 그동안 면접 봤던 대면 면접, 코딩테스트, 전화 인터뷰 등의 내용을 다시 정리해 보고 베스트 프렉티스를 작성해보려고 한다.
가장 자신 있는 프로젝트 이력에 대해 설명해 주세요.
기업에 들어가면 나오는 단골 질문이다. 나는 위에 예시로 나와있었던 타이머 기능과 실시간 랭킹 구현을 이야기를 했었고 여기에서 내가 해당 프로젝트를 가장 자신 있다고 이야기할 수 있는 이유는 다음과 같다.
- 다양한 기술들을 조사하여 적은 비용과 개발 인력으로 유지보수 할 수 있도록 시스템을 디자인을 함. (적절한 트레이드오프 능력, 효율적인 시스템 설계 능력)
- 타이머의 시작과 정지 사이에 존재하는 복잡한 정책들과 앱의 라이프 사이클에 따라 달라지는 다양한 유즈케이스를 체계적으로 분석하고, 이를 쉽게 알아볼 수 있도록 잘 정리하여 팀원들과 공유. 이를 통해 병목 없이 원활한 협업을 이끌어냈으며, 제한된 일정 안에서 성공적으로 배포. (복잡한 문제 해결 능력, 협업 및 커뮤니케이션 능력)
- 사용량이 많아지면서 늘어나는 버그와 유즈 케이스들을 캐싱 아키텍처를 리디자인 하여 해결함(시스템 디자인 능력, 문제 해결 능력)
- 혼자 독학으로 레디스를 학습하고 적절한 자료구조를 활용함(학습 능력, 구현 능력)
해당 프로젝트를 처음 받았을 때에는 '사수도 없고 인수인계도 잘 받지 못한 상황에서 이런 복잡한 요구사항을 준 거지?'라고 생각했었지만 지금 생각해보면 나를 가장 크게 성장시켜 준 과제 중 하나라고 이야기할 수 있을 것 같다.
하지만 이렇게 많은 내용들을 면접에서 이야기하려고 하다 보니 머리에 정리되지 않은 채로 구구절절 말이 길어졌고 대화의 흐름이 정상적이지 않게 대답하게 되었다. 그렇게 나의 논리의 뼈대가 무너지면서 나의 강점을 잘 전달하지 못했던 것 같다.
주문 시스템에서 PG 사에 대해 문제가 발생했을 때 어떻게 할지 시스템 디자인을 해주세요
답변
- 결제를 요청해서 성공했을 때 결제 후 처리를 하기 위한 결제 완료 이벤트를 발생
- 결제 실패시 결제 실패 이벤트를 발행한다.
- PG 사가 복구되면 결제 실패 내역에 쌓여있던 주문들을 다시 결제 시도를 한다.
꼬리 질문) 큐에 넣는 로직조차 실패하면 어떻게 할 것인지?
답변
- 클라이언트에 500 응답을 하여 예외 처리
답변을 하면서도 찝찝한 기분이 들었던 것은 왜일까? 아마도 질문의 의도를 정확히 파악하지 않고 답변했기 때문인 것 같다. 질문의 의도를 깊이 분석해 보니, 그 의도는 시스템이 외부 의존성 문제에 직면했을 때 어떻게 안정적으로 운영할 수 있도록 시스템을 디자인할 것인지 묻는 질문이 아니었을까 생각이 든다.
질문 의도
- 외부 시스템이나 서드파티에 의존되었을 때 어떻게 시스템의 신뢰성과 안정성을 보장할 것인지?
- 예외 처리 전략이나 재시도 전략을 구성하여 예외가 발생했을 때 어떻게 실패한 요청을 어떻게 추적하고, 시스템에 부정적인 영향을 미치지 않도록 설계할 수 있는지?
- 결제와 같이 중요한 트랜잭션에서 데이터의 무결성과 일관성을 어떻게 보장할 것인지?
전체적인 아키텍처
최선의 답변
- 단순 네트워크 문제일 수도 있기 때문에 외부 시스템에 요청이 실패했을 때 재시도 패턴을 통해 3회에서 5회정도를 재시도 요청을 할 것 같습니다.
- 재시도를 해도 실패를 한다면 SQS나 카프카와 같은 메시지 큐에 실패한 주문을 저장하여 결제 시스템이 정상화되었을 때 재시도를 하도록 설계할 것 같습니다.
- 만약 큐에 넣는 것도 실패한다면 백오프 전략을 통해 잠시후에 다시 시도하거나 별도의 로그나 디비에 저장하여 자동 또는 수동으로 처리하도록 디자인할 것 같습니다.
- 하지만 여기서 유저가 재시도 요청을 하면서 중복된 주문들이 메시지 큐에 다수로 쌓일 수가 있는데 이 문제는 각 결제 요청마다 고유한 키를 생성하고 중복된 키로 요청했을 경우 첫 번째 요청을 제외한 나머지 주문은 무시하도록 설계해야 됩니다.
- 이외에도 모니터링 시스템과 알림 시스템을 만들어 실무자와 담당자들이 즉각적으로 대응할 수 있도록 시스템을 구축하고 클라이언트에서는 500 응답을 주고 다른 결제 수단을 선택하도록 유도하도록 설계하는 것이 좋아 보입니다.
낙관적 잠금과 비관적 잠금의 차이
답변
- 낙관적 잠금은 실패가 될 것을 가정하고 실패 발생 시 후처리 하는 방법입니다.
- 비관적 잠금은 실패가 되지 않도록 락을 걸어놓고 다른 트랜잭션에서 해당 레코드를 접근하지 않도록 설계하는 것입니다.
최선의 답변
- 낙관적 잠금은 트랜잭션의 충돌이 적거나 나지 않을 것이라고 가정하고 리소스를 잠그지 않는 전략입니다. 대신 버전이나, 유니크 키 등을 정의하여 트랜잭션 커밋 시 충돌이 발생한 뒤에 처리하도록 설계합니다.
- 비관적 잠금은 트랜잭션끼리 충돌이 발생한다고 가정하고 리소스를 잠가 다른 트랜잭션에서 해당 레코드를 업데이트하지 못하도록 막는 전략입니다. 충돌이 빈번하거나 롤백하면 안 되는 데이터에서 이러한 전략을 선택합니다.
꼬리) 낙관적 잠금과 비관적 잠금을 구현해 주세요
답변
- 낙관적 락은 유니크한 키를 만들어서 트랜잭션 커밋 시 충돌이 발생했을 때 Exception을 핸들링하여 처리하도록 구현합니다.
- 비관적 락은 어노테이션을 활용하여 걸어서 사용합니다.
최선의 답변
- 낙관적 락은 특정 칼럼들의 조합으로 유니크한 키를 만들거나 아니면 버전이라는 칼럼을 활용하여 구현할 수 있습니다. 다른 트랜잭션에서 트랜잭션을 커밋할 때 업데이트하고자 하는 버전이 현재 데이터베이스에 저장된 버전과 일치하지 않는다면, 해당 트랜잭션은 실패하게 됩니다. 이를 통해 낙관적 락은 여러 트랜잭션이 동시에 동일한 데이터를 업데이트하려고 할 때, 데이터의 무결성을 유지하도록 합니다.
- 비관적 락은 락 어노테이션을 통해 공유락이나 베타락으로 레코드를 잠글 수 있고 다른 트랜잭션에서 해당 레코드를 수정할 수 없도록 됩니다. 다른 트랜잭션이 읽을 수 있도록 하려면 공유락, 다른 트랜잭션이 읽을 수 없도록 설계하려면 베타락을 사용하며 해당 전략은 디비 성능과 서버 성능에 영향을 주기 때문에 재고나 잔고와 같이 여러 트랜잭션에서 접근하지만 데이터의 무결성과 일관성을 유지해야 되는 테이블에 사용합니다.
MySQL에서 Repeatable read가 Phantom Read가 발생하는 이유
+ 꼬리) Repeatable read에서 Phantom Read가 발생하지 않는데 Serializable 레벨이 존재하는 이유
해당 질문은 내가 Mysql innodb의 정확한 동작 원리를 알지 못해 잘 답변하지 못했다. mysql에서는 기본적으로 갭락을 통해 트랜잭션을 제어하므로 한 가지의 케이스를 제외하고는 phantom read가 발생하지 않는데 이걸 제대로 설명하려면 갭락의 원리에 대해 어느 정도 이해해야 한다.
갭락의 동작 과정
- MySQL에서 동일한 트랜잭션에서 동일한 결과를 보여주기 위한 락 전략
- 다른 트랜잭션에서 특정 데이터의 범위를 갭락으로 잠금을 걸면서 해당 범위에 속하는 데이터는 넥스트 키락이 잠기게 됩니다.
- 잠금을 걸고 있는 트랜잭션이 종료가 되면 새로운 데이터를 추가하는 트랜잭션이 실행됩니다.
갭락 예시
Thread1: select * from employments where where id >= 3 for update; Transaction-ID-8
Thread2: Insert into employments(name, type) values ('will', 'contract-worker'); // Transaction-ID-10
id | name | type |
1 | rio | contract-worker |
2 | michael | contract-worker |
3 (갭락) | sam | full-time |
4 (갭락) | mina | part-time |
Thread1: Transaction-ID-8 종료
Thread2: Insert into employments(name, type) values ('will', 'contract-worker'); // Transaction-ID-10 실행
id | name | type |
1 | rio | contract-worker |
2 | michael | contract-worker |
3 | sam | full-time |
4 | mina | part-time |
5 | will | contract-worker |
팬텀 리드 예외 케이스 예시
Thread1: select * from employments where where id >= 3; // Transaction-ID-8 실행
Thread2: Insert into employments(name, type) values ('will', 'contract-worker'); // Transaction-ID-10
id | name | type |
1 | rio | contract-worker |
2 | michael | contract-worker |
3 | sam | full-time |
4 | mina | part-time |
Thread1: select * from employments where where id >= 3 for update; // 팬텀 리드 발생
Thread2: Insert into employments(name, type) values ('will', 'contract-worker'); // Transaction-ID-10 실행
id | name | type |
1 | rio | contract-worker |
2 | michael | contract-worker |
3 (갭락) | sam | full-time |
4 (갭락) | mina | part-time |
5 (갭락) | will | contract-worker |
위의 예시와 같이 mysql에서 갭락을 사용하지 않고 조회 후 그 뒤에 다시 갭락을 건다면 팬텀리드가 발생할 수밖에 없게 된다. (언두로그는 락이 안 걸리기 때문에) 이런 경우에는 Serializable로 Transaction isolation 레벨을 설정하여 그 어떤 트랜잭션도 접근하지 못하도록 제어하거나 분산락을 통해 접근을 제어해야 한다.
서비스의 중단 없이 대규모 데이터를 가진 테이블을 마이크로서비스로 어떻게 전환할 수 있는지?
답변
- 일단 스키마를 먼저 분리하여 논리적으로 데이터베이스를 분리할 것입니다.
- 그리고 해당 테이블을 바라도록 모델을 수정한 뒤 관련된 비즈니스로직을 수정할 것 같습니다.
- 만약 위와 같이 구현이 어려울 상황에는 피처 토글을 통해 마이크로서비스를 호출하는 로직과 기존 레거시 디비를 호출하는 로직을 핸들링하도록 설계하고
- DMS나 카프카와 같이 CDC 툴을 활용하여 레거시 데이터베이스와 마이크로서비스 디비의 데이터 정합성을 맞추도록 설계할 것입니다.
위와 같은 답변은 상황에 따라 다른 답변이 될 수도 있고 맞은 답변도 될 수 있을 것 같다. 여기서 답변을 하기 전에 의도를 어느 정도 생각하고 답변을 했다면 좀 더 정답에 가까운 답변을 했을 텐데 내가 생각하고 있는 케이스만을 생각하고 답변하게 되면서 논리의 뼈대가 갖춰지지 않은 채로 결론을 내렸던 것 같다.
최선의 답변
- 가장 우선적으로 해야 되는 것은 마이크로서비스로 분리하는 도메인 영역의 데이터베이스와 애플리케이션 코드들을 전부 분석하고 스케치하는 것입니다.
- 도메인의 대한 영역이 어느 정도 정리 되었다면 변경으로 발생할 수 있는 영향도와 도메인 간의 의존성 등을 고려하여 어떻게 분리를 진행할지 계획을 수립합니다.
데이터의 분리가 어렵거나 코드가 컴포넌트화 되어있는 경우
- 이런 경우에는 코드들을 우선적으로 분리하는 전략이 적합하다고 생각합니다.
- 주키퍼와 같은 코드 정적 분석기를 통해 자주 변경되는 도메인 영역을 찾아 분리하기 쉽고 간단한 영역부터 데이터 계층부터 차례대로 분리하는 것을 고려할 것 같습니다.
- 도메인의 경계가 명확하게 잘 분리가 되어 모놀리식에 독립적으로 배포할 수 있을 정도가 된다면 데이터 분리를 시도해 볼 것 같습니다.
데이터의 경계가 명확하거나 보안, 스케일링의 이유로 데이터베이스를 먼저 분리하는 경우
- 데이터 우선 분리 전략에서 중요한 것은 데이터의 무결성과 일관성을 유지하는 것입니다. 그렇기 때문에 서로 다른 데이터베이스에서 데이터가 일관되게 저장될 수 있도록 설계해야 됩니다.
- 분리해야 되는 서비스에 크기나 시간에 따라 달라질 것 같은데요, 일단 마이크로서비스의 전환이 오래 걸리거나 분리하고자 하는 도메인의 영역이 너무 큰 경우 교살자 패턴을 활용할 것 같습니다.
- 교살자 패턴을 통해 중간에 들어오는 요청을 인터셉터가 가로채고 마이크로서비스와 모놀리식 두 개의 서버에 요청을 핸들링하도록 설계할 것입니다. 해당 패턴을 사용하게 되면 문제가 발생했을 때 안정적으로 핸들링할 수 있기 때문에 기능 단위로 점진적 마이크로서비스 전환을 진행할 때 적합하다고 이야기할 수 있을 것 같습니다.
- 도메인의 영역이 작고 빠른 기간 내에 전환할 수 있는 프로젝트라면 기능 토글을 통해 모놀리식과 마이크로서비스에 호출할 수 있도록 설계할 것입니다.
- 하지만 해당 전략은 두 개의 데이터베이스의 데이터 동기화와 스키마 동기화가 필요하기 때문에 AWS DMS나 flyway와 같은 디비 마이그레이션 툴을 활용하여야 합니다.
코드리뷰를 할 때 어떤 점을 주로 보시나요~?
답변
- validation이나 파이썬의 f-string과 같이 보안적으로 문제가 발생할 수 있는 코드는 Change Request를 보냅니다.
- 저는 다른 개발자가 해당 코드를 변경하더라도 문제가 발생하지 않도록 설계하는 것을 중요시 생각합니다(어떤 의미?)
- 각 메서드가 단일 책임을 가지도록 설계하는 것을 중점으로 리뷰합니다.
질문 의도
- 자기만의 코드 철학이나 소프트웨어 디자인을 할 때 중요시하게 생각하는 것을 알려주세요.
- 코드리뷰도 결국 병목이 될 수 있는데 상황이나 환경에 따라 어떻게 협업하시나요?
최선의 답변
- 코드를 작성하면서 중요하게 생각하는 것 중 하나는 클라이언트에서 오는 요청 값을 검증하여 데이터베이스에 반영하는 것입니다. 클라이언트의 요청을 신뢰하고 바로 디비에 반영하거나 의도와 다르게 설계된 코드들은 수정을 요청하도록 리뷰하는 편입니다.
- 그다음으로는 성능적으로 문제를 일으킬 수 있는 코드들을 살펴보는 것을 리뷰할 것입니다. N+1 문제, 슬로 쿼리, 불필요한 루프 등을 사용하였는지 체크하고 개선안들을 제시합니다.
- 만약 시간이 된다면 코드의 응집성과 결합성을 고려하여 잘 설계되었는지와 캡슐화가 잘 된 클래스를 설계하였는지 리뷰를 합니다.
- 코드 리뷰는 서로 많은 시간을 요구하고 상황에 따라 피드백 루프가 길어질 수 있기 때문에 중요도를 나누어 당장 수정해야 되는 코드와 나중에 시간이 되면 수정해 주는 코드들을 명시할 것입니다.
앞으로의 계획
회사를 휴직한 후 빨리 이직하고 싶은 마음에 2주 동안 무작정 달려왔던 것 같다. 그 과정에서 같은 실수를 반복하고, 급하게 일정들을 잡으면서 계획했던 일들을 지키지 못하는 경우가 많았다. 이렇게 계속하다가는 밑 빠진 독에 물을 붓는 듯한, 의미 없는 시간만 보낼 것 같다는 생각이 들었다.
그래서 잠시 서류 지원을 멈추고, 재정비의 시간을 가지기로 했다. 이직 시장에서 내가 부족한 것이 무엇인지 정리하고, 필요한 역량들을 파악해보려 한다. 일정이 충돌하지 않도록 Todoist 같은 도구를 활용해 가시적으로 정리하는 습관도 들여야 할 것 같다.
이직을 준비하면서 느낀 것은, 마치 불확정성의 원리처럼 지금 내가 이 시장에서 어느 위치에 있는지, 공부를 통해 얼마나 성장했는지 알 수 없다는 것이다. 눈을 가린 채 미로를 헤매는 기분이 든다. IT 업계의 상황이 좋지 않아 더 많은 경쟁자가 생겼다는 소식에 나도 모르게 스트레스를 받기도 한다.
하지만 이런 상황에서도 나아가기 위해서는 꾸준히 기록하고, 자신의 지표를 만들며 검증하는 과정이 필요하다. 그 과정을 통해 자신감을 쌓고, 피드백을 받아가며 조금씩 답에 가까워지는 것이 중요하다고 생각한다. 그렇게 꾸준히 나아가는 것이 결국에는 좋은 결과를 가져다줄 것이다.
'일상' 카테고리의 다른 글
다사다난 했던 2024년을 보내며...(부제: 제어할 수 없는 인생) (2) | 2025.01.05 |
---|---|
나를 그려가는 10가지 물음과 앞으로의 그림들 (15) | 2024.10.12 |
채용 한파 속 이직 시장에서 살아남기 (개발자 편) (0) | 2024.08.10 |
옵시디언 VS Notion VS WriterSide 메모의 끝판왕은? -개발자 편- (6) | 2024.04.28 |
슬랙 메시지 하나로 회사에서 IT 세미나를 열게 된 썰 (34) | 2024.04.10 |