티스토리 뷰

안녕하세요. Web Frontend팀 이민하입니다.

 

지난 편에서 꿀템 서비스를 기획하고 필요한 개념들의 이름을 지어주며 이를 바탕으로 데이터베이스를 설계해 보았습니다. 이번 편에서는 어떤 기술 스택을 선택했는지 소개하도록 하겠습니다.

 

기술 스택 선택과 개발

 

External 망에는 기존에 BSD 프론트엔드 영역 어플리케이션들이 있습니다. node.js와 경량 웹프레임워크인 fastify로 되어있습니다. 프론트에서 api를 호출하면 attraction 집계 어플리케이션이 메인 데이터 저장소인 Oracle DB에서 데이터를 조회해 옵니다. 구매내역, 링크루 등 외부 api를 호출한 결과도 전달해 줍니다. 

저장된 데이터는 Admin화면을 통해 관리할 수 있습니다. 누가 어떤 피드를 작성했고 누가 좋아요 버튼을 눌렀는지, 구매는 얼마나 일어났는지 등을 확인할 수 있습니다.

 

프론트엔드는 별도의 서버를 구축하지 않고 플러그인 형태로 개발되었습니다.

event  관련 어플리케이션은 프론트엔드 이름이 붙어있지만 여러 프로모션 영역의 비즈니스 로직이 많이 들어있는 어플리케이션입니다. 이번 BSD는 랭킹 탭 옆에 신규 탭으로 꿀템 탭이 신설될 것이므로 event 어플리케이션 안에 신규 생성한 attraction 어플리케이션을 번들 형태로 import 했습니다. 

  • Typescript, React 18.2
  • Tanstack query (aka. react-query) 
  • api fetching 기능을꿀템의 등록/편집/좋아요 처리에 개별적으로 상태를 관리하지 않아도 되서 개발에 유리하였고요
  • Tanstack virtual을 이용해 무한스크롤 기능을 구현하였습니다.
  • event-fe와 마찬가지로 Fastify 프레임워크를 사용하였습니다.
  • Vite로 번들링 하여 사내 배포시스템을 통해 배포하였습니다.

또 아래의 특징을 갖고 있습니다.

  • attraction 집계 api 호출 및 사용자 입력값 검증 (xss 방어)
  • 이벤트 프론트 저장소가 분리되어 있고 꿀템 저장소(attraction 프론트엔드)도 별도라서 꿀템 쪽 fastify용 코드는 attraction 프론트에서 개발하고 결과물을 fastify plugin 형태의 npm 패키지로 생성해서 이벤트 프론트 쪽에서 가져다 쓰는 방식으로 구현
  • 개발 중에는 attraction 프론트엔드에서 이벤트 프론트의 구성과 유사하게 dev server를 띄워서 개발하였습니다.

 

백엔드의 기술스택은 다음과 같습니다.

 

  • 이벤트 영역에서는 C# 닷넷과 Node.js로 이루어진 어플리케이션이 많은데 팀 내에서 처음으로 Spring과 Kotlin을 적용하여 어플리케이션을 개발했습니다. 오라클을 사용하니 Stored Procedure 대신 jpa와 QueryDSL을 적용하였습니다.
  • Admin 서비스는 스프링 멀티모듈로 만들어 웹 UI와 oracle에 접속할 api가 같은 도메인 모델을 공유하도록 하였습니다.
  • 기존 사내에서 사용되는 공통 Admin 시스템은 사용하는 모든 도메인이 관련 프론트엔드 저장소 한 곳에 있다 보니 개발배포가 번거로울 때가 있습니다프로젝트가 무거워 빌드에도 시간이 꽤 걸리고 어플리케이션 별로 자체 스케일링하기 어렵습니다. 관련 어드민에서는 외부 시스템과의 연동을 위해 SSO(Single Sign On)을 지원합니다이를 이용해 사내 쿠버네티스 시스템인 fusion에 올릴 수 있는 java/kotlin, node.js 등으로 변경하면 관리포인트가 줄어 유지보수에 매우 도움이 됩니다또한 닷넷 프레임웍을 쓰지 않기 때문에 맥북으로 개발이 가능해집니다. 하지만 사내 어드민 공통 기능은 자체적으로 구현해야 하고 별도의 admin qa를 거쳐야 하므로 개발기간이 길어질 수 있어 개발과 유지보수의 trade off 관계라고 생각합니다팀 내에서는 같은 기술스택이지만 다른 어드민을 작업하고 있었기 때문에 수월하게 적용할 수 있었습니다.
  • HTMX + Thymeleaf → 마찬가지로 cshtml이 아닌 htmx를 이용하여 admin ui 구성했습니다. 완성도보다는 빠르게 기능을 구현하는데 목적을 둔 어플리케이션이지만 htmx 적용을 테크니컬 챌린지라고 말씀드릴 수 있습니다. (물론 일정이 촉박해질수록 후회를 안 할 수가 없었지만..)

HTMX에 대해 한마디 하자면...

html만 봐도 이해하기 쉽습니다.

 

htmx는 별도의 스크립트를 이용하지 않고 서버와 ajax 통신을 도와주는 프론트엔드 웹 프레임워크입니다. 기존에 통신하기 위해 수많은 js 코드를 만들어야 했는데 보일러 플레이트를 꽤 많이 없애 코드량 자체가 줄어드는 효과가 있습니다.

hx-” 프리픽스가 붙은 속성을 선언해 간편하게 원하는 데이터를 만들어 낼 수 있습니다필요한 기능은 Extension으로 만들 수 있어 사용성이 무궁무진합니다. 또 빌드 단계도 별도로 없어 즉시 개발할 수 있게 도와줍니다.

장단점이 아주 명확한데요장점은 서버 통신할 때 js 정말 사용하지 않고 html 본연의 목적에 충실할 수 있다

특히 thymeleaf와 결합하면 레이아웃 재사용으로 리액트의 컴포넌트 재사용과 같은 효과를 가질 수 있습니다백엔드 개발자 혼자서 프론트를 만들 수 있다는 것도 장점입니다

단점은 json 데이터로 api 통신을 주고받았다 보니까 받았다 보니까 html 시맨틱 태그를 응답받는 것에 적응할 시간이 필요하다는 것과  간단한 통신을 해도 쉽지 않다는 것입니다. 특히 컨트롤러에서 @RequestBody만 주로 사용해 오다가 @RequestPart, @ModelAttribute로 바꾸고 data class가 바인딩되지 않는 이슈는 한참을 헤매게 만들었습니다.

데이터를 주고받는 데이터 패킷량도 많아져 트래픽이 많이 몰리는 프론트 서비스에서는 사용이 부담스러울 수 있습니다

무엇보다 필요한 기능은 결국 js를 써야 합니다 써야 합니다. Csv 엑셀 다운로드, form reset이나 date-picker를 사용하기  위한 공통 함수 작성.. 그리고 typescript를 쓸 수 없다..는 것이 또 큰 단점이죠. Htmx 레퍼런스가 많이 없어 거대한 js 덩어리를 디버깅해 가며 입맛에 맞게 수정했던 경험도 있네요

어플리케이션 구현할 때 기술스택에 있어서 정답은 없습니다. fastify 대신 express를 쓸 수도 있고 nest.js를 적용할 수도 있습니다. spring 대신에 ktor를 사용할 수도, htmx 대신에 handlebar나 rythm 템플릿, jsp를 쓸 수도 있고요. 다양한 기술들은 저마다의 다양한 장단점을 가지고 있습니다. 

 

2주간 매일 스크럼을 진행했습니다.

짧게는 1분 길게는 10분 정도의 데일리 스크럼을 진행했습니다. 현재 진행하고 있는 작업과 진척 상황을 공유했습니다. 데일리 스크럼을 통해 프로젝트의 불확실성을 조율할 수 있었고, 짧은 시간 동안만 진행하기 때문에 불필요한 추가 업무나 의논이 생기지 않았습니다. 또 별도의 회의실이 필요하지 않아 자리에 모여 진행할 수 있었습니다.

 

팀 내에서 자체 QA를 진행했습니다.

서비스가 론칭되려면 QA(Quality Assurance) 단계를 거쳐 서비스의 이상은 없는지 확인을 하는데요 QE팀과 담당 PM분들이 같이 진행을 합니다. 사내 PM 분들의 리소스가 넉넉한 편이 아닙니다. 이때 저희 팀원분들의 자체 QA 단계를 먼저 진행해 굉장히 많은 도움을 받을  있었습니다서비스의 대략적인 프로세스만 설명하고 별도의 가이드가 제공되지 않은 상태로 자체 QA를 진행했는데요사소한 버그부터 불편하다고 생각하는 동선혹은 추가 기획 아이디어 등 수준 높은 피드백을 받을 수 있었습니다. 또한 참여하신 모두가 개발자이신 만큼 이를 개선할 방법을 제시해주시기도 하셨습니다. QA 시나리오에 없는 내용들도 꼼꼼하게 봐주셨기 때문에 서비스가 더욱 견고해질  있었던 이유라고 생각합니다. 론칭 준비 중인 쇼룸도 자체 QA를 진행해서 굉장히 큰 도움을 받았는데요, 개인적으로 저는 이런 팀 문화가 매우 만족스럽습니다. 

 

개발을 하던 와중에 마주한 몇 가지 이슈 상황이 있었는데요, 몇 가지 소개해보도록 하겠습니다.

Oracle DB에서 pk가 순차적(serial)으로 채번 되지 않는 이유가 있었습니다.

 

기능을 구현하고 dev 테스트를 진행해 보니 pk가 순차적이지 않고 들쑥날쑥이었습니다들쑥날쑥 이었습니다. 저는 주로 사용했던 PostgreSQL에서는 Serial 데이터 타입이 존재해 기본적으로 pk는 순서가 보장되었지만 Oracle은 뭔가 달랐습니다. OracleDB 환경은 RAC(Real Application Cluster) 구조로 되어있습니다네트워크 단의 L4, L7 스위치 같은 로드밸런서와 비슷한 역할을 하고 있는데요, N개의 Instance를 통해 동일 DB에 접근할 수 있는 방법입니다.

N개의 Instance DB의 자원을 이용하기 때문에한 node node의 부하를 분산시키는 효과를 갖습니다.

 

위 RAC 구조에서 Sequence를 생성하게 되면 설정되어 있는 CACHE 옵션만큼 채번을 해 메모리로 올려둡니다. (CACHE 옵션이 20이라면 1번 node에서 1~20까지, 2번 node에서 21~40까지...)

실제 트랜잭션에서는 커넥션을 맺은 Instance의 채번 된 sequence를 가져오게 됩니다. 그래서 순서가 보장되지 않을 수 있는 것이죠 (1, 21, 41...) pk 채번이 비즈니스 적으로 반드시 순차적인 Sequence가 보장되어야 할 때(주문, 결제 관련 트랜잭션 등)는 ORDER 옵션을 사용해 순서를 보장할 수 있습니다.

그러나 RAC 구조에서 CACHE + ORDER 옵션을 설정하고 Sequence의 NEXTVAL()을 사용해 채번을 하게 되면 Data Dictionary가 자주 변경 되고 Exclusive instance SV lock을 필요로 하게 됩니다. 이는 동일 세션에서 Hang을 유발할 수 있기 때문에 DB에 부하를 주게 됩니다. Cache 크기를 크게 잡는다면 메모리 휘발로 인해 오히려 사용되지 않을 수 있으므로 적절한 캐시 크기를 잡는 것이 중요합니다.

따라서 보통의 경우 유일함(Unique)만을 보장하고 싶다면 NO ORDER 옵션을 통해 Sequence를 채번하는 것이 좋습니다. 오라클에서도 CACHE 크기는 크게 잡고 NO ORDER 옵션을 권장합니다. (캐싱을 하지 않는 NO CACHE 옵션은 Oracle에서도 사용하지 말라고 권고) RAC에 대해 더 알고 싶다면 Oracle 공식 가이드를 참고하세요.

 

SP 대신 JPA, QueryDSL을 사용했습니다.

MSSQL의 경우에는 SP(Stored Procedure)라고 불리는 방식의 쿼링을 하는데 native query를 미리 만들어두고 이를 검수, 배포하여 어플리케이션에서 사용합니다. 쿼리의 성능을 보장하고 전사적으로 모니터링을 하기 위함입니다.

Oracle / MySQL의 경우에는 SP를 사용을 권장하지 않기 때문에 다른 DB팀의 검수 및 배포하지 않습니다. 대신 DB Consulting Jira를 통해 검수를 진행합니다. 사내에서 JPA, queryDSL 적용 가이드가 있었는데 다만 저희는 이러한 프로세스를 인지하지 못했었는데요, 실제로 dev 테스트 중에 DB Index를 설정하지 않아 조회 시 Full Scan이 일어났고 실행 시간이 매우 길어졌습니다. 

그 후 실제 적용할 Query는 만들었으나 이를 queryDSL로 포팅하는 과정에서 애를 먹었습니다. Admin에서 구매건별 구매자, 구매 금액등을 검색조건과 매핑하기 위한 화면을 그릴 때 필요한 상황이 있었는데요, 구매데이터를 저희가 생성하는 게 아니라 링크루 서비스로부터 받아오기 때문에 join조건이 상당히 복잡한 쿼리였습니다. 그 와중에 from절과 join절에 서브 쿼리를 사용하는 것을 queryDSL에서 지원을 하지 않아 의도한 대로 쿼리를 만들어낼 수 없는 이슈였습니다.

여러 자료를 찾다가 subQuery 자체를 마치 View처럼 Entity화 하는 방법을 발견했고 이를 적용해 해결할 수 있었습니다. 

 

사내에선 다양한 도구를 활용해 프러덕을 만들어갑니다.

사내에서 정말 다양한 개발도구를 이용해 업무를 진행합니다. it 인프라를 적극 활용하는 것이 빠르고 정확하게 서비스를 만드는 과정이라고 생각합니다.

Git & Github

개발소스는 형상관리 툴 중하나인 Git을 이용하고 있으며 사내 GitHub에서 소스 관리하고 있습니다. 협업을 하기 위한 필수 도구로 여러 개발자가 한 어플리케이션을 개발할 때 작업 내용, history 등을 볼 수 있습니다.

Jira

모든 업무의 끝과 끝에는 Jira가 있습니다. 업무의 최소 단위이기에 History 관리가 잘되어야 합니다. 사내 Infra 요청에도 Jira를 사용하기에 익숙해지는 것이 좋을 것 같습니다. 각종 문의도 jira를 통합니다.

Saturn Initializr

세상에는 다양한 개발 도구와 이를 도와주는 라이브러리들이 있는데 보안을 이유로 사내에서만 사용하는 패키지들이 있습니다. 백엔드 어플리케이션을 생성할 때 필요한 사내 패키지를 주입해 만들어주는 Saturn Initializr를 통해 더욱 빠르게 만들 수 있습니다. 

Fusion & 사내 VM 툴

사내 Cloud Native 오케스트레이션 툴인 Fusion은 RedHat이라는 회사에서 만든 Openshift라는 툴입니다. Github 주소로부터 개발소스를 불러와 빌드, 배포하고 라우팅 할 수 있는 강력한 도구입니다. BSD 같은 큰 이벤트를 앞두고 아주 쉽게 똑같은 서버를 복제해서 운영할 수 있기 때문에 개발자들에게 있어 필수로 다뤄야 하는 툴이라고 생각합니다.

Cloud 자원이 아닌 가상 물리 서버인 Virtual Machine은 사내에선 다른 이름으로 제공해주고 있습니다. 저는 리눅스 서버를 다루는 게 편해서 해커톤이나 메세지큐 적용을 위한 poc를 해당 서비스를 통해 이용해 본 경험이 있습니다. 요새는 대부분의 툴들을 Docker 이미지로 받을 수 있으니 fusion을 이용하는 추세입니다. 사내에 docker 이미지 저장소에 원하는 이미지가 없다면 보안검수를 통해 등록하는 것이 가능합니다. 

Kibana & 사내 로그 모니터링 시스템

쿠버네티스 환경에서 최소 단위의 하드웨어, 즉 서버를 Pod라고 부르는데 이 Pod 개수가 수십, 수백 개가 되면 각 서버 내에 적재되는 로그를 찾기가 매우 어렵습니다. Pod에 쌓이는 로그를 Elastic Search라는 검색엔진을 통해 취합하고 이를 시각화하는 도구인 Kibana를 통해 타임라인별 로그를 확인할 수 있습니다.

개발자가 유의미하다고 판단되는 로그를 모아 볼 수 있게 한 사내 서비스도 있습니다. 개발 환경별로 패키지가 제공되어 매우 간편하며 fusion을 이용하지 않는 기존 C# 닷넷에서도 해당 로깅 시스템을 이용하고 있습니다.

nGrinder

부하 테스트를 위한 툴입니다. Naver에서 오픈소스로 만들었고 서버가 견딜 수 있는 트래픽을 의도적으로 부하를 주어 성능 테스트를 진행할 때 사용합니다. 이번 꿀템의 경우도 실제 서비스에 얼마나 많은 사람들이 방문할지 모르기 때문에 아주 많은 성능 테스트를 진행했습니다. ride가 10만 건, 100만 건 일 때 스트레스 테스트도 문제없었습니다. nGrinder 환경 세팅은 fusion을 통해 세팅할 수 있습니다.

Datadog

사내에서 사용하는 APM(Application Performance Monitoring) 툴입니다. 운영 중인 어플리케이션의 요청이 기록으로 남기 때문에 타임라인 별로 트래픽을 비교하고 에러 건수를 확인할 수 있습니다. 

 

 

Dependency Map 등을 통해 에러가 어느 지점에서 발생했는지 유추할 수 있어 에러 추적에 용이합니다. 또한 경보 체계를 만들어 장애 발생 시 Teams나 Slack으로 alert이 갈 수 있도록 합니다.

 

 

이번 서비스를 런칭하면서 이런 의의가 있다고 생각합니다.

  • 급하게 POC를 진행했음에도 불구하고 빅스마일데이 기간 동안 장애 없이 서비스했다는 점
  • 개발 조직으로부터 시작된 서비스 → 아이디어를 제시하는 것 뿐아니라 구현까지 단기간에 이루어진 케이스
  • 프러덕 디벨롭은 누구나 챌린지 할 수 있다. → 긍정적인 영향 전파

결국 꿀템이 오픈될 수 있었던 이유는 유관부서의 적극적인 협조가 있었기에 가능했습니다. 이 자리를 빌려 다시 한번 감사하다는 말씀드립니다.꿀템은 댓글 기능, 신고하기 기능 등 다양한 신규 기능을 가지고 한가위 빅세일에 다시 한번 진행할 예정입니다! 앞으로 성장할 꿀템 서비스를 잘 지켜봐 주세요!

댓글