컬리 카트 도메인이 1년간 4세대에 걸쳐 진화한 개발 연대기다. 라라벨 기반 카트가 주소지 기반 재고 노출이라는 비즈니스 요구와 데드락·응답속도라는 기술부채를 풀기 위해 Legacy에서 Stack, Modern, Multi 카트로 재설계된 과정을 담았다.
핵심 포인트- 2020년 김포센터 오픈에 맞춰 주소지 기반으로 물류센터 재고·전시·할인을 조회하는 구조로 카트를 재설계
- 1세대 Legacy Cart: 3,000줄 컨트롤러를 Interface·Service로 분해하고 Manager Pattern·Cart Response 객체 도입
- 2세대 Stack Cart: 트리에서 선형 구조 마이그레이션을 위한 dual write를 시도했으나 데드락으로 실패, 롤백도 불가
- 카트 데드락의 원인은 회원의 n세션 1카트 구조와 최신 1개만 남기는 cart cleansing 경합
- 4세대 Multi Cart: 카트끼리 아이템을 공유하는 구조와 cleansing 제거로 경합을 없애 데드락 근본 해결에 근접
상세 정리- 목표: 비즈니스 측면은 주소지에 포함되는 물류센터 코드 기반으로 재고·전시·할인 API를 조회하는 것, 개발 측면은 레거시 탈피·테이블 개선·DDD 도메인 분리·데드락 해결·응답속도 개선이었다.
- 1세대 Legacy Cart: 라라벨에 맞춰 약 3,000줄이 몰린 컨트롤러를 Interface와 Service 단위로 분해하고, foreach로 매번 조회하던 걸 relation 구조 Eager Loading으로 바꿨으며, Manager Pattern과 Cart Response 객체로 상품구조를 관리했다.
- 2세대 Stack Cart: 트리 구조 Legacy와 선형 구조 Modern은 형태가 달라 table migration이 어려워, 같은 데이터를 순간마다 동시에 적재하는 dual write로 자연스러운 전환을 노렸다.
- Stack 실패: 데드락이 많던 기존 로직과 신규 로직이 dual write하며 크게 앓았고, 주문 골드타임(밤 10~11시)마다 API 성능 지표가 무너졌다.
- 롤백 불가: 앱이 이미 배포됐고 카트가 네이티브를 쓰는데 Endpoint 규격이 바뀌어, 1세대로 온전히 롤백하려면 강제 업데이트가 필요해 리스크가 컸다.
- 3세대 Modern Cart: Stack 배포 시 기반이 어느 정도 완성돼 약간의 리팩토링으로 배포했고, 개편 전이던 주문서가 Modern 구조를 못 받아 주문서 진입 전 상품 리스트를 구 카트로 부분 마이그레이션하는 방식으로 연결했다.
- 남은 데드락: Modern으로도 특정 트래픽에서 카트가 휘청거려 데드락을 궁극적으로 해결할 때가 왔다고 판단했다.
- 데드락 원인: 비회원은 1세션 1카트지만 회원은 n세션 1카트라, 여러 플랫폼 동시 로그인 시 최신 1개만 남기는 cart cleansing이 서로 카트를 뺏으며 경합·장애를 유발했다.
- 4세대 Multi Cart: 카트끼리 서로의 아이템을 뺏지 않고 공유하는 구조로 바꾸고 cart cleansing을 제거해(1세션 유지에서 n세션 유지로) 경합을 없앴다. 대신 카트 id와 아이템 관계를 알아야 해 쿼리는 복잡해졌다.
- 기술부채 상환: 100원딜 상품 검증 로직과 쿼리를 리팩토링하고, 카트 아이템 개수 표현에 캐시 레이어를 넣었다.
- Modern vs Multi: 추가·수량 변경·삭제·리스트·체크아웃 동작에서 Modern은 B 카트 아이템을 A로 강제 Update 후 처리하는 반면, Multi는 A·B 카트를 함께 조회하고 group by로 처리하도록 비교·정리했다.
- 5세대 계획: 카트가 다른 도메인의 장애 지표가 되지 않도록 불필요한 DML을 줄여 Write DB 리소스를 아끼고 대규모 트래픽을 빠르게 처리하는 걸 목표로 설계 중이다.
왜 읽나커머스 카트처럼 세션 경합·데드락·레거시 마이그레이션을 안고 도메인을 여러 세대로 재설계하는 백엔드 개발자에게 dual write·공유 구조 전환의 실전 회고.