티스토리 뷰
안녕하세요. Seller 팀에서 판매자 대상으로 업무를 진행하고 있는 개발자 박명훈입니다. 오늘은 지난 회고록에서 이야기했던 업무에 대해 좀 더 정리해서 이야기해보려고 합니다. 아직까지 부족한 부분이 많지만 나름의 발전 정도라고 생각해주시면 감사하겠습니다.
Gmarket과 Auction에서 쓰는 상품 리스트, 상품 상세 페이지에서 사용하는 브랜딩 서비스에 대해 서술합니다. 저는 뒷단의 백엔드 서비스를 담당하여 개발하였습니다.
브랜딩 정책은 비지니스 룰이 많아서 규칙이 까다롭기 때문에 여러 테이블을 조인해서 기준을 판단하며, 요청마다 이러한 테이블을 조인해서 응답을 주게 되면 이는 곧 성능 저하로 연결됩니다.
Gmarket과 Auction의 상품에서 브랜드 정보 요청은 모든 사용자들이 사용하므로 많은 요청량을 가지고 있습니다. 이 때문에 이러한 요청이 왔을 때마다 RDB에서 해당 정책을 만들어서 확인 여부를 확인해 주는 것은 RDB에게도 과한 부담이 되고, 사용자 경험적으로 좋지 않습니다. 따라서 이 데이터를 정제한 후 캐싱하여 사용자에게 제공해줄 필요가 있었습니다.
일반적으로 다들 아시겠지만 RDBMS와 Nosql의 차이는 다음과 같습니다
RDBMS | NoSQL | |
목적 | 범용 | 범용, 대량의 데이터 추출, 관계 분석, 탐색 등 |
스키마 | 엄격함 | 유연함 |
확장성 | 수직적 확장 | 수평적 확장 |
ACID 트랜잭션 | 지원 | 대부분 지원하지 않음 |
크게 Nosql 모델은 4가지 특성(Key-Value Database, Document Database, Column Family Database, Graph Database)으로 나눠지는데 이중에서는 Key-Value 구조를 선택했습니다. Key-Value 모델은 저장과 조회라는 원칙에 가장 충실한 구조입니다. Key-Valaue Nosql 모델 중에서 Redis를 선택한 이유는 많은 기업들이 사용하면서 많은 문서와 안전성을 제공하고, 다양한 데이터 타입을 지원하며, 영속성을 가지는 특성으로 인해 선택했습니다.
일반적으로 대용량 트래픽이 들어왔을 때, 취할 수 있는 전략은 다음과 같습니다.
- 수직적 상승
- 수평적 확장
Redis는 수평적 확장에 강점이 있으며 추가적으로 데이터를 쓰는 비율보다 조회 비율이 압도적으로 많기 때문에 사내에서 사용하는 Redis의 master-slave 구조도 적합하다고 할 수 있습니다.
백엔드 내에서도 크게 두 서비스로 구성을 했습니다. 한 서비스는 Redis에 저장되어 있는 브랜딩 정보를 Front에 데이터를 서빙하는 API 서비스이고 다른 서비스는 DB에서 데이터를 브랜드 규칙에 따라 정제하여 올려주는 Sync API 서비스입니다.
Redis에 저장되어 있는 브랜딩 정보를 서빙해주는 API 서버의 경우, 브랜딩에 대한 키를 대상으로 정책과 판매자 정보를 들고 있기 때문에 이를 서빙하여 Front 사용자에게 제공해주는 기능은 설계적으로 큰 이슈가 없었습니다. 그러나 DB의 데이터를 Redis에 Sync 해주는 API 서버의 경우, 비지니스 룰이 많기 때문에 설계적으로 많은 고민이 있었습니다.
아래에서는 초기 아키텍처와 그로 인한 문제, 이후의 아키텍처를 그리며 가졌던 시행착오에 대해 이야기합니다.
최초의 공식 브랜드 Sync API 설계
최초 요구사항이 들어왔을 때는 공식 브랜드 요구사항만 있었습니다. 공식 브랜드는 판매자와 브랜드 번호에 따라 정책을 제공하는 구조였습니다. 따라서 이 비지니스 룰에 맞춰서 데이터를 싱크 해준다고 판단했습니다.
다만, 이 당시는 입사 후 거의 첫 프로젝트였고 아키텍처적으로 많이 공부를 하지 않은 상태였기 때문에 부족한 부분이 많았습니다. 이를 간단하게 표현하면 다음과 같습니다.
다만 해당 프로젝트의 경우, 클래스에 대한 책임이 무거운 등의 SOLID 원칙이 어긋나는 구조였습니다. 그로 인해 확장성과 유지보수성에 단점이 많았습니다.
요구 사항의 변경과 디자인 변경
그러던 와중, 기존의 공식 브랜드가 아닌 패션 스퀘어, 백화점, 아울렛, 홈쇼핑과 같은 브랜드 태그 정보를 주어야 했고 이에 따른 좀 더 개선을 할 필요가 있습니다. 최초에는 기존의 형식을 확장시키면 된다고 생각하였으나 특정 브랜딩의 경우에는 판매자에 종속되어 있고 다른 경우는 브랜딩 정보까지 필요한 경우가 있었습니다. 따라서 기존의 확장성이 없는 구조로 진행을 하기에는 어려운 상황이었습니다. 그에 따라 다시 비즈니스 룰을 명시하고 진행할 필요를 느꼈습니다.
또한 많은 비지니스 룰을 정리하지 않으면 이를 추가 개발하는 다른 사람, 혹은 해당 이슈를 확인할 때 어려운 부분이 많습니다. 그렇기 때문에 최대한 비즈니스 룰을 추상화하고 정형화할 필요가 있었습니다
재설계를 진행할 때 가장 신경을 썼던 부분은 확장성과 유지보수성이였습니다. 재설계 시 신경 쓴 부분은 다음과 같습니다.
- Enum 형을 통해 각 브랜딩 정보를 명확하게 정리
- Enum 형을 Controller에 추가하여, 특정 오류가 발생한 경우 브랜드에 따라 수동으로 정제를 하는 기능을 추가
- 클래스와 함수의 책임을 줄이기
- SOLID 원칙에 준수하기
- 네이밍을 통해 다른 개발자가 봤을 때 정확히 무슨의미인지를 파악하게 하기
- 기존의 오류를 수정하기
- 방어적 코드를 구성하기
- 인터페이스를 통해 브랜드 별 기능을 분리하기
- 저장 로직을 기존 코드와 분리하기
- 테스트 레버리지를 100%로 만들기
이러한 부분을 신경써서 구성한 아키텍처는 다음과 같습니다. (물론 부족한 부분이 많고 간단하게 작성을 하였기 때문에 오류가 있을 수도 있지만 주니어의 자그마한 부족함으로 이해해주시면 감사합니다.)
결과적으로.
신규 프로젝트에 참여하여 설계부터 프로젝트를 진행함으로써 얻은 것이 많았습니다. 또한 기존 아키텍처에서 개선을 통해 관리적인 측면에서 이점을 얻었습니다. 따라서 현재 해당 서비스는 추가적인 조치 없이 정상적으로 잘 작동하고 있으며 추가로 신규 요구사항이 들어왔을 때, 큰 수정이 필요없이 반영할 수 있게 되었습니다.
개인적으로도 해당 프로젝트는 성장을 할 수 있었던 프로젝트 였습니다. 설계적으로 부족한 부분이 많아 객체지향의 사실과 오해, 객체지향과 디자인 패턴, 리팩터링 등의 책을 보며 공부했던 내용을 프로젝트를 통해서 반영할 수 있었고 성장할 수 있었습니다.
해당 프로젝트는 사용자 입장에서도 상품에 대한 명확한 가이드를 제공하며, 사용자 경험을 올린 좋은 경험이라고 느낍니다. 물론 아직까지 부족한 부분이 많고, 추가적으로 수정할 부분을 성장함에 따라 많이 느끼지만 그럼에도 이를 통해 성장하고 있음을 느낀 좋은 기억입니다.
이상으로 주니어의 부족한 내용이지만 읽어주셔서 감사합니다.
'Backend' 카테고리의 다른 글
객체는 어떻게 식별하고 구현해야 할까? (0) | 2022.08.17 |
---|---|
Java Generic 을 파헤쳐보자 - 심화편 (0) | 2022.08.10 |
Linger 로 오버헤드 줄이기 (0) | 2021.07.14 |
try-catch 지옥 벗어나기 (0) | 2021.05.21 |
Rust Memory Management (2) | 2021.05.07 |