본문 바로가기
항해99 취업 리부트 코스/WIL

[항해 취업 리부트 코스] 개인프로젝트 3주차 후기

by 봄의 개발자 2024. 5. 7.

들어가며

3주차... 대망의 3주차... 지금까지는 아무것도 아니었다. 프로젝트 주차의 피날레...? 랄까 😵‍💫

이번주에는 결제 관련 api를 만들고 자동화 테스트 툴 구축하는 것이 목표이다. 

우선 결제 관련 api를 만들면서 상품 재고 감소 -> 주문 -> 배송 -> 결제 이 프로세스로 동작한다.

현재 결제 진입 api는 거의 다 완성한 상태고 결제 api가 남아있다. 결제 프레세스에 대해 EDM을 활용해서 이벤트를 처리할 생각이다. 

원래는 AWS sqs 를 사용했지만 대용량 처리라는 주제에 걸맞게 카프카를 도입해보기로 했다.

어제는 카프카 세팅을 완료하고 토픽을 생성하고 재고 감소, 주문, 배송 프로세스까지 완료 했다.

 

트러블 슈팅 및 기술적 고민

  • Circuit Breaker 메소드에 적용하는 방식
  • circuit breaker fallback method 호출 안되는 문제
    • CircuitBreaker 어노테이션이 적용되는 실제 메서드의 반환값과 동일해야함
  • resilience4j의 circuitbreaker의 fallbackmethod와 retry fallbackmethod의 차이점
- 호출 시점의 차이:
    - CircuitBreaker의 fallbackMethod는 회로가 열린 상태에서 호출됨
    - Retry의 fallbackMethod는 최대 재시도 횟수를 초과했을 때 호출됨
- 사용 목적의 차이:
    - CircuitBreaker의 fallbackMethod는 서비스 장애 시 대체 데이터 제공 등의 목적으로 사용됨
    - Retry의 fallbackMethod는 재시도 실패 시 대체 데이터 제공 등의 목적으로 사용됨
- 장애 처리 방식의 차이:
    - CircuitBreaker는 회로 개념을 사용하여 장애를 격리하고 관리함
   	- Retry는 재시도 로직을 통해 일시적인 장애를 극복하려 함

 

  • postman 테스트 시 날짜(localDateTime) 받아올 때 json parsing 에러
    • pattern이 맞지 않은 것이 원인
    • yyyy-MM-dd HH:mm → yyyy-MM-dd’T’HH:mm
  • 상품페이지 표기 재고 수량 설정 방식 비교
    • 전체 상품 수 - 결제 프로세스 도입 수
      • 실제 재고 수량에 가깝게 표기할 수 있지만 결제 프로세스에 들어간 상품이 취소되거나 반품되는 경우 재고 수량 표기가 정확하지 않을 수 있음
    • 전체 상품 수 - 결제 완료 수
      • 결제가 완료된 주문의 상품 개수만큼 재고를 차감하므로 재고 수량 표기가 더 정확해짐 하지만 결제 프로세스에 진입했지만 아직 완료되지 않은 상ㅍ무은 재고 수량에 포함되어 있기 때문에 실제 재고와 차이가 날 수 있음
  • 결제 시나리오
더보기

 

* 캐시 미스 혹은 Redis 다운 -> RDB 접근

1. Rdeis 재고 유효성 검증 

2. Redis 재고 감소

-> 실패 시 예외 발생 (품절 등) -> 주문 취소

3. 주문 성공 -> 주문 완료 상태 변경

4. 결제

 

  • aws sqs 에 대한 고민
    • sqs는 카프카 처럼 토픽이라는 개념이 없다.
    • 메시지를 보내면 어떤 메시지인지 구분을 할 수가 없다.
    • group id 가 토픽과 동일한 역할을 한다.
  • 레디스 캐싱과 레디스 분산 락을 통한 재고 관리
  • 트랜잭션 범위 줄이기 vs. 데이터베이스 호출 횟수 줄이기

 

멘토링 정리

5월 1일 멘토링

더보기

Q. 결제 API

A. 많은 걸 구현하고 싶다 → portone 그냥 되어있는거 쓰겠다 → toss payments

서버에 api를 요청을 보내는 것 부하 테스트 할 때 많은 요청을 보내는 건 안됨 → mocking만 해두는 식으로 사실 그렇게 몰리는 거 자체가 사실 말이 안됨 우리 서버만 괜찮으면 그쪽도 괜찮음 남의 api를 테스트하진 않음

토스페이먼츠 추천! 실제로 사용하려면 유료, 포트원은 공짜

 

Q. 상품 재고 관리

A. 절대 over selling 이 일어나면 안된다.

동시성 제어가 중요!

덜 팔리는 건 괜찮음 → fix가능한 에러 더 팔리는 건 안됨 재고에 10개밖에 없는데 20개가 팔린다? 절대 안됨!!

 

Q. 개인 질문

A. user : notion_page = n : m user ? vs notion_page ?

중간 테이블 위치 notion_page에 있는 게 웬만하면 낫지 않을까 !?

우려하는 것: 만약 user에 있으면, user table이 너무 복잡해짐 (?)

각 도메인마다 분산되어 있다면 user 쪽으로 몰리진 않음 이런 식으로 고려해보기 상위 개념을 골라서, 극단적으로 생각해보기

 

Q. 추가 질문1 현업에서 재고 관리할 때 레디스를 활용하는지?

A. 사용 용도가 맞는지 레디스로 해도 되고, 안해도 되고 -> 극한의 상황이라면 사용할 수 밖에 웬만하면 db로 처리가능 (동시성 처리가 잘 되어있다는 전제 하)

레디스에 저장해두고 특정 시점 update (재고가 0이 되는 순간) 등등 여러가지 방법이 있음

레디스 → 싱글 쓰레드라서 동시성 제어가 자동으로 되는 거임 → db보다 엄청 빠름

 

5월 3일 멘토링

더보기

4주차까지 다 구현해야 한다는 강박관념을 가지지 말자!!

Q. 상품 주문 요청이 들어오고 이벤트를 발생시키며 작업을 수행할텐데, 주문 관련 이벤트를 처리할 때 주문 서비스에서 상품 서비스로 OpenFeign을 사용하여 조회 요청을 보내도 상관없을지 궁금합니다.

A. 당연히 해도 된다. 그러나 최대한 EDM 방식을 구현하지 않았으면 좋겠다!! 일단은 Feign으로 구현하라!!

→ SQS로 하다가 끝가지 못할 수 있다. 일단 feign으로 처리하라!! sqs에 목메일 필요가 없다.

→ SQS 없이도 Redis를 사용해 재고 관리 성능 향상을 가져올 수 있다!!

 

Q. 카프카 경우는 토픽으로 구분해서 이벤트를 처리하는 것으로 알고 있습니다. 찾아보니 aws sqs는 토픽과 같은 개념을 사용할 수 없는 것 같습니다. 그럼 메세지 그룹 id 값을 지정한 후에 원하는 그룹 id에 대한 메세지만 처리해주는 식으로 해야할까요? 아니면 이벤트별로 큐를 따로 만들어서 사용하는 게 좋을까요?

A. sqs에서 메시지 그룹 ID를 토픽이라 생각하면 된다!! 이벤트별로 큐를 여러개 사용할 필요 없다. 서버리스라 내부적으로 자동으로 스케일아웃된다!!

 

Q. aws sqs로 이벤트를 처리하다가 에러가 발생하면 어떤 처리를 해줘야하는지 잘 모르겠습니다.

A. 어디서 에러가 발생하느냐에 따라 다르다. 일단 로그를 찍어두고 완성 후 수정해라

 

Q. 구현 요구사항 중 결제 진입 API, 결제 API 2개가 무엇이 다른지 모르겠습니다.

A. 결제 진입 API는 결제를 막 시작,

 

Q. 결제 시뮬레이션을 위한 결제 API 구현을 위해 별도의 서비스 모듈을 분리해야 하는지 궁금합니다.

A. 여러 군데서 사용될 수 있으므로 따로 분리하는 게 좋은 것 같다.

 

Q. 구현 요구사항에 재고 수량 정보를 Redis에 넣어 캐싱하라고 하는 것 같습니다. 이때 캐시 쓰기 전략을 Write Throw 방식을 써야할 것 같은데, 이때 만건에 대한 상품 주문을 처리할 때 캐싱으로 인한 재고 조회 성능 이점보다 캐시에 재고를 수정하는 것으로 인한 성능 저하가 더 크지 않을지 궁금합니다.

A. 그렇게 걱정할 필요 없다. 레디스는 디비랑 달라서 개빠르다!! 그래서 레디스로 인한 성능 저하를 생각할 필요 없다. 단, 레디스는 싱글 쓰레드라 O(N) 쿼리는 날리면 안된다.

 

Q. 이벤트 기반 방식을 사용할 때, 오류 발생 시 롤백을 해야할텐데 이때 이벤트 과정에서 발생하는 주문이나, 배송, 결제 같은 객체에 하나의 이벤트임을 나타내는 transactionId 같은 필드를 추가해서 하나의 주문 요청?임을 구분해야 하는지 궁금합니다.

A. 필요하다!! - 주문이 발생하면 주문 코드를 만들게 될텐데 주문 코드 같은 걸 사용하면 된다!! OrderId랑은 다를 수 있다. 예를 들어 주문을 취소하고 재구매 하는 경우 주문코드를 사용해 같은 주문으로 처리할 수 있다.

 

Q. 주문 요청이 들어왔을 때, 주문 서비스에서 주문 관련 처리를 하고 예를들어 결제, 상품 재고 감소 같은 다른 이벤트들을 한 번에 발생시키는 게 좋을지, 아니면 순서를 정해서 이벤트를 순차적으로 발생시키는 게 좋을지 궁금합니다.

A. 동시에 할 수 있으면 동시에 하는 게 좋다.

 

Q. OpenFeign Client를 컨트롤러에서 받아서 서비스로 넘겨주는 게 좋을지, 서비스에서 받아서 바로 사용하는게 좋을지 고민중이네요.

A. 의존성은 이렇게

→ 컨트롤러는 최대한 순수해야 한다.

→ feign client로 받은 데이터를 서비스를 거쳐 가야한다. 컨트롤러에서는 할 수 없다.

→ controller는 서비스 외에 의존성을 가지고 있으면 안된다!!

→ Controller에서 Repository로 바로 접근하는 건 좋지 않다.

 

Q. feign을 위한 별도의 패키지를 만드는 게 좋을까요?

A. 넵!! 기존 패키지 구조에 feign을 넣어도 괜찮습니다!

 

Q. 레디스를 사용할 때, 레디스는 중앙 집중식 형태로 구현되어야 하나.

A. 여러 모듈들이 하나의 레디스를 사용하도록

 

5월 4일 멘토링

더보기

Q. Open feign을 사용하였을 때 트랜잭션 관리는 어떻게 하는게 좋을까요?? (분산 트랜잭션)

A. 트랜잭션 관리를 못하는게 맞다. → 실패했을 때 어떻게 관리할 것이냐 → 기능 구현 먼저 다 끝내고 실패케이스 관리하는게 좋을 것 같다.

-> feign client : rest api 도와주는 라이브러리일 뿐 -> RestTemplate, Webclient

 

Q. 이벤트 기반 방식을 사용할 때, 예를 들어 결제 과정에서 에러가 발생하면 앞단에서 수행한 모든 과정 각각에 대해 한번에 롤백 이벤트를 발생시키는 게 좋을지, 아니면 바로 직전 과정에 대한 작업만 롤백 이벤트 발생시켜 순차적으로 롤백을 수행하는 게 좋을지 궁금합니다.

A. 하나의 과정에서 이뤄지는 거라면 하나의 롤백 이벤트를 두는 게 좋다. 여러개 두면 유지보수 측면에서 힘들다. 이벤트 발생 시 추적이 힘들다.

-> 결제 과정에서 롤백을 할 게 있나 ?

재고, 주문 상태 변경 -> 이벤트 히스토리가 쌓인다

재고를 먼저 감소시키는 게 좋다.

 

-> overselling 이 안 일어나는 게 가장 중요하다.

주문 요청이 들어오면 재고 감소부터 하고 주문 엔티티를 생성해라!

나중에 해도 유저 경험의 차이일 뿐 크게 상관은 없다.

 

Q. 실패에 대해 하나의 롤백 이벤트만 두고 각 서비스들이 이 이벤트를 구독해서 처리하는 게 맞을까요? 아니면 각 서비스별로 롤백 이벤트를 두는 게 나을까요?

A. 하나의 과정으로 이루어 지는거면 하나의 롤백으로만 이루어 지는게 유지보수면에서 낫다.

 

Q. 현재 배송 엔티티가 있는데 우리 프로젝트에서는 배송에 관한 별다른 프로세스가 없어서 단순히 주문이 발생하면 저장하는 용도로만 사용됩니다. 주문이 발생한다면 주문 서비스에서 주문을 생성하면서 배송도 함께 생성하는 게 좋을지 아니면 별도의 이벤트를 발생시켜 배송을 생성하는 게 좋을지 궁금합니다.

A. 이벤트로 처리하는게 좋다. 이벤트로 해야 나중에 배송 상태를 관리할 때 편하다.

 

Q. 만약에 배송을 별도의 이벤트로 분리한다면, 주문 과정 중 마지막에 진행할 생각인데 배송이 실패했다고 앞선 주문 생성, 재고 감소, 결제 등 여러 작업들을 모두 롤백 시키는 게 맞는 건지 궁금합니다.

A. 분리하는게 좋다. 롤백이 아닌 히스토리를 남기고 업데이트를 하는 방식이 맞는 것 같다.

생성, 재고 감소, 결제 등에 대해 update를 해줘야한다 

히스토리를 남기고 update하는 과정으로, 롤백은 시점이 멀리 있어서 어렵다.

예) 배송 엔티티 상태 변경

 

Q. 구매 요청이 들어오면 재고감소부터 하는게 좋을것 같다고 말하셨는데, 요구사항에선 레디스에 캐시해서 쓰는 방법을 쓰는데 재고감소를 어떻게 시키는지?

A. 레디스에서 재고감소를 시켜라. 레디스는 싱글스레드 방식이기 때문에 동기식이므로 재고관리에서 동시성 문제가 발생할 수 없다.

 

Q. 레디스의 성능이 그렇게 좋을 수 있는 이유

A. 메모리의 속도는 생각하는 것 이상으로 빠르다.

 

Q. 재고 감소부터 하는 게 좋다고 하셨는데 레디스 상품 재고 캐싱 -> 이때 캐싱하는 건 어디서 쓰이는 건지?

A. 재고 감소

-> 레디스 -> 동시성 제어를 크게 안 해도 된다. 

rdb 멀티 쓰레드, redis 싱글 쓰레드 -> mem이라서 그렇게 빠르다.

레디스: 실시간 스트리밍 (pub-sub) 메시지 큐 역할, sorted set 중복 제거, caching 등

AWS Elasticache -> redis 특화 서버

ehcache: Redis보다 빠르게,  로컬 메모리여서 더 빠르다. 네트워크 지연이 없기 때문에, 레디스보다 기능이 적음. 정말 캐싱 용도로만 (상태 관리는 불가)

redis 단점: 다른 서버에 있으니까 네트워크 속도에 영향을 받아서 우리가 어떻게 컨트롤할 수 없는 비용

 

write-back: 재고가 0일 때 rdb로부터 받아오기

레디스 날아가면 어떻게 되냐? 휘발성인데

replication 사용하든가 ->  aws elasticache, aof rds(레디스 정보 저장하는 공간) 키워드 공부해보기

 

5월 6일 멘토링

더보기
  • Write-back 방식으로 redis의 재고를 rdb에 업데이트 하려고 하는데, goods 마이크로 서비스에서 스케쥴러를 통해 일정 주기마다 업데이트 시켜주면 되는건가요?
    • yes! 사실 그 방법 뿐
    • dynamodb streams : 람다 트리거 가능 (시간이 남으면,,,)
    • 일정 주기, redis 재고가 0일 때 update
  • Write-back 방식을 사용할 때 레디스 서버의 에러로 인해 데이터가 휘발 될 경우의 백업은 어떻게 할 수 있나요?

개별 고민

  • 코드 더러운 건 나중에 리팩토링 하세요 지금은 시간이 없으므로,,, 빠르게 하세요!
  • 3주차가 젤 중요, 이력서 쓸 내용 많음
  • 최소 3주차까지는 완성할 수 있도록!
  • 레디스의 키 값을 어떻게 구성해서 재고관리를 할지 고민입니다.
    • set으로 재고 개수 파악할 수 있나?
    • set 쓰지말고 그냥 저장하세요!
    • unique key값으로 key-value 형태로 저장하기

 

  • 적어도 3주차 basic 기능이 다 돌아가게끔!!!