본문 바로가기

반응형

전체 글

(24)
10주간의 이커머스 개발을 통해 설계부터 대규모 데이터처리까지 경험하기 드디어 10주간의 여정이 끝났다. 10주 동안 개발해온걸 간단하게 요약하자면 다음과 같다. 1~3주차 : 도메인 모델링, 계층 분리, 객체 협력 설계4~6주차 : 트랜잭션과 동시성, 읽기 최적화, 외부 시스템(결제 PG) 연동과 회복 탄력성7~9주차 : 이벤트 기반 분리, Kafka와 실시간 집계, 랭킹 시스템 구축10주차 : 배치와 Materialized View를 통한 대규모 집계와 조회 최적화유저가 거의 없는 솔루션 회사에 재직한 내가 대용량 트래픽처리에 대한 경험을 해볼 수 있는건 매우 값진 시간이었다. 특히 학습 과정에서 떠오른 경험이 하나 있다. 이전 회사에서 공공기관과 협력하며 약 100만 건의 데이터를 삭제해야 했던 작업이 있었다. 당시에도 인덱스에 대한 기본적인 이해는 있었지만,EXPL..
Redis ZSET로 랭킹 시스템 구현하기 무신사나 29cm 같은 e커머스에 필수 기능인 랭킹 시스템은 과연 어떤식으로 구현되어있는 것일까? 나는 이전에 kafka 기반의 이벤트 파이프라인을 구축하였다. 이번에는 그 위에 Redis ZSET을 얹어 “조회/좋아요/주문” 신호를 가중치로 녹여 내는 랭킹 시스템을 만들었다. 왜 API는 Redis에서 조회하고 Streamer에만 적재하여 책임을 분리한 것일까? commerce-api 는 조회, 좋아요, 주문 등이 발생할 때마다 이를 이벤트 형태로 Kafka에 produce 하고, Kafka에 적재된 이벤트는 commerce-streamer 에서 consume 된다 (여기까진 이전 글에 더 자세히 적혀있다)streamer는 배치 리스너를 통해 이벤트를 묶어서 수신한 뒤, 이벤트 유형에 따라 가중치를 ..
Kafka, 동작 과정 샅샅이 파해쳐보기 요즘 개발자 포토폴리오에 빠짐없이 적혀있는 Kafka, Redis 그 중에서도 Kafka는 매우 뜨거운 주제라고 볼 수 있다. 근데 Kafka를 처음 써보는 나로서는 어떠한 동작 과정으로 처리하는지 이해가 어려워 실제 구현보다 이론에 조금 치중한 한주를 보냈고 그 고민을 이 곳에 풀어보는 시간을 가지려고 한다. Kafka는 분산 로그 저장소(Distributed Log Store)다. Kafka Components에는 Broker, Cluster, Topic, Partition등이 있다. 복잡한 용어 대신 나만의 용어로 쉽게 풀어보자면 “프로듀서는 특정 터널(Topic)에 메시지를 push하고, 톨게이트 매표소(Broker)는 이를 차선(Partition) 단위의 로그로 저장하며,컨슈머는 자신에게 ..
ApplicationEvent 를 활용해 Facade 경량화 하기 나는 E-commerce 개발을 하면서 Application Layer에 점점 비대해진 Facade를 마주하게 되었다.특히 CreateOrder와 같은 기능은 주문 생성이라는 하나의 유스케이스 안에서 여러 책임을 동시에 처리하고 있었다. createOrder() ├── 재고 차감 ├── 포인트 차감 ├── 결제 요청 └── 주문 저장 아래와 같이 주문 생성 과정에는 재고, 포인트, 결제, 주문 저장까지 서로 성격이 다른 작업들이 한 번에 엮여 있었다. 이러한 구조에서는 내부 도메인 로직뿐만 아니라 PG와 같은 외부 모듈 호출까지 하나의 트랜잭션 안에서 함께 처리되었다. 그 결과, 트랜잭션의 경계를 명확히 나누기 어려웠고, PG 장애와 같은 외부 요인으로 인해 결제 요청이 실패하면 이미 검증이 끝난 주문 ..
외부 PG 지연이 전체 응답을 망치던 문제를 서킷 브레이커로 해결하기 이커머스 개발을 하며 외부 PG 서버의 지연과 오류가 내 서비스 전체 응답 속도를 악화시키는 문제가 발생하였다. 나는 이를 해결하기 위해 Resilience4j 서킷 브레이커(Circuit Breaker)을 사용해 외부 장애가 내부 서비스까지 확산되는 것을 차단하였다. 아키텍처 개요호출 흐름: commerce-api → PG(Feign) → 콜백(PG → commerce-api)보상 흐름: 콜백 실패/지연 시, PENDING 건을 일정 주기로 동기화(폴링)하여 상태 확정서킷 브레이커를 사용한 이유? Retry 회복 전략은 잘못 사용시 서버에 부하를 주거나 DoS 공격처럼 동작할 수 있다.서킷 브레이커는 “실패율이 임계치를 넘으면 빠르게 실패”시켜 전체 시스템을 보호한다 (누전 차단기의 역할)서킷 브레이..
인덱스를 이용하여 50만건의 데이터 빠르게 조회하기 + k6를 이용한 성능 테스트 상품 데이터가 약 50만건이 넘어가고 나면 정렬 조건을 바꿀 때마다 수 초의 시간이 걸린다. 만약 해당 쿼리 조건이 복잡하면 복잡해질수록 걸리는 시간은 더욱 길어진다. 나 또한 10건 정도로 생각하고 코드를 테스트 해볼 때엔 해당 문제를 직면하지 못했으나, 50만건의 데이터를 넣은 후엔 Swagger에서 조회할 때 엄청난 시간이 걸리는 것을 확인하였다. 참고로 상품 데이터는 DataInitializer를 통해 상품 CSV 파일을 Import 하였다. @Slf4j@Component@Profile({"data"})@ConditionalOnProperty(name = "seed.enabled", havingValue = "true")@RequiredArgsConstructorpublic class Da..
JPA CountBy 조회 vs 엔티티 필드 저장, DDD에서는 무엇을 선택해야 할까? 쇼핑몰 서비스를 만들다 보니 제품에 눌린 좋아요의 총 개수를 계산할 때 Product 엔티티에 likeCount 같은 상태 필드를 추가할 것인가,아니면 Like 테이블에서 countByProductId()로 매번 조회해서 계산할 것인가라는 선택의 기로에 섰다. 일부분만 코드로 적어보자면// Product Entity@Column(name = "like_count", nullable = false)private Long likeCount = 0L;public Long getLikeCount() { return likeCount;}public void increaseLikeCount() { this.likeCount = (this.likeCount == null ? 1L : this.likeCou..
Facade 패턴으로 레이어 책임 분리하기 나는 지난주 이커머스 기능을 개발하며, Point를 VO(Value Object)로 할지, 별도의 Entity 테이블로 만들지 고민하다가 후자를 선택하였다. 가장 큰 이유는 별도의 테이블로 구현하는 것이 VO로 구현하여 User 테이블에 포함되게 하는 것보다 추후 복잡한 연산이나 비즈니스 로직을 적용하기에 적합하다고 판단했기 때문이다. (물론 이 또한 지난주 피드백을 통해 처음부터 미리 이렇게 설계하지 말고, 점진적으로 발전시키라고 하셨다) 그리하여 별 문제 없이 public class UserController { private final UserService userService; private final UserDtoMapper userDtoMapper; private final P..

반응형