25년은 굉장히 밀도 있는 해였다. 먼저 회사 업무 측면에서 보면 작년 말부터 이어진 회원 통합 프로젝트에 이어, 숙소 상세 사이트 개편과 공통 미들웨어 라이브러리 개발이 함께 진행되었고, 이어서 전배와 동시에 신규 프로젝트들을 진행하고 있다.
회사 업무 외에도 꽤나 도전적이었다. 번지점프를 하러 마카오 여행을 다녀왔고 일본 여행도 3번이나 다녀왔지만, 대학원 진학을 도전한 것이 가장 큰 변화를 만들고 있다. 새로 진행하는 개인 프로젝트도 꽤 재미있는 작업이었는데, 대학원 진학을 위해 시작했지만 정작 나 자신은 사용하지 않게 된 것이 아이러니하다. 이런 일들에 대해 정리할 겸 적어본다.
주요 회사 업무
회원 통합 프로젝트
이전 글에 적은 것과 같이, 24년 말부터 25년 초까지 야/인/트 3개 서비스를 통합하는 회원 사이트를 개발했다. 초기 손발이 맞지 않아 개발에 지연이 발생했고, 프로젝트 자체의 난이도가 어렵다보니 업무 배분에도 이런저런 문제가 있었다. 무엇보다 일반 로그인에 더해 소셜 로그인의 도메인 지식이 필요했던, 꽤나 어려운 프로젝트였다. 특히 소셜 로그인은 지금 생각하면 아쉬운 부분이 많다.

지금 생각해보면 재미있던 점도 아쉬운 점도 가득한 프로젝트였다. 이 프로젝트를 통해 다양한 것을 배울 수 있었다. 특히 개발과 관련된 것 외에도 다른 사람을 이끌어 줄 수 있는 사람이 되기 위해 부족했던 점들을 배울 수 있었다. 이 경험은 개발자로서도 사람으로서도 큰 도움이 되리라 믿는다.
숙소 상세 사이트 개편
재직 중인 회사는 PC 웹에 약하다. 한동안 모바일 중심으로만 운영해왔기 때문에 PC 유저에겐 억지로 늘린 화면을 제공했다. 그러다 보니 너무 어색하고 완성도가 떨어져 보인다는 의견이 많았고, 드디어 PC 화면을 지원하고, 공통 디자인 요소를 기반으로 하는 프로젝트를 진행했다. 한동안 숙소 상세 사이트를 해왔지만, 다른 작업을 진행하고 있어 내가 오너십을 가지지 않고, 화면 구성에 필요한 컴포넌트 몇 가지를 개발하는 정도로 진행했다.

결과적으로 깔끔하게 진행되지 못한 아쉬운 프로젝트였다. 문제가 되었던 것은 대부분 QA 단계에서 누락된 요소들이다. 간단하지만 어처구니 없던 대실 예약 문제가 기억에 남는다. 대실은 숙박이 아니다보니, 더 짧은 시간(4시간 내외)만 제공하는 형태다. 주문 페이지로 이동하기 위해 대실 시간을 전달하는데, 하루가 더해져 전달되었음에도 그 여러 과정 중 어디에서도 검증되지 않고 있었다. 물론 그렇게 전달한 것 자체도 큰 실수지만 주문 페이지에 다음 날까지로 노출되는 것을 아무도 발견하지 못했고, 어느 API에서도 오류가 발생하지 않았다는 점이 경악스럽다. 다행히 하루 이상 대실할 수는 없으니 DB 레벨에서 수정할 수 있었다.

두번째 문제는 심각했다. 야간 시간대의 예약 문제였다. 모텔은 그 특성상 0시부터 2시는 전날로 판단하는 비즈니스 로직이 있다. 개발 단계에서 이 비즈니스 로직을 테스트하기 위해 야간 시간대로 판단하는 테스트용 플래그를 만들어 사용한다. 하지만 단순 노출 뿐 아니라 예약이나 관리자와 연관된 기능이 많아, 실제로 야간에 테스트 해보지 않으면 작동을 장담할 수 없다. 심지어 이 시간대가 가장 중요한(강성 CS도 잦은) 피크 타임이다. 바로 이 부분에서 문제가 발생했다.

이전에는 이 위험한 부분을 테스트하기 위해 신규 기능이 추가되면 새벽에 테스트하는 것을 최종 단계로 하곤 했다. 하지만 이번에는 그렇게 하지 않았던 것 같다. 꼼꼼한 테스트 없이 라이브 배포까지 이루어졌고, 기기에 따라서는 예약 자체가 불가능해지는 문제까지 발생했다. 난 이 즈음 팀을 이동해 자세한 상황에 대해 알지 못했지만, 좋지 않은 일들이 있었다고 한다.

QA팀도 대규모 조직 개편으로 혼란스러웠던 것을 이해한다. 하지만 발생한 문제들은 실무 담당자들에게 굉장히 큰 스트레스로 돌아오게 된다. 스트레스 내성이 어지간히 높은 사람이 아니라면 정말 힘든 일이 되었을 것이다. 실제로 이 프로젝트 도중에 입사한 PM은 프로젝트가 끝나자마자 곧바로 퇴사했다. 몇 년 전 내가 개편을 진행했을 때에도 아주 똑같은 일이 있었기에 웃을 수 밖에 없었다.

공용 미들웨어 프로젝트
회원 통합 프로젝트로 로그인은 단순 세션에서 JWT 기반의 토큰으로 전환되었다. 자연스럽게 서비스를 구성하는 사이트들은 토큰의 유효성을 검사하고 갱신하는 로직이 필요해졌다. 하지만 이 로직을 각각의 사이트가 모두 직접 구현하고 유지보수 하기에는 어려움이 있다. 명확한 담당자가 없으면 작업이 누락될 확률이 높고, 발생한 문제가 세션과 관련된 것이라면 전면 장애까지 이어질 수 있기 때문이다. 이 상황에서 몇 년 전 만든 게이트웨이 어플리케이션이 다시 주목받기 시작했다. 게이트웨이 어플리케이션의 목표는 사용자가 모두 같은 도메인으로 접근하고, 루트 도메인에서 하위 path에 따라 다른 웹 어플리케이션으로 프록시한다. 사용자들이 같은 도메인으로 세부 어플리케이션에 접근하게 되면 브라우저 입장에서는 같은 도메인의 사이트로 판단하므로 권한이나 스토리지 등 다양한 부분에서 유리해진다. 특히 공통으로 사용되는 헤더나 쿠키 처리 로직이 있어 이곳에 토큰 로직을 담는다면 쉽게 해결될 법 했다.

하지만 이 어플리케이션을 확장해 사용할 수는 없었다. 구성한 방식이나 아이디어는 나쁘지 않았지만, 선택한 언어와 프레임워크가 문제였다. Node Express.js로 구현했기 때문이다. 그럭저럭 잘 운영 되는가 싶었지만, 결국 대규모 트래픽에 따른 성능 측면에서 결점을 드러냈다. Node의 이벤트 루프 방식은 작은 규모에서 아주 강력하지만, 대규모 인입에 대해서는 성능적 한계를 가진다. 수많은 사용자의 요청을 받아내야 하는데, 프록시되는 서비스 어플리케이션에서 지연이 발생하면 이벤트 루프에 프록시 처리를 위해 만들었던 콜백이 쌓이게 되어 점점 더 느려지기 시작한다. 더군다나 위 그림처럼 헤더나 쿠키를 설정하기 위한 로직이 포함되어 있다 보니, 많은 연산이 필요해 그 문제는 더욱 심각하다(요청과 관련된 문제라 work thread를 사용하기도 난감하다). 거기에 웹 특성상 응답이 지연되면 일정 임계점을 넘는 순간부터 사용자의 새로고침 폭격이 시작된다. 이 폭격을 맞는 순간부터는 정상적인 운영이 완전히 불가능해진다. 이런 고질적인 문제를 가지고 있어 숙소 도메인보다 더 큰 트래픽이 몰릴 티켓 도메인은 합류가 불가능했다.

어찌되었건, 한계에 봉착해 변화를 주어야 하는 이 게이트웨이 어플리케이션에서 토큰 갱신을 처리하는 것은 불가능하다고 판단했다. 당장 이 공통 요소가 필요한 티켓 도메인은 게이트웨이 어플리케이션을 거치지 않기 때문이다. 대안으로 사이드카 스타일의 아키텍처를 선택했고, 가장 간단하고 강력한 방법으로 먼저 Next.js 미들웨어에서 사용할 수 있는 라이브러리를 만들기로 했다. 토큰 갱신과 더불어 기존 게이트웨이 어플리케이션에서 수행하던 실험군 선정이나 UA 파싱, 유저 핑거프린트 생성 등의 기능도 포함하기로 했다.
이렇게 거창하게 라이브러리화 할 것 없이 그냥 API를 호출하면 토큰이 갱신되는 것 아닐까 생각할 수 있다. 하지만 생각보다 오래된 서비스이다 보니 그리 호락호락한 문제가 아니었다. 여러 웹 어플리케이션에서 공통된 세션을 사용하기 위해 Shared Redis Session을 사용하는데, 이 곳에 토큰을 저장해 두다 보니 각 어플리케이션이 이 세션에 연결될 수 있어야 했다. 여기서 문제가 발생한다. Next.js의 미들웨어는 기본적으로 Redis Client를 사용할 수 없는 Edge Runtime이라는 것이다. Next.js 15.5부터 다시 Node Runtime을 선택할 수 있게 되었지만, 이미 개발이 완료되어 서비스중인 어플리케이션을 위해서 Edge/Node Runtime 양 쪽 환경을 모두 지원해야 했다.

Edge Runtime은 특성상 Redis 커넥션을 직접 만들 수 없다. Redis와 직접 통신이 불가능하니, 미들웨어에서 직접 Redis Session에 연결할 수 없게 된다. Page Router라면 페이지 레벨(getServerSideProps)에서 고차 함수를 사용한다던지 하는 방법으로 해소가 가능하지만, App Router라면 쉽지 않다. 공통된 방법으로 요청을 처리하는 방법이 미들웨어 뿐이기 때문이다(요청마다 처리한다는 끔찍한 아이디어는 꺼내면 안된다).
이 문제를 해결하기 위해, Edge Runtime에서 사용할 Internal API endpoint를 만들기로 했다. 어플리케이션의 API에서 Redis에 연결하고, Edge Runtime의 미들웨어에서는 이 API에 요청을 보내도록 하는 것이다. 그리 깔끔한 방법이 아닌 것처럼 보이지만, 이런 식으로 처리되는 라이브러리를 서비스를 하고 있는 곳도 있어 의외로 괜찮은 방법이라고 생각된다.

이렇게 Redis를 조회할 수 있는 내부 API를 제공하여 Redis에 연결할 수 있게 되었다. 하지만 또 다른 문제가 있었다. 이 회사의 어플리케이션은 오래 전부터 Express-session를 이용해 세션을 연결하고 있다. 이 라이브러리는 전통적인 세션 구현을 위한 미들웨어 라이브러리로, 세션 키를 쿠키로 설정해주는 역할도 겸한다. 이 라이브러리를 그대로 사용하면 좋았겠지만, 플러그인으로 connect-redis를 사용해 Shared Redis Session을 사용하고 있는 것이 문제다. 앞서 이야기한 것처럼, Edge Runtime에서는 Redis에 직접 커넥션을 맺을 수 없으니 이 라이브러리를 사용할 수 없었다. 심지어 운영 중인 서비스와 세션을 유지할 수 있어야 하니 이 라이브러리를 분석해 동일하게 동작하도록 만들어야만 했다. 많은 시도 끝에 암호화되는 쿠키 키를 다룰 수 있게 되었고, Redis에 저장되는 세션도 동일하게 만들어냈다.

이 외에도 꽤나 많은 기능을 추가하며 공통 미들웨어 패키지를 확장할 수 있도록 구조를 설계했는데, 위에서 설명한 세션같은 것들 때문에 난이도가 너무 높아져 버렸다. 결국 수정할 수 있는 인원이 한정적인 상황이 되어 버렸다. 이전 protobuf 로그 생성기도 그렇고, 묘하게 나만 유지보수할 수 있는 썩은 프로젝트가 생겨나고 있다. 좋지 않은 상황이다.
팀 전환배치
사실 회원 통합 프로젝트를 진행할 때부터, 이번에 CTO가 되신 분이 새로운 팀을 만들려 한다는 이야기를 들었다. 굳이 내가 발벗고 나서 이동할 이유도 없고 의욕도 딱히 없었기에 관심을 가지지 않았었다. 하지만 여러가지 정치적(?) 이슈와 심경의 변화로 새로운 팀에 합류하게 되었다. 심지어 팀장을 맡게 될 예정이다. 이전에 팀장이 되면 어떻게 할 지에 대해 생각해 보고 글을 적었는데 이렇게나 빨리 실전 경험을 하게 될 줄은 몰랐다. 자신은 없지만 해야 할 일은 알고 있으니 그에 맞춰 잘 해보려 한다.

함께 넘어온 팀원들은 이미 너무나도 잘 하고 계시는 분들이기에, 내가 방해되지 않는다면 어려운 일은 없으리라 생각한다. 특히 CTO가 되신 리더분이 원하는 것은 빠르고 주도적인 개발이다. 나 역시도 그런 스타일로 개발하기를 원한다는 점에서 방향성이 같다. 그래서 나는 훌륭한 플레이어이기에 앞서 길에 있는 방해물을 치워주는 역할을 해 볼 생각이다.
CID(AI 채팅 탐색) 프로젝트
유행하는(?) AI 채팅 프로젝트를 개발해 런칭했다. 현재로썬 사용률이 매우 낮지만 새로 결성된 팀이 빠르게 개발하고 런칭했다는 점에서 의미가 있었던 프로젝트다. 1개월도 안되는 매우 짧은 개발 기간을 가졌지만 완성도 있는 결과물을 내보여 팀의 역량을 보여줄 수 있었다. 앞으로 다양한 응답 모델과 형식을 추가해 사용성을 강화한다고 하는데, AI 관련 사이트를 꽤나 개발해본 입장에서 사용자의 입력을 받는 대화형 서비스는 히트하기 기대하기 어렵다고 본다. 사실 팀을 위한 성과로 생각하고 있었기에 별 애정이 없는 프로젝트다.

검색 사이트 이관과 Golang을 이용한 BFF 개발
나는 이 프로젝트가 정말 큰 Breaking point가 될 프로젝트라고 판단하고 있다. 다른 팀에서 담당하던 BFF API 서버와 그 서버를 이용하는 검색 및 탐색 페이지를 이관하는 프로젝트다.
두가지 측면에서 큰 변화를 줄 것이라고 생각하는데, 첫번째는 팀의 정체성을 보여주는 것에 있다. 팀이 만들어지긴 했지만 아직 담당하는 부분이 명확하지 않다. 앞서 CID 프로젝트가 팀의 역량을 보여주었다고 적었지만, 팀의 필요성에 대해서는 어필이 부족한 상황이다. 하지만 이 프로젝트가 진행되면 팀이 맡는 주요 서비스가 있기에 업무를 명확하게 이야기할 수 있게 된다.
두번째는 기술적 챌린지다. 사내의 다른 FE 팀과 달리 코어 백엔드 서비스와 맞닿아 연결되는 BFF 서버를 직접 맡게 된다. 이건 팀 구조와도 연관이 있는데, 완전한 기능 조직이었던 이전 팀과 다르게 새로운 팀은 목적 조직 스타일로 구성되어 있기 때문에 가능한 부분이다. 특히 팀원 각각의 역량이 뛰어나기 때문에 가능하다. FE는 걱정할 필요가 없을 정도로 역량을 가지고 있으니 그 원천 데이터에 관심을 가질 수 있게 된다. 이렇게 FE팀이 BFF 서버를 직접 개발하면 FE 코드와 일관성을 가질 수 있고(특히 enum류가 많다), 외부에서도 작업 요청을 위한 과정이 줄어든다는 점이 큰 변화를 줄 것이다. 특히 그 BFF 서버를 Go로 만든다는 것이 엄청난 차이점이고 도전이다.
왜 Golang을 선택했나
다른 팀에서 관리하던 BFF 서버는 Nest.js + fp-ts로 만든 Node 어플리케이션였다. 앞서 게이트웨이 어플리케이션에서도 언급했지만, Node 자체가 가지는 한계가 있다. Node API 개발자라면 반발할 수 있겠다. 이전 BFF 서버를 담당하던 팀은 Node API 개발자로 이루어진 팀으로 다양한 방법으로 성능 개선에 도전하고 있다. 하지만 명쾌한 해답을 내놓지 못했고, 인스턴스 수로 트래픽을 받아내고 있는 실정이다. 엄청난 노하우를 가진 개발자라면 그 한계를 넘어설 수도 있을지 모른다. 하지만 그런 개발자가 없으니 현실적으로 불가능하다. 또, 그렇게 튜닝에 성공한다고 해도 그 상태 그대로 유지보수가 가능한지는 또 다른 의문이다. 이런 성능적 한계가 새로운 방식을 택해야 했던 첫번째 이유다.

두번째는 Protobuf 편의성이다. 이전에 로그 전송 함수와 관련해서 사내의 정의(enum같은 것들)를 Protocol Buffer(protobuf)로 전환하고 있다는 글을 썼다. 야/인/트 서비스의 병합과 동시에 파편화 되어 있는 정의를 통일해야 한다. 이미 사용하고 있고, 어느정도 성공적인 결과를 만든 Protobuf가 있으니 공통적인 Protobuf를 정의하고 사용하리라는 것을 충분히 예상할 수 있다. 거기에 더해 Protobuf로 모두 정의되면 자연스럽게 gRPC를 사용할 가능성도 높다고 판단했다(벌써 현실이 되어가고 있다). 결과적으로 Protobuf, gRPC와 호환성이 좋은 방법을 찾아야 했다.
이 두가지 상황에 대해 팀원들에게 공유하고 후보를 추렸다. 내가 생각한 후보는 두가지였다. 첫번째는 JVM 언어(주로 Java Spring), 두번째는 Go였다. 그리고 일정이 틀어졌을 때 선택할 마지막 카드로 Node 유지가 있었다. 재미있는 것은 Spring 이야기를 했을 때 모두가 크게 반발했다는 것이다. 진입 장벽은 있으면서, Protobuf를 고려했을 때 큰 장점을 얻을 수 없다는 것이다. 개인적으로는 여차할 때 다른 팀에서 도움을 줄 수 있다는 점도 좋은 장점이라고 생각했는데, 결사 반대하는 모습에 포기했다.

남은 후보인 Go를 선택하기에 앞서 조금 고민해보았다. 과연 팀 운영에 도움이 될까. 러닝 커브가 있음은 명확하다. 특히 FE 개발자에게 Go로 개발하라고 하는 것은 가혹한 일이다. 하지만 LLM 시대는 언어의 장벽이 무너졌다. 잘 모르면 물어가며 개발하면 된다. 그렇기 때문에 Go를 선택하고 개발을 시작했다(사실 기존 코드를 그대로 바꿔달라고 하면 될 줄 알았는데 그건 불가능했다...)
개발 방식
사실 참고할만한 Go API 프로젝트를 찾지 못했다. 간단한 Hello World 수준은 많지만 실무에서, 특히 FE 개발자가 운용할 BFF API에 어울리는 스타일을 찾지 못했다. 거기에 앞서 말한 것처럼 Protobuf를 사용해야 하며, gRPC도 지원할 수 있어야 했다. 결국 내가 고민할 수 밖에 없었다. 지금까지 얻어온 경험과 나름의 노하우를 종합해 아래 방식으로 개발을 진행하고 있다.
1. API Endpoint 및 외부 API와의 연동은 Protobuf로 정의한다
프로젝트에서 반드시 지켜야 하는 가장 강력한 룰이다. 프로젝트 내에서 처리하는 객체는 구조체로 정의하곤 하지만, 프로젝트 외부에서 접근할 수 있는 타입은 모두 Protobuf로 정의한다. 외부 프로젝트에서도 정의해둔 Protobuf 파일을 빌드해 API의 세부 Path와 요청, 응답을 타입을 그대로 사용할 수 있게 한다.


2. 싱글톤 패턴을 준수한다
Node로 만든 API는 의외로 테스트에 약한 편이다. 많은 이유가 있겠지만, 개인적으로 export default로 대표되는 전역 객체 선언 방식이 문제라고 생각한다. 전후 연관 관계를 가진 객체를 주입할 수 없으니 유닛 테스트가 어려워진다고 생각한다. 이런 점을 처음부터 막기 위해 uber의 fx를 이용해 로직을 수행하는 서비스를 싱글톤으로 생성하게 유도했다. 이와 동시에 API의 품질을 보증할 수 있는 테스트 코드를 작성하는 것을 가이드했다.
이외에도 gRPC와 http를 모두 지원하기 위해 gin-gateway로 구현했다던지, facade-service 레이어를 가진다던 하는 것들이 있지만 프로젝트를 마무리한 후 적어보려 한다.
성과
대부분의 API 작업이 완료되어 간단하게 성능을 테스트 해볼 수 있었다. 로컬 및 배포 환경 기준으로 대부분의 API의 응답 속도가 40~50%로 감소했다. 기존 API보다는 성능 측면에서 더 나을 것이라 기대하고는 있었지만 이렇게까지 큰 성능 차이를 보여줄 것이라고는 생각하지 않았다.


추측하기로는 기존 API가 fp-ts로 만들어져 있어 많은 객체 생성이 이루어지니, 객체 처리에 부하가 큰 Node와 역시너지가 발생하고 있지 않을까 한다. 하지만 그렇다고 해도 너무 큰 차이라 혹시나 놓친 로직이나 API가 있나 싶어 겁이 나는 상황이다.

대학원 진학과 개인 프로젝트
내년 기술 경영 대학원에 입학한다. 이제 주어진 업무를 수행하는 개발자를 넘어 리더를 목표로 하고 있기에, 이 목표에 도움을 줄 수 있는 과정이라고 생각한다. 이 석사 과정이 스펙 측면뿐 아니라 실제로 더 나은 리더가 될 수 있는 도움을 줄 수 있을지는 아직 모르겠다.

대학원 지원과 동시에 개인 프로젝트도 시작하게 되었다. 석사 과정을 모집하는 학교들을 보니 어학 점수를 요구하는 경우가 꽤 있었다. 그래서 손놓았던 어학 공부를 해볼까 하여 관련 앱들을 보았는데, 구독료가 너무 비싸게 느껴졌다. 그래서 개인 용도로 사용하기 위해 만들기 시작했다. 대충 만들어 보니 꽤나 괜찮아 보여 프로젝트를 확대해 개발했고, 지금은 서비스를 런칭 했다.

서비스 개발자가 대개 그렇듯, 직접 만든 서비스는 사용하지 않게 되어 결국 어학 점수를 요구하지 않는 대학원에 진학하게 되었다. 미국에서 유학 중인 동생이 차후 투자를 위해 많은 사람들과 이야기를 나눠보니 굉장히 긍정적으로 평가했다고 한다. 추세를 보며 기능을 확장해 준비한다면 조만간 제대로 시작할 수 있을 것 같다.
마치며
매년 블로그를 쓰다 보니 의도하지 않아도 무엇인가를 도전하게 되는 것 같다. 내가 했던 일과 생각을 정리하는 것만으로도 큰 도움이 되지만, 최소한 작년보다 더 나은 일년을 보내기 위해 목표를 설정하고 또 해내려고 하는 것이 도움을 준다. 아직까지 아쉬운건 글 쓰는 주기가 너무 길다는 것이다. 글을 쓸 때마다 매번 더 열심히 써야지, 자주 써야지 하는데 쉽지 않다. 형식 없이 간단하게 써두고 싶어도 마음이 허락하지 않는게 크다.
이런저런 일이 좀 있다보니 올해보다 내년에 더 해야 할 일이 많다. 회사 업무도 있지만 대학원으로 생활 패턴 자체가 바뀌는 것이 큰 영향을 주지 않을까 생각한다. 이전에는 이렇게 해야 하는 일들이 줄지어 있을 때 걱정이 컸는데, 이젠 걱정보다 기대가 앞선다. 잘 해낼 수 있을지는 모르지만 잘 못한다고 해도 별 문제 없다는 것을 알게 되어서가 아닐까.
'ETC' 카테고리의 다른 글
| 네이티브 앱에서 하이브리드 앱으로 (1) | 2024.09.25 |
|---|---|
| 팀장이 되면 어떻게 해야 할까 (2) | 2024.09.17 |
| 2024년 상반기를 보내며 (0) | 2024.06.09 |
| protobuf를 이용한 "자동 함수 생성" 프로젝트 (1) | 2024.06.02 |
| H2에서 청크 개수 제한은 성능에 영향을 줄까? 주는 것 같다. (1) | 2023.12.07 |