안녕하세요.
늦은 밤.. 주니어 개발자로서 궁금한 점이 생기어 이렇게 글을 남기게 되었습니다...
현재 진행중인 프로젝트에서 상품 테이블이 있고 상품별 판매수량이 있으며 일괄이 아닌 낱개 판매가 가능한 상품입니다.
이러한 경우에 초당 3000명이 결제할 수 있고 1분 내에 18만명 정도가 결제가능한 시스템을 만들고자 할 때, 시스템 아키텍처를 어떻게 가져가야 할 지 모르겠습니다 ㅠㅠ
1. 트랜잭션 시작
2. 상품 조회 (비관적 락 적용, select for update)
3. 상품 수량 마이너스 처리
4. 결제 API 호출
5. 결제 API 200 OK 시, 트랜잭션 종료
6. 결제 API 200 OK 아닐 시, 예외 발생하여 롤백처리
비즈니스 로직은 위처럼 되어있고 비관적 락, 낙관적 락 둘 다 적용하여도 퍼포먼스가 제대로 안 나오는 상황입니다 ㅠㅠ
고민해본 결과, 레디스를 도입해서 아래 플로우를 생각해봤습니다.
1. Redis에 상품별 판매가능 수량을 미리 캐싱
2. 트랜잭션 시작
3. Redis 상품수량 조회 + decrease 원자적 연산 수행
4. 결제 API 호출
5. 결제 API 200 OK 시, 트랜잭션 종료
- 재고처리 이벤트 발행하여 DB 데이터 싱크
6. 결제 API 200 OK 아닐 시, Redis 판매수량 increase
다만 이렇게 되면 레디스 상품 수량을 decr 한 다음 서버가 죽어버리면 판매수량 데이터가 일치하지 않는 경우가 있어 옳은 방법인지 모르겠습니다..
고수 개발자님의 인사이트를 공유해주시면 감사하겠습니다 ㅠㅠ
늦은 밤.. 주니어 개발자로서 궁금한 점이 생기어 이렇게 글을 남기게 되었습니다...
현재 진행중인 프로젝트에서 상품 테이블이 있고 상품별 판매수량이 있으며 일괄이 아닌 낱개 판매가 가능한 상품입니다.
이러한 경우에 초당 3000명이 결제할 수 있고 1분 내에 18만명 정도가 결제가능한 시스템을 만들고자 할 때, 시스템 아키텍처를 어떻게 가져가야 할 지 모르겠습니다 ㅠㅠ
1. 트랜잭션 시작
2. 상품 조회 (비관적 락 적용, select for update)
3. 상품 수량 마이너스 처리
4. 결제 API 호출
5. 결제 API 200 OK 시, 트랜잭션 종료
6. 결제 API 200 OK 아닐 시, 예외 발생하여 롤백처리
비즈니스 로직은 위처럼 되어있고 비관적 락, 낙관적 락 둘 다 적용하여도 퍼포먼스가 제대로 안 나오는 상황입니다 ㅠㅠ
고민해본 결과, 레디스를 도입해서 아래 플로우를 생각해봤습니다.
1. Redis에 상품별 판매가능 수량을 미리 캐싱
2. 트랜잭션 시작
3. Redis 상품수량 조회 + decrease 원자적 연산 수행
4. 결제 API 호출
5. 결제 API 200 OK 시, 트랜잭션 종료
- 재고처리 이벤트 발행하여 DB 데이터 싱크
6. 결제 API 200 OK 아닐 시, Redis 판매수량 increase
다만 이렇게 되면 레디스 상품 수량을 decr 한 다음 서버가 죽어버리면 판매수량 데이터가 일치하지 않는 경우가 있어 옳은 방법인지 모르겠습니다..
고수 개발자님의 인사이트를 공유해주시면 감사하겠습니다 ㅠㅠ
원본글 링크
https://www.teamblind.com/kr/s/HeKk42Bu
최근 본 블라 글중 제일 유익하노 ㅋㅋ
내가 심심해서 커뮤를 3~4개정도 보는데 좋은것들만 걸러서 보면 실력향상에도 도움되는듯 - dc App
또 뭐있음
여기랑 블라랑 dba오픈카톡이나 커뮤, 디스코드 디스코드는 그냥 자기하고 싶은거 하는 어린친구들이 주로있긴하드라 - dc App
해당 댓글은 삭제되었습니다.
토스가 특히 좋아함 토뱅도
넌 댓글 볼수 있냐 댓글이 메인인디 - dc App
차에코야 개소리로 뭉개지 말고 솔루션을 제시할거면 똑바로 제시해라
또또 뭉개네 그럴거면 그냥 조용히 있어
알아듣지도 못할 경험담 씨부리지 말라고 임마 순도 98%로 너를 위한 말이다 버릇이 이상하게 들었음 ㄹㅇ
새끼..기합!
차에코 이새키는 본인은 명확한 솔루션이 없고 어디서 주워들은거 끼워 맞추는데 하나도 안맞음ㅋㅋㅋ 면접 탈락하기 딱 좋음ㅋㅋ
대규모 트래픽 처리는 한가지 방법만으로 솔루션을 낼수가 없음 항상 보완재가 필요하기 마련이니까 고민할때 참고해라
답이 모야?
고민해서 글을 올려라 피드백 줄게
참고하려 했는데 사라졌음..
안사라졌는데
존재하지 않는 글이래
본문은 내가 여기 복사해다 놨고, 댓글은 볼 필요 없음 어차피 저 문제의 답은 단 하나뿐이고, 댓글들 중에 가치있는 것들도 다 비슷한 이야기를 하고 있다
지금 생각나는건 레디스 HA를 위해서 일단 센티넬 방식으로 구성한 다음, redis 캐싱해놓고 트랜잭션은 재고 감소까지만 해놓고(결제 응답 길어질 시 커넥션 고갈) 결제 API는 실패 시 롤백 트랜잭션 실행하도록 하고 이벤트 쌓아놓은거 DB 싱크 맞추면 될 것 같은데..
재고 감소까지만 트랜잭션 해놓으면 다른 로직은 어카노;; 문제를 역으로 꼬아버린 것 같은데
다른 로직이라면 어떤거??
니가 재고 감소까지만 해놓는다며, 그럼 트랜잭션에서 빠지는 로직이 있다는거잖아
아 내말은 결제 API 호출만 트랜잭션에서 빼는거였슴 외부사에 문제가 생겨서 트랜잭션이 길어지면 그만큼 커넥션을 길게 들고있는거자나 트랜잭션 밖으로 빼서 실패 시만 롤백 트랜잭션 생성해주면 성능에 도움이 될 것 같은데 !
아 위에 써져있는거로만 말한거군, 그러면 트랜잭션 안에서 하는게 redis 처리밖에 없잖아 ㅋㅋㅋ 트랜잭션 왜쓰냐
그리고 외부사에서 문제가 생겼는지 아닌지는 어떻게 알아? api는 실패해도, 타임아웃 등의 이유면 외부사 내부적으로는 성공한 상태일수도 있어
내가 결제 도메인을 해본적이 없어서 찾아봤는데 일단 타임아웃이 나서 요청의 상태가 [알 수 없음] 이면 일단 그 상태로 트랜잭션을 저장하고 고객에게 재시도를 유도한 다음 해당 요청건이 [알 수 없음] 상태로 저장된 트랜잭션인지 확인하고 후처리를 통해서 해당 결제를 처리한다네. 추가적으로 후처리 또한 타임아웃에 의해 [알 수 없음] 상태가 되면 일단 트랜잭션을 종료하고 후에 대사 배치에서 정합성이 안맞는건 잡히게 되고 별도 오퍼레이션을 통해서 후보정 한다고 나와있음. 고객 계좌에 돈은 여유금으로 선지급 한다고 함.
잘 찾았음 굿굿
나 궁금한게 내가 설계한 아키텍쳐 퍼포먼스는 어떻게 측정하는거임? 도구가 있음? 나도 내가 만든 서비스 성능 측정해보고 시픔
구글링을 먼저 이것저것 해봐
여러가지 도구가 있지 근데 니가 만든 서비스 성능 측정은 별 의미가 없음.. 장비 스펙과 개수도 중요하거든 니 레벨에서는 설계적으로만 고민하는게 좋다
레디스가 죽었을 때 지금까지 판매된 수량을 어떻게 복구 할지가 너무 궁금한데 AOF로 로그 저장해서 다시 띄울떄 복구하는건 뭔가 너무 느릴거같고 redis replication도 해결책이 될 수 있을거 같은데 아직 잘 모르겠고 그냥 해당 키로 지금까지 발행된 재고처리 이벤트 수 카운팅해서 다시 채우는건 별로임?
DB랑 redis랑 이중으로 관리해야지 DB는 반영이 좀 느린거고
100개 있는데 50개 팔리면 이벤트 50개 발행됐고 그 50명은 느려도 실제 DB에 뭐 구매 정보같은게 저장되고 만약 50개 팔리자마자 레디스 죽어서 판매 카운트 날라가면 DB에서 구매정보 카운팅 해서 다시 채워주면 된다 맞음?
ㅇㅇ 어차피 redis가 전부 다 죽으면 장애 상황이고 서비스가 불가능해 그럼 DB에 이벤트 다 반영되는거 확인하고 다시 redis 올려서 서빙해야지
그리고 궁금한게 저렇게 순간적으로 트래픽이 몰리는 상황에서 재고 관리할 때 락 거는건 성능 이슈가 너무 크고 결국 레디스 같은걸로 재고관리+동시성 처리까지 하고 성공한만큼만 이벤트 발행해서 쓰기 연산 비동기로 처리하는게 최선의 해결법에 가까움?
ㅇㅇ 대규모 환경에서 락 거는거 아니야
문제는 레디스는 비싼 자원이라서 순간적으로 트래픽이 몰릴만한 것들에 대한 예측이 필요함 모든걸 레디스에 올릴순 없지, 10년 전에 올린 상품이고 아무도 안사는거면 걍 db에다 직접 decrease 날리는게 나음
ㄱㅅㄱㅅ 이거가지고 꽤 고민했는데 좀 해소됨
그럼 또 궁금한게 생기는데 이게 어떤건 순간적으로 몰리니까 레디스로 처리해줘야하고, 어떤건 이제 주문 별로 안들어와서 DB로 처리하는데 그걸 개발자가 일일히 그때그때 레디스에 넣고 말고 할 수도 없고 지금 짧게 고민해봐서 생각나는건 한번 레디스로 재고관리 했던 물품은 TTL걸고 DB에 기록해두고 나중에 레디스에서 지워지면 그걸 플래그 삼아서 레디스에 최초로 서빙할지 안할지 구분하는 방법은 어떤거 같음?
그런식으로는 좀 어렵고, 상품을 인기 상품으로 올리면 id가 바뀌도록 하는게 좋을 것 같음 정확히는 기존 상품을 내리고 인기 상품이라는 새로운걸 등록하는거지 그 과정에서 redis에도 입력하면 데이터 정합성이 어긋날 일을 없음 인기 상품으로 올리는건 실시간 로직, 새벽 배치, 관리툴 등등으로 수행할 수 있겠지
기존 상품을 그대로 인기 상품으로 변환하면서 db에서 redis로 올리면 타이밍 이슈로 db랑 redis랑 싱크가 안맞을수도 있음 이벤트 기반으로 맞추는게 가능은 한데, 복잡도가 미친듯이 올라갈듯
오 일단 아이디어 감사합니다 한번 어떻게 인기 상품을 관리할 수 있을지도 고민해봄요
리마큐에 있는 index skip locked 이용하면 어떤가요
되겠냐? 에라이..
뭐야 나 왜. 지워졌엉 플래그(처리중)하나 꽂아서 레디스나 mysql터질때 정합성유지하거나 보상하는 방식을.. - dc App
넌 좀 어렵게 생각해라, 개소리를 너처럼 당당하게 말하는거도 재주다
긴 과정을 꽉꽉 압축해서 간단히 요약느낌이.. 나중에 이런질문 나오면 그냥 플로우 다 말해본다ㅅㅂ - dc App
아니 그냥 다 틀렸음
저 진지하게 이런 처리중 같은 추가적인 플래그를 세우는 방법이 싹다 틀려먹은걸까요? 장난아니고 조금 의아해서 댓남깁니다 - dc App
응 싹다 틀려먹은거야 설마 내가 너랑 장난한다고 생각하는거야?
미안합니다 - dc App
틀린건 문제가 아닌데, 정신에 굉장한 문제가 있음 그런식이면 백날 공부해봤자 아무것도 모르는 사람이랑 다를게 없다 한국어 안다고 말 잘하는거 아니야
이 댓글은 게시물 작성자가 삭제하였습니다.
이 댓글은 게시물 작성자가 삭제하였습니다.
이 댓글은 게시물 작성자가 삭제하였습니다.
이 댓글은 게시물 작성자가 삭제하였습니다.
초보라 대규모 트래픽, 레디스, 카프카, 결제 시스템에 대해서 생각해 본 적이 없어서 문제 상황 이해조차 어려운데 내가 이해한 것이 맞음? 정상작동 한다면, 레디스에 DB의 데이터를 캐싱 -> 사용자 물건 구매 요청 -> 레디스에 decrease 원자적 연산을 수행 -> 외부 결제 API에 요청 -> 외부 결제 API 200 OK 응답 -> 이벤트 발행 -> DB는 이 이벤트를 보고 데이터를 Update. 서버가 다운된다면 레디스에 DB의 데이터를 캐싱 -> 사용자 물건 구매 요청 -> 레디스에 decrease 원자적 연산을 수행 -> X서버다운X 여기서 블라인드 작성자의 고민은 레디스가 decrease 원자적 연산을 수행하고 난 뒤, 이벤트를 발행하기도 전에 서버가 다운 돼 버리면
DB가 이벤트를 받을 수가 없으니 redis의 데이터가 변경된 것도 인식을 못하게 되며, 결과적으로 Redis의 데이터와 DB의 데이터가 달라서 문제가 된다는 거임?
내가 이해한건 redis 서버가 다운되는 상황 같음 그냥 서비스 서버가 다운되면 어차피 요청이 못들어 오잖어
redis가 죽든 네트워크에 순단이 생기든 api서버가 처리 도중에 죽든 어느 식으로도 redis와 db간의 정합성이 깨질 수 있음
요는 트랜잭션의 범위(row, 시간 둘다)는 최소로 가져가되 외부로의 요청을 어떻게 보정할거냐 인데. 결론부터 말하면 실시간으론 불가능함. 그래서 후보정 배치 같은거 돌려서 보상 트랜잭션 만드는거고. 실제로 쇼핑몰에서 결제까지 했는데 가끔 결제 완료, 주문완료로 바로 안넘어 가는 경우도 봤을거임. 보통 금방 재처리돼서 완료로 바뀌는거고
*외부로의 요청을 어떻게 보정할거냐 -> 보장할거냐
그런걸 방지하려면 아웃박스 패턴 써야지 트랜잭션 열고 이벤트 저장, 레디스 decrease 트랜잭션 커밋
근데 결제 api를 아웃박스로 치면 결제 결과는 콜백으로 받아옴?
증감 이벤트만 아웃박스로 친다는 소리였음
니 말대로 후보정을 하던지, 완전히 방지하고 싶으면 아웃박스로 빡세게 관리하던지 선택하기 나름일듯
요런거 공부할만한 도서 있나 - dc App
ㅇㅎ 나도 곧 신규 결제 시스템 볼게있어서 ㅋㅋ
책으론 그나마 가상면접이랑 데이터중심 애플리케이션..?
개인적으론 가상면접이 고민해볼법한 문제들 많아서 재밌었음 (아직 다 못읽음)
아웃박스 패턴 지금 쭉 읽고 왔는데 그러면 트랙잭션 잡고 있는 시간은 더 길어지는거 아님? 이벤트 발행을 따로 뺀게 트랜잭션을 빨리 끝내서 TPS올리고 이벤트 처리 결과는 후반영하겠다는 느낌 아니야..? 잘 몰라가지고
아 댓글 다는 사이에 추가로 달렸네 증감에만 아웃박스치면 괜찮겠다
증감과 결제는 나누어서 보는게 맞을듯 후보정에서 함께 보더라도
이 상품 재고 설계 내용을 콘서트 티켓 자리 예매 설계에도 적용 할수있음? 각 자리를 재고 1개 남은 개별 상품으로 보면 돼?
ㅇㅇ
저 궁금한거 잇슴 db update를 count >=0조건 으로 때리고 affected row 0이면 에러 던지고 롤백타는건 깔고 가는거 맞남요?
아니
잉 그럼 일반적인 로직은 그냥 비관적락이나 낙관적락 걸고 트랙잭션 돌려요?
db 업데이트를 비동기로 해서 트랜잭션 분리하는게 핵심인데 혼자 뭔 뻘소리하냐
구매가 좀 있는상품말고 경합이 적은 비인기상품쪽에서는 rdb로만 처리하는게 나을거같고 그부분 걍 저렇게 해두는게 낫지않나 생각했어서요. 글고 비동기 이벤트 받아다 후처리할땐 트랙잭션으로 rdb 업데이트하는것도 동일하고
그때는 락잡고 돌릴건데 저렇게 돌려도 될거같아서요
작은 프로젝트만 레디스 카프카만 써봤고, 깊은 고민을 해본적 없어서 허술한점 먼저 사과드립니다.. 앞서 이야기한것처럼 Redis 도입으로 어느정도 문제는 해결되었으나 Redis에 감소시키고, 결제하는 과정에서 서버가 다운되면
어떻게 재고를 다시 보상할 것 인가에 대한 문제라고 이해했습니다. 가장 먼저 든 생각은 Redis에 (stockKey, amount)를 감소하는 연산을 하고, (임의의 Key, {(stockKey, amount}) 를 TTL를 걸어서 가지고 있다가 레디스 측에서 만료되면 pub/sub으로 만료를 알려주고, 해당 값은 amount만큼 따로 배치에서 올려주
는 방식을 생각했습니다. 성공시 TTL을 지워주몬 되고 실패 시 이를 참고해서 올려주면 된다구 생각했어요..이부분도 틀리지 않았을까 생각해요 ㅠ 근데 해당 방법은 서버가 죽은동안 레디스가 pub/sub를 날리면 이는 서버가 받지 못할거라고 생각했습니다. 그래서 중간에 카프카를 두면 어떨까 싶더라
구요. 레디스에서 keyspace Notifitation을 사용하면 만료된 데이터를 카프카로 바로 보낼 수 있다고 검색을 통해 알았는데 중간에 MQ가 있다면 서버가 다운되도 해당 메세지는 살아있으므로 서버가 살아나면 처리되지 않을까 생각했습니다ㅠ
흐름 끊길가봐 미리 적고 복붙함다
글 지우지마라다오 결제도메인 보는데 도움많이대네 중간에 댓글 하나가...
그냥 단순하게 생각하면 주문내역 테이블을 만들고, 한 트랜잭션에서 (redis에서 상품 구매시 decrease 하는 연산) + (주문내역을 rdb에 insert 하는 연산)을 수행하고 redis가 다운되서 상품 재고 정합성 문제가 있을 때 주문내역에 select count 이렇게 조회해보면 되지 않을까요?
insert 연산 자체는 동기적으로 수행될 필요가 없으니깐 성능 문제도 없을거같은디..
왜 첫 번째 방식이 낮은 퍼포먼스를 보이게 될려나. 1) DB 관점에서, 단일 상품에 대한 결제는 레코드 락으로 인하여 퍼포먼스가 낮을 거 같은데, 불특정 다수의 상품에 대한 결제는 락이 겹치지 않아서 괜찮지 않나? 2) 서버 관점에서, 락에 의하여 스레드가 대기 상태에 빠진 경우에도 퍼포먼스가 낮을 것 같긴하네...
락도 락이고 트랜잭션 범위가 애초에 외부 연동사 API까지 있기 때문에 커넥션을 길게 들고 있어서 요청이 밀림.
낙관적 락 적용하고 단일 큐에 밀어넣으면 됨