장바구니 공지사항
NHN아카데미 인증과정 프로젝트 발표회는 ‘장바구니’라는 주제로 팀을 대표해 발표됐다.
저에게 너무 뜻깊고 좋은 경험이었기에 이렇게 글을 남깁니다.
이 글은 노션에서 작성했습니다.용어 링크
카트 구현 프로세스
1. 설계 및 구현
첫 번째 장바구니의 경우 구현 목표는 MySQL로 구현하는 것이었습니다.
회원의 장바구니 데이터를 영구적으로 보관하기 위함이었습니다.
팀원들과 ERD 설계 및 구현에 대해 이야기할 때 무신사, 쿠팡 등 실제로 사용하는 쇼핑몰을 예로 들었습니다.
회원의 장바구니를 영구적으로 만드는 것에 대해 이야기했고 그에 따라 구현하고 싶었습니다.
인터넷 기사에서도 사용자 사용 패턴 분석기사를 보니 스팀 기능이 있는 장바구니를 이용하는 유저들이 많다.
지금 사지 말라는 글이었는데 장바구니에 담는 행위가 매출로 이어집니다.
즉, 이것이 실제 운영에 큰 영향을 미칠 것이라고 믿었습니다.
다만, 비회원의 경우 DB에 저장하기가 어려웠습니다.
그래서 비회원은 Redis로, 회원은 MySQL로 구현을 시작했습니다.
문제
- 분기 코드는 모든 코드에서 회원과 비회원을 구분하기 위해 작성해야 했습니다.
- 회원이 아닌 비회원에게 고가의 리소스인 Redis를 사용하는 데는 포인트가 있습니다.
- 이 구현으로 인한 이점은 명확하지 않습니다.
- ‘왜?’, ‘확실히?’ 의문을 제기하는 구현 방법이었습니다.
첫 번째 구현을 위한 코드
@PostMapping("/product/{productNo}")
public boolean productAddToCart(@AuthenticationPrincipal Object principal,
@CookieValue(value = COOKIE_NAME)Cookie cookie,
@Pathvariable(value = "productNo")Integer productNo) {
if (principal instanceof UserDetailsDto) {
...
return cartService.addProductMemberToCart(cartMemberRequestDto);
}
...
return cartService.addProductAnonymousToCart(cookie.getValue(), productNo);
}
@DeleteMapping("/product/{produtNo}")
public void productDeleteToCart(@AuthenticationPrincipal Object principal,
@CookieValue(value = COOKIE_NAME) Cookie cookie,
@Pathvariable(value = "productNo") Integer productNo) {
if (principal instanceof UserDetailsDto) {
...
cartService.deleteProductMemberToCart(cartMemberRequestDto);
return;
}
...
cartService.deleteProductAnonymousToCart(cookie.getValue(), productNo);
}
2차 처형
그래서 구현 방식을 바꾸고 싶었다.
회원과 비회원 모두 Redis로 카트 데이터를 관리합니다.하다,
회원용 로그아웃 시 Redis에서 사용 가능한 장바구니 데이터는 MySQL DB로 이동되어 저장됩니다.이렇게 구현했습니다.
이 구현은 위의 문제를 해결할 수 있습니다.
쇼핑몰의 특성상 일정한 시간에 많은 이용자들이 모여들고,
이것은 현재 DB에 많은 상품 정보를 담고 있어 DB를 덜어줍니다.
Redis에서 장바구니 상품 정보 캐싱 및 사용할 수 있다는 장점도 있었습니다.
그리고 회원가입을 하면
로그인 전 저장된 데이터와 DB에 있는 데이터를 합치는 방식에 의해 관리받는
또한 회원 정상적으로 로그아웃하면~ 안에
Redis에 있는 장바구니 데이터를 DB로 옮기고 저장하는 방법로 구현되었습니다
이를 통해 다음 흐름을 구현할 수 있었습니다.

그러나 보다 구체적으로 말하자면 연속 기입 패턴이 아닙니다. 쓰기 되돌림 패턴 구현 그것은 이미 끝났다
패턴을 통해 쓰기
데이터를 데이터베이스와 캐시에 동시에 저장하기 위한 전략
저장할 때 데이터를 캐시에 먼저 저장한 후 DB에 직접 저장합니다.
캐시와 DB를 함께 업데이트하여 데이터 정합성을 유지할 수 있어 안정적이다.
단점은 각 요청에 대해 두 개의 쓰기가 있기 때문에 성능 문제가 발생할 수 있다는 것입니다.*다시 쓰기 패턴 *
데이터는 일정한 주기적인 연산을 통해 캐시에 수집되어 관리되고 DB에 저장됩니다.
캐시 및 DB 동기화가 비동기화되므로 동기화 프로세스가 제거됩니다.
캐시에 수집되어 DB에 기록되기 때문에 쓰기 요청을 가져오는 비용과 부담을 줄일 수 있습니다.
단점은 캐시에 오류가 있으면 데이터가 유실될 수 있고 캐시와 DB 값이 다른 상황이 발생할 수 있다는 점이다.
따라서 장바구니는 후기입 패턴을 사용하여 구현되었습니다.
그러나 이 부분에도 문제가 있었다.
바로, 회원님 비정상적인 로그아웃~의 경우 Redis에 존재하던 장바구니 데이터붓다 어떻게든 DB에 저장나는 무엇을 해야할지 생각해 왔습니다.
내가 처음 생각한대로 Redis에 저장된 키의 접두사를 스케줄러로 찾아 만료 시간을 확인하고 저장하는 방법~였다
그렇다면 비정상적인 로그오프로 인해 만료되기 전에 Redis에서 관리하는 데이터를 성공적으로 찾아 DB로 옮겨 저장할 수 있을 것이라고 생각했습니다.
이런 생각을 갖고 실행에 옮겼습니다.
@Scheduled(cron = "0 0 0 * * *")
public void saveAllCartByRedis() {
Set<String> keys = redisTemplate.keys("CID=*");
...
keys.forEach(
key -> redisHashMapList.add(redisTemplate.opsForHash().entries(key))
);
...
cartAdaptor.saveAllCart(list);
}
그 결과 비정상 로그오프 시 Redis에서 사용 가능한 데이터를 성공적으로 DB로 이동하여 저장할 수 있었습니다.
문제
그러나 위의 구현에는 큰 문제가 있었습니다.
곧, Redis에 있던 모든 키 스캔그렇게 해야 했다.
공식 Redis 문서에 따르면 프로덕션 환경에서는 데이터 풀 스캔이 권장되지 않습니다.했다
또한, Redis는 단일 스레드입니다.때문에 데이터 풀 스캔 중
다른 요청은 처리할 수 없습니다.그리고 동시에 Redis에 작업이 들어오면 문제가 될 것이라고 판단했습니다.
2차 시행 대비 개선
그래서 다른 방법을 생각해봤습니다.
그렇게 많은 생각을 하다가 Redis 데이터가 만료되면 어떤 메시지가 표시됩니까?나는 그것에 대해 걱정했다.
그러던 중 Redis에서 제공하는 기능인 Pub/Sub 기능에 대해 알게 되었습니다.
Redis 게시/구독이란 무엇입니까?
이벤트(메시지)를 생성하는 게시자가 있습니다.그리고 발행인 특정 채널(또는 주제)에 대해 이벤트 보내기하다.
특정 채널(또는 주제) 구독 가입자가 존재합니다게시자와 상관없이 **게시된 이벤트를 수신할 수 있습니다.
**
Redis 채널은 문자 그대로 TV 채널을 생각할 수 있습니다. 하루 종일 수백 개의 채널이 TV에서 방송됩니다. 각 방송사(출판사)의 생방송은 해당 채널 시청시에만 시청이 가능합니다. 또한 같은 채널의 모든 구독자는 같은 프로그램을 동시에 시청할 수 있습니다.
그리고 Spring Data Redis에서 동등한 기능을 추상화하여 사용하기 쉽다는 것을 알았습니다.
수업 때문에 Redis에서 데이터가 만료되는 경우 이 데이터에 대한 메시지를 받을 수 있었습니다.
하지만, 관련된 데이터의 키 값만 가져올 수 있습니다. 그 안에 존재하는 데이터를 검색할 수 없는 문제가 있었습니다.
이 함수는 데이터가 만료되는 즉시 메시지를 보내므로 Redis에서는 데이터가 만료되는 즉시 그 안에 있는 데이터가 사라집니다.
추론을 통해 이 문제를 해결하려면 원본 데이터보다 낫습니다. 먼저 만료되는 임시 날짜 포함그래서,
이 임시 데이터가 원본 데이터를 참조하도록 합니다. 원본 데이터를 얻는 방법함께 왔다
따라서 원본 데이터의 키 Phantom 접미사가 있는 데이터는 회원 로그인 시 함께 삽입됩니다.
관련 날짜는 원래 날짜보다 5분 일찍 만료됩니다.현재 Pub/Sub 정보 받은 메시지를 사용하십시오.원본 데이터를 찾기 위해 DB로 옮겨 저장하여 구현끝내다
이를 통해 이전에 언급한 데이터 풀 스캔 문제를 해결하고 원래 계획한 대로 배포를 완료할 수 있었습니다.
지금까지 구현에 대해 이야기했고 이제 프리젠테이션에 대해 이야기할 것입니다.
팀 내 발표 주제 선정
프로젝트가 끝나고 각 팀에서 발표 주제를 정하고 마지막으로 판교에 있는 NHN 본사로 가서 발표를 했습니다.
팀 내에서 어떤 주제를 고를지 고민할 때,
담당자는 구현 내용이 나쁘지 않고 구현이 현재 사업과 매우 유사하며 발표를 계속하면 좋을 것이라고 말했습니다.
또한 각 팀이 발표 주제를 겹치지 않게 선정해야 했기 때문에 다른 팀과의 차별성이 있어야 했다.
우리 팀이 유일하게 이런 방식을 구현했고, 내가 팀원들과 만나 발표를 책임졌다.
발표회는 대학시절에도 자주 접해보지 못한 경험이었고, NHN 현직사원 뿐만 아니라 현직사원 및 타 협력사 채용담당자들도 참석하여 발표회에 대한 책임감과 설렘을 많이 느꼈습니다.
그래서 최선을 다해 준비해야겠다는 생각에 PPT를 아래와 같이 수정했습니다.
대본도 준비했고, 발표 내용을 완벽하게 이해하려고 노력하면서 마스터하려고 노력했습니다.

발표 당일



준비를 잘 해서 그런 건지 일시적인 각성 효과인지는 모르겠다.
발표 당일은 생각보다 긴장하지 않아서 잘 마무리 했습니다.
그러나 이 발표에는 분명히 몇 가지 단점이 있었습니다.
제 발표 내용이 현직자들에게는 자명할 수 있다고 생각해서 많은 관심과 질문을 받지 못했습니다.
하지만 프레젠테이션을 준비할 때부터 많은 사람들 앞에 서서 내가 구현한 내용과 내 생각을 전달할 수 있는 프레젠테이션을 경험하는 것은 많은 의미가 있었습니다.
즉, 신인으로서 나는 기존 사람들이 당연하게 여길 만큼 충분히 구현한 방식으로 인정받는 것이 아니라 인정받는다는 느낌을 받을 수 있었다.
의미 있는 경험을 하게 되었고 이런 기회를 주신 NHN아카데미와 약 3개월 동안 저와 함께 해주신 모든 팀원들에게 감사드립니다.
