-
Notifications
You must be signed in to change notification settings - Fork 3
07 27
📄 원본(노션): https://app.notion.com/p/3aa19935b8ae80bbbacddbcdd632e7c8 — 최종 동기화 2026-07-27
- 석희
- 호프를 봤는데 좀 난해한 영화였다.
- 쉼부름 프론트엔드 UI 완성
- 모솔연2를 봤는데 이번주가 기대된다.
- 현성
- 스터디 카페 가서 개인공부
- 동혁
- 졸프 보고서 작업
- 스프링공부, 김영한 강의를 봄
- 현서
- 스프링 공부
- 카카오 api를 사용해서 DB 넣기 시연을 해보았다.
- 현성
- 기능명세 스프레드 시트 이해
- 작업 시작?
- 현서
- 스프링 관례 찾기 DTO 설정 등
- 스프레드 시트 보강
- 동혁
- 현성님과 작업 부분 분배
- 코드 짜기
- 석희
- 일단은 AWS말고 로컬에서 작업하자 (로컬 서버 + H2 DB)
- 앞으로 Discussion
14:00 ~ 17:30 프로젝트 멘토링
-
✅ 자바 공통 응답 양식 구현체 구현완료 (석희)
- 사용은 어떻게 하면 되는가?
-
✅ 이번주에는 어떤 작업을 할 것인가? 수요일 쯤 AWS에 연동하자.
-
✅ 자기소개 - 증명사진 기반 나중에 코드 쌀먹하기.
-
❓
@Validannotation - DTO단에서 걸기Spring 코드 스타일 - @Valid- 추가적으로 JPA entity단에서 해야할까
-
❓
@PreAuthorize어노테이션- Spring Security에 있어서 사용은 못함.
가능한 방법들:
- 서비스 레이어에 권한 인가 관련된 코드 상단에 작성
- Custom Annotation
- Spring Security에 있어서 사용은 못함.
가능한 방법들:
-
✅ Commonresponse → Enum의 error code? Spreadsheet에 기록을 해놓는 것으로.
-
✅ Penalty - 페널티, 패널티? 페널티로 일관성 통일
실시간 라이더 위치 전송을 HTTP+SSE로 할 것인가 아니면 Websocket으로 할것인가
AI에게 질문시 넘길 정보
- ms초단위로 양방향 동기화가 아님.
- 앱 없이 오직 모바일 웹만을 기준으로 해야함
- 웹소켓은 매칭 부분에서 미리 구현을 할 것임. 그래서 재사용 가능하긴 함
| 아키텍처 방식 | 배달원 → 서버 | 서버 → 주문자 | 주요 장점 | 주요 단점 및 한계 | 현재 상황 적합도 |
|---|---|---|---|---|---|
| 1. 순수 웹소켓 | WebSocket | WebSocket | • 양방향 실시간 통신 가능 • 통신 헤더 오버헤드가 가장 적음 |
• 이동 중 LTE/5G 전환, 탭 비활성화 시 연결 자주 끊김 • 핑/퐁, 재연결, 상태 복구 로직 구현 부담 큼 |
❌ 비추천 (모바일 웹 환경에 매우 취약) |
| 2. 순수 HTTP Polling | HTTP POST (3초) | HTTP GET (3초) | • 무상태(Stateless) 구조라 매우 단순 • 네트워크 끊김 걱정 아예 없음 |
• 주문자가 3초마다 GET을 쏘므로 서버/DB 부하 증가 • 서버 데이터 변경 없이 불필요한 요청 지속 |
🔺 MVP용 (가장 빠르게 만들 때만 사용) |
| 3. HTTP POST + SSE | HTTP POST (3초) | SSE (Server-Sent Events) | • 배달원 단말의 백그라운드 끊김 완벽 우회 • 주문자 브라우저가 자동 재연결(Auto-Reconnect) 지원 |
• 이미 구축된 매칭 웹소켓 외에 SSE 파이프라인을 추가로 유지보수해야 함 (기술 파편화) | 🔺 양호함 (웹소켓이 없을 때 최선의 대안) |
| 4. 하이브리드 [최종 추천] | HTTP POST (3초) | WebSocket (기존 채널) | • 기존 매칭용 웹소켓 인프라 100% 재활용 • 배달원은 POST 응답으로 주문 취소 즉시 감지 • 모바일 웹 끊김 우회 + 주문자 실시간 UX 확보 |
• 배달원 → 서버 구간에 3초 단위의 간격이 존재 (배달 도메인 기준으로는 문제없음) |
✅ 최적의 선택 (현재 구조에서 가장 깔끔함) |
결론: 일단 매칭하는 부분은 확실하게 웹소켓으로 하기로 했으니, 매칭 부분을 먼저 구현하고 그 다음에 실시간 위치 방식을 결정하자
웹소켓 장점
- 기존 코드 재활용 가능
- 빠르게 요청 응답 가능 (상태변경)
HTTP 장점
- 웹소켓은 상태가 있기에, 추후 서버
- 웹소켓 연결 끊겨도 복구 로직이 필요 없음
페어프로그래밍 https://velog.io/@congaweb/Pair-Programing
웹소켓 구현 프롬프트 (현성)
우리는 실시간 퀵서비스를 만들거고, 매칭 기능을 추가할거야
- 기술 스택 : SpringBoot 4.1.0 버전 / JDK 21 / SpringBatch 및 SpringSecurity 사용 금지 / 최대한 외부 라이브러리를 활용하지 않게 / 매칭 기능은 웹소켓으로 구현 / 프론트엔드는 웹 (React사용)
- 라이더(배달원)은 "드리미", 주문자는 "부르미"라고 할거야.
- 매칭은 서버에서 드리미와 부르미를 직접 연결해주는 시스템이야
- 매칭 대기 상태에 들어가있는 드리미와 매칭 대기 상태에 들어가있는 부르미가 서로 매칭 되는 시스템이야
- 서로 거리가 너무 먼 대상은 매칭에서 제외해야해
- 매칭 가능 범위 내에 들어오면, 매칭은 우선 드리미(라이더)에게 우선적으로 주문건을 매칭해줄거야.
- 이후 드리미가 해당 주문을 수락하면, 이번에는 해당 주문을 등록한 부르미에게 드리미 정보를 넘겨주고 부르미가 이를 수락하면 실제 배달 상태로 넘어가 (배달상태부터는 고려하지마. 일단은 매칭만 구현할거야)
- 부르미는 여러 주문을 등록할 수 있지만, 드리미는 한번에 하나의 매칭만 가능해
- 가능하면 상태는 인메모리에 저장해줘
- 부르미 수와 라이더 수에 대한 시간복잡도도 계산해
- 코드를 작성하라는게 아니야. 로직을 설명만 해
- 드리미는 매칭 중에 움직일 수도 있으니, 최소 몇분 주기로는 드리미의 위치를 갱신해야해 (단, 주문의 픽업 위치는 한 주문상태에서 고정이야)
- 내용 중에 빠진게 있어보이면 알려줘
1. 예를 들어 부르미가 "가방"을 주문으로 등록하면서 부르미가 매칭 대기 상태로 진입하면, 이미 매칭 대기 중인 드리미 중에서 배달 가능 범위에 있는 드리미 목록을 골라
2. 그 드리미 중에서, 드리미의 별점, 픽업까지의 거리, 매칭 실패한 횟수, 거절 횟수 등 여러가지 변수를 고려해서 어떤 공식을 통해 우선순위 값이 결정돼
3. 그 우선순위 값이 높은 드리미들부터 우선적으로 알림을 보낼거야.
4. 만약에 100명의 드리미가 조건을 만족한다면, 100명에게 전부 한번에 알림을 보내는게 아니라 20명씩 차례로 보내는 듯이 우선순위가 높은 사람들을 묶어서 나눠서 알림을 보낼거야
5. 어떤 드리미가 매칭을 수락하면, 매칭 알림은 왔으나 수락하지 못한 드리미에게 "이미 수락된 매칭"으로 알림이 가야해 (이런 경우 프론트엔드에서 매칭 팝업을 꺼줘야하기 때문에 필요해)
6. 드리미가 매칭 수락하면, 해당 주문 부르미에게 알림이 가. 여기서 부르미가 드리미의 정보를 읽고 동의하면 이제 실제 배달상태가 시작돼.
- STOMP 연결 및 개인 구독 채널 구현
- WebSocket/STOMP 의존성 추가
- STOMP 엔드포인트와 메시지 브로커 설정
- 사용자를 식별할 Principal 연결
- 개인 구독 destination 정의
- 서버에서 특정 사용자에게 메시지 전송
- React에서 연결·구독
- 연결 및 개인 메시지 테스트
- 드리미 매칭 대기 등록·취소 및 인메모리 상태 관리
- 거리 기반 후보 필터링
- 우선순위 점수 계산
- 20명 단위 개인 알림 발송
- 수락 동시성 처리
- 부르미 확인 요청
간과했던 점
- 배달 시스템과 퀵서비스의 라이더 매칭 속도
- 배달은 조리시간이 있기 때문에 매칭 시간을 크게 고려안해도 됨 ⇒ 순차적으로 팝업을 띄우면서 시간을 벌 수 있음
- 하지만 퀵서비스는 택시처럼 빠르게 매칭이 필요함
- 예시) 카카오T 블루의 경우 1:1 강제 매칭
- 픽업 위치와 드리미 사이의 거리를 통해 정렬을 한다? → 그러면 주문건이 1000건이면 그 1000개의 기준에 대한 모든 라이더들의 정렬이 필요함
- 셀 기반 공간 인덱싱을 통해 매칭 후보를 축소한다.
- 대기 진입, 거절, 취소, 타임아웃, 위치 변경, 검색 반경 승격 등의 이벤트가 발생하면 해당 사용자를 기준으로 재매칭한다.
이벤트 발생
→ 재매칭 요청 큐
→ 매칭 요청
→ 셀 기반 후보 조회
→ 실제 거리 계산 (특정 기준점을 벗어나면 재갱신요청)
→ 후보 선정
→ 상태 원자적 선점
→ 매칭 제안
event단위의 큐를 사용.
Map<GridCell, Set<UUID>> dreamisByCell;
Map<GridCell, Set<UUID>> boormisByCell;가능한 이벤트 서버 매칭 / 사람 매칭
-
재매칭 요청 삭제 후 다시 넣는다 (논리적으로만)
-
드리미의 거절
-
부르미의 거절
-
드리미의 거리 갱신 요청
-
대기 드리미 추가
-
대기 드리미 삭제
- 드리미의 삭제 요청과 동시에 UUID에 해당하는 드리미의 상태 변경 후 event fire.
- 부르미가 해당 드리미와 매칭이 된다면 상태를 보고 pass
- 훗날 삭제 event 발생시 셀에서 제거
-
부르미의 주문 추가
-
부르미의 주문 삭제
-
탐색 범위 추가
-
동혁님
- 상태 변경은 즉시 처리하고, 진짜 물리 action(실제 맵에서 삭제 추가)는 큐에 넣어서 처리한다
- 원소 추가,삭제를 할 때는 잠금 필요
-
이벤트 순서 역전 문제 → Entering 요청 시 최소 상태를 먼저 등록하여 해결 가능
ENTER 요청 → ENTERING 상태 등록 → 셀 등록 이벤트 큐에 추가 → 응답 이벤트 pop → 현재 상태 확인 → ENTERING이면 셀에 삽입 → ENTERING → WAITING 변경 → 재매칭 요청 -
사용자 입장에서는
- 드리미가 먼저 수락하고
- 그 다음에 부르미가 드리미 평 보고 수락
- 쌍방 처리 이지만, 실제 메모리에서는
- 드리미와 부르미 둘다 매칭(Proposal)으로 처리하고 대기 맵에서 뺴버림
- 그리고 둘중 한명이라도 거절하면 다시 마치 새로운 주문처럼 추가한다
용어
-
MATCHING → PROPOSED → MATCHED
- 사실상 PROPOSED이랑 MATCHED 둘다 DELETED상태라고 보면 됨
- 역방향 진행 X
상태
-
ENTERING 사용자가 추가 넣으면 칼같이 이걸로 바뀜.
-
MATCHING
-
PROPOSED
-
CANCELLED
-
시연영상 준비. 발표 형식을 대비 + 10분. (면접 없다)
수락()
- 다른 공간에 들어감
추가함수()
- 그냥 일단 넣어. WAITING으로 (이거는 순회중에 만나도 무시)
- 이벤트 큐에 action을 넣어 (WATING에서 MATCHING은 Map에 Lock을 걸고 작업)
삭제함수()
- 상태를 DELETED로 바꿔
- 이벤트 큐에 delete action을 넣어
- 배민 라이더 웹 소켓 로직이 복잡하다!
- 배민은 초기에 웹소켓 사용하다가 재연결 문제로 포기함
유튜브 페이스북: http 폴링. heart beat + 위치가 순간이동?
- 이걸 주의깊게 다뤄도 좋다.
- 생각보다 되게 어렵다. 우리가 좀 더 잘해보자! 숏폴링 롱폴링 SSE WebSocket.
기획의 문제로 기술의 문제를 커버할 수 있다.
한 부르미가 여려개의 주문을 할 수 있다.
정리
자료구조
public record MatchOffer(
UUID offerId,
UUID orderId,
UUID dreamiId,
MatchOfferStatus status,
Instant expiresAt
) {
}
public enum MatchOfferStatus {
OFFERED, // 드리미에게 제안이 전달되어 응답 대기 중
PENDING_BOORMI_CONFIRMATION, // 해당 드리미가 수락하여 부르미의 승낙을 대기중.
MATCHED, // 해당 드리미가 수락했고 부르미도 수락해 매칭 후보로 확정됨
BOORMI_REJECTED, // 해당 부르미가 명시적으로 거절함
DREAMI_REJECTED, // 해당 드리미가 명시적으로 거절함
BOORMI_EXPIRED, // 제한 시간 내 부르미가 응답하지 않아 만료됨
DREAMI_EXPIRED, // 제한 시간 내 드리미가 응답하지 않아 만료됨
WITHDRAWN // 다른 드리미가 먼저 수락했거나 서버가 제안을 회수함
}
public record WaitingDreami( // 기다리고 있는 드리미 (콜 대기중인 드리미)
UUID dreamiId,
GeoPoint location,
WaitingDreamiStatus status,
Instant updatedAt
) {
}
public enum WaitingDreamiStatus {
MATCHING, // 지금 매칭 중
PROPOSED // 지금 Match Offer 방 안에 들어감
}
Map<OfferUUID, MatchOffer> offersById;
Map<DreamiUUID, Set<OfferUUID>> offerIdsByDreamiId;
Map<OrderUUID, Set<OfferUUID>> offerIdsByOrderId;
Map<OfferUUID, List<MatchOffer>> offerMap;
HashMap<UUID, WaitingDreami> dreamiMap;
Queue<액션> q;
void 액션_수행() {
switch(q.pop()){
case "드리미_등록" -> 드리미_등록();
case "드리미_삭제" -> 드리미_삭제();
...
}
}
void alarmBySocket() {
// 소켓을 통해 알림보내기
// 아직 코드 구현X
}
void 드리미_등록() {
dreamiMap.insert(상태=MATCHING);
}
void 드리미_삭제(UUID dreamiId) {
dreamiMap.remove(dreamiId);
}
// 액션 하나 처리
void 매칭_시작(주문건) {
UUID offerUUID = 새로운_UUID_할당(); // 제안UUID
List<WaitingDreami> top3List = dreamiMap.stream().filter(MATCHING상태인_드리미만).sorted(거리순이든뭐든_정렬기준은아직미결정).limit(최대3명).toList();
// 상황 : 상위 3명 알림 보낼 드리미 완성
List<MatchOffer> matchOfferList = ~~;
// 드리미 3명에게 status를 변경 요청
for (WaitingDreami dreami : top3List) {
UUID dreamiId = dreami.dreamiId;
dreamiMap.get(dreamiId).status = WaitingDreamiStatus.PROPOSED; // 해당 드리미의 status를 변경
}
offerMap.insert(제안UUID, matchOffeList); // 방안에상위3명넣기 (사실상 방 만들기)
alarmBySocket();
}
// 팝업에서 수락을 눌렀다는 가정
void 드리미의_수락(WaitingDreami dreami, UUID offerId) {
// 이미 자신 matchOffer상태가 WITHDRAWN이면? -> 실패메시지
MatchOffer matchOffer = offersById.get(offerId);
if (matchOffer.status == MatchOfferStatus.WITHDRAWN) {
alarmBySocket("이미 다른 드리미가 수락한 주문입니다.");
return;
}
// TODO : 추후에 여기서 다른 matchOffer.status에 대한 분기도 작성 필요
// 수락한 사람의 상태를 PENDING_BOORMI_CONFIRMATION로 변경
// 나머지 매칭오퍼 상태를 WITHDRAW로 변경
for (MatchOffer offer : offerMap.get(offerId)) {
// 수락한사람은 PENDING_BOORMI_CONFIRMATION
// 나머지 사람은 WITHDRAWN
if (offer.dreamiId == dreami.dreamiId) {
offer.status = MatchOfferStatus.PENDING_BOORMI_CONFIRMATION;
// 드리미의 status는 PROPOSED 유지
assert dreami.status == PROPOSED;
alarmBySocket("부르미한테_드리미정보_팝업넘기기");
} else {
offer.status = MatchOfferStatus.WITHDRAWN;
// 선착순에서 패배한 드리미를 다시 매칭 수락가능한 상태로 변경
WatingDreami otherDreami = dreamiMap.get(offer.dreamiId);
otherDreami.status = WaitingDreamiStatus.MATCHING;
alarmBySocket("팝업꺼지게(선착순패배)");
}
}
}
// 드리미가 거절하면, DREAMI_REJECTED로 변경 및 다시 대기상태로
void 드리미의_거절(WaitingDreami dreami, UUID offerId) {
dreami.status = WaitingDreamiStatus.MATCHING;
MatchOffer offer = offersById.get(offerId);
offer.status = DREAMI_REJECTED;
alarmBySocket("거절했으니 팝업끄면됨");
}
void 부르미의_수락(UUID offerId) {
MatchOffer matchOffer = findMatchOfferByOfferId();
assert matchOffer.status == PENDING_BOORMI_CONFIRMATION;
matchOffer.status = MATCHED; // 부르미까지 수락 완료
실제_배달_로직으로_진행();
}
void 부르미의_거절(UUID offerId) {
MatchOffer matchOffer = findMatchOfferByOfferId();
assert matchOffer.status == PENDING_BOORMI_CONFIRMATION;
matchOffer.status = BOORMI_REJECTED;
// 거절당한 드리미의 상태를 배달가능 상태로 변경
WaitingDreami waitingDreami = findWaitingDreamiByDreamiId(matchOffer.dreamiId);
alarmBySocket("거절당한_드리미에게_부르미가_거절했다고_알려주기");
waitingDreami.status = WaitingDreamiStatus.MATCHING;
}
// 이건 액션에 의해 실행 되어야함
void 드리미가_시간내에_수락안누름() {
// 해당 match가 OFFERED 상태가 아니라면 다른 로직에 의해서 처리가 된거임
if (matchOffer.status != OFFERED) {
return;
}
matchOffer.status = DREAMI_EXPIRED;
dreami.status = MATCHING;
}
void 부르미가_시간내에_수락안누름() {
// 드리미가 다시 배달이 가능하게 바꿔야함
matchOffer.status = BOORMI_EXPIRED;
dreami.status = MATCHING;
}
void 드리미_거리갱신_요청() {}- 인덱스용 맵은 무엇?
- 방 지우는건 나중에
아래의 모든 내용은 MVP단계에서는 ActionQueue 방식으로 Serial하게 처리한다. 이후 점차 병렬화 하는 것을 목표로 한다.
-
한 주문은 여러 명의 드리미에게 알림을 보낼 수 있다.
- 이 때 드리미는 MATCHING 상태 (알림도 오지 않은 상태)이고 상한을 3명 정도로 잡아서 보낸다.
-
드리미 중 가장 빠르게 잡은 사람이 주문을 받게 된다.
- 이 때 드리미, 부르미는:
- 모든 MatchOffer 상태 수정 (PENDING_BOORMI_CONFIRMATION, MATCHING)
- 모든 DreamiWaiting 상태 수정 (PROPOSED, MATCHING)
- 잡지 못한 Dreami들에게는 알림을 통해 팝업 지우기
- 동시에 요청을 보냈을 경우 실패 메시지 띄우기.
- PENDING_BOORMI_CONFIRMATION에서 부르미가 수락 할 경우 [종료조건]
- MatchOffer 상태 수정 (MATCHED)
- DreamiWaiting 삭제
- 픽업 진행.
- 이 때 드리미, 부르미는:
-
거절 또는 timeout이 생길 경우
- 드리미의 거절:
- MatchOffer 수정 (DREAMI_REJECTED)
- DreamiWaiting 수정 (MATCHING)
- Offer의 거절 이력으로 + 쿨타임.
- 드리미의 타임아웃:
- 주문 상태 수정 (DREAMI_EXPIRED)
- 드리미 상태 수정 (MATCHING)
- Offer의 timeout 이력으로 + 쿨타임. (optional?)
여기까지는 PENDING_BOORMI_CONFIRMATION도 유사한 상황이다.
- 모든 드리미의 거절:
- 주문 추가 (Offer에 거절 되지 않은 다른 드리미들을 불러오자)
PENDING_BOORMI_CONFIRMATION 상태에서도 동일하다.
- 드리미의 거절:
-
드리미가 수락 한 경우
- 주문
-
타임아웃 액션의 경우
- 드리미가 수락 할 차례:
- 미리 수락 한 경우 (MatchOffer = PENDING_BOORMI_CONFIRMATION) 무시한다.
- 수락하지 않은 경우: 3-b
≠ 으로 찾는게 더 정확한 것 같음.
- 부르미가 수락 할 차례
- 미리 수락 한 경우 (MatchOffer = MATCHED) 무시한다.
- 수락하지 않은 경우
- 주문 상태 수정 (BOORMI_EXPIRED)
- 드리미 상태 수정 (MATCHING)
- 드리미가 수락 할 차례:
-
Offer의 경우 거절 이력을 저장해야 한다.
- 1분 동안 같은 주문의 알림을 받지 않도록.
-
셀과 관련 된 로직은 나중에 구현한다.
public interface DreamiCandidateFinder { List<DreamiCandidate> findCandidates( PickupLocation pickupLocation, int limit ); }
인터페이스로 구현하면 간단하다.
-
방법1. Action Queue를 Time 기반 우선순위 큐로 만들고 보통 이벤트는 실행 한 시간, Timeout이 필요한 것들은 Current + Timeout을 해서 큐에 넣는다.
- 단점 일반 액션과 예약 액션이 한 큐에 섞이고, 더 빠른 즉시 액션이 새로 들어왔을 때 Worker를 깨우는 로직까지 필요하다
-
DelayQueue
BlockingQueue<MatchingAction> actionQueue;
위의 메인 큐는 즉시 실행 할 액션만 담을 수 있고
DelayQueue / Scheduler와 같은 별도의 시간 관리 장치만 타임아웃을 가진다.-
예시 코드
scheduler.schedule( () -> actionQueue.offer( new OfferTimeoutAction(offerId) ), 15, TimeUnit.SECONDS );
-
- JPA Entity를 적을 때
@Columnannotation 명시하기. - 도메인 명을 할 때 Notion에 있는 이름 그대로.
다른쪽에서 가져올 게 있으면 새로 선언하지 않고 가져와서 사용을 한다. (Entity 중복 사용 지양)
석희 - user 도메인 끝내기, 프론트 UI 마무리 짓기 동혁, 현성 - 매칭 알고리즘 완성 후 코드 구현 및 API 작업 분배 현서 - 회원가입 할 때 드리미 인증 사진 받아오기. 이후 드리미 도메인 구현
- Presigned Domain? → 크게 어려운건 아님.
이게 성능이 좋긴 한데 보안상으로…
조금 더 짧은 Presigned Domain으로 보완 가능.
- API 명세서 진행 사항 업데이트 하기.
Team 3 · 석희 · 현성 · 동혁 · 현서
Softeer Bootcamp 8th · WEB Team 3 · 이 위키는 팀 노션에서 동기화됩니다 📄
🏠 시작하기
🤝 협업
📋 기획안
🧠 기술 결정 기록 (ADR)
- 세션 vs 토큰
- 세션 및 다중 탭 처리 정책
- 실시간 배달 상태 전달에 SSE를 선택한 이유
- SSE 연결은 왜 1개였다가 5개가 되고, 다시 1개가 되었을까
- 드리미 GPS 끊김 감지 정책
- Redis 기반 로그인 대기열 도입기
- 카카오 API 응답 Redis 캐시 설계
- 활동 내역 조회를 커서 기반 페이지네이션으로 진행
- 알림 - WebPush 도입 이유와 SSE의 한계
- S3 Presigned URL 도입 결정
- 포인트, 머니 시스템 설계
🕸️매칭 시스템
- 매칭 시스템 설계
- Command와 Producer Consumer를 조합한 single writer 구조의 매칭 엔진
- 제안은 누구에게 해야 할까
- 쉼, 부름의 부르미‐드리미 매칭은 어떻게 동작할까
- 배정 정책에 Greedy 배정을 선택한 이유
- 매칭 시스템 구현
🧪 부하 테스트
- 매칭 부하테스트
- 매칭 부하테스트 보고서 (8.12)
- 매칭 부하테스트 보고서 (8.13)
- 매칭 부하테스트 보고서 (8.13 · 외부 API 트랜잭션)
- 매칭 부하테스트 보고서 (8.18 · 배포환경 720명)
🔧 트러블슈팅
- UploadSession - 옛 키 재사용 공격 방어 설계
- 8/6 · EC2 SSH 접속 불가
- 8/7 · 매칭 확정 후 Delivery가 생성되지 않는 고아 Order 문제
- 8/8 · 영속성 컨텍스트 detach로 dirty checking 미반영 문제 (@Modifying(clearAutomatically = true))
- 매칭 확정 후 Delivery가 생성되지 않는 고아 Order 문제
- 매칭이 성공하지 않았음에도 Orders 테이블에 PENDING으로 상태 변경하는 문제
- 부르미 확인 타임아웃 시 주문 DB·매칭 메모리 불일치
- 주변 콜 조회 시 반복되던 주문 조회 개선
- 드리미 오프라인 조회에 필요한 복합 인덱스 추가
- 매칭 단계 취소시 포인트 이중 환불 문제
- 로컬 DevStorage 사용시 로그인 상태에서도 401 UNAUTHORIZED 뜨던 문제
🗓️ 데일리 스크럼