-
Endpoint
- URL:
/api/token
- Method:
POST
- 설명: 대기열을 위한 유저 토큰 발급 요청
- URL:
-
Request
-
Body:
항목 Type 설명 비고 userId Long 유저 ID
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공400 Bad Request
: 잘못된 요청500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 result String 결과 코드 (200 : 성공 / 그 외 : 실패) message String 결과 메시지 data Object 토큰 데이터 -
data 정보 파라미터
항목 Type 설명 비고 token String 대기열 토큰 queuePosition Integer 대기열 위치 expiresAt String 토큰 만료 시간 -
응답 예시
{ "result": "200", "message": "Success", "data": { "token": "randomUUID", "queuePosition": 1, "expiresAt": "2024-07-04T12:00:00" } }
-
-
Error
-
400 Bad Request: 필수 파라미터 누락 또는 잘못된 데이터 형식
-
응답 예시
{ "result": "400", "message": "Missing or invalid userId" }
-
-
500 Internal Server Error: 토큰 발급 중 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 없음
-
예약 가능 날짜 조회
-
Endpoint
- URL:
/api/{concertId}/available-dates
- Method:
GET
- 설명: 예약 가능한 날짜를 조회합니다.
- URL:
-
Request
-
Query Parameters:
항목 Type 설명 비고 token String 유저 토큰 concertId Long 콘서트Id
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공401 Unauthorized
: 인증 실패500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 concertOptions List<Object> 예약 가능 콘서트 옵션 리스트 -
concertOptions 정보 파라미터
항목 Type 설명 비고 concertOptionId Long 콘서트 옵션 ID concertDate String 콘서트 날짜 -
응답 예시
{ "concertOptions": [ { "concertOptionId": 1, "concertDate": "2024-07-04" }, { "concertOptionId": 2, "concertDate": "2024-07-05" } ] }
-
-
Error
-
401 Unauthorized: 유효하지 않은 토큰
-
응답 예시
{ "result": "401", "message": "Invalid or expired token" }
-
-
500 Internal Server Error: 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 유저 토큰 필요
-
Authorization Header:
Authorization: Bearer randomUUID
-
-
-
예약 가능 좌석 조회
-
Endpoint
- URL:
/api/{concertOptionId}/available-seats
- Method:
GET
- 설명: 특정 날짜에 예약 가능한 좌석을 조회합니다.
- URL:
-
Request
-
Query Parameters:
항목 Type 설명 비고 token String 유저 토큰 concertOptionId Long 콘서트 옵션 ID
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공401 Unauthorized
: 인증 실패500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 seats List<Object> 예약 가능 좌석 리스트 -
seats 정보 파라미터
항목 Type 설명 비고 seatId Long 좌석 ID seatNumber String 좌석 번호 status String 좌석 상태 -
응답 예시
{ "seats": [ { "seatId": 1, "seatNumber": "A1", "status": "열림" }, { "seatId": 2, "seatNumber": "A2", "status": "열림" } ] }
-
-
Error
-
401 Unauthorized: 유효하지 않은 토큰
-
응답 예시
{ "result": "401", "message": "Invalid or expired token" }
-
-
500 Internal Server Error: 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 유저 토큰 필요
-
Authorization Header:
Authorization: Bearer randomUUID
-
-
-
Endpoint
- URL:
/api/reserve
- Method:
POST
- 설명: 좌석 예약 요청
- URL:
-
Request
-
Body:
항목 Type 설명 비고 token String 유저 토큰 concertOptionId Long 콘서트 옵션 ID seatId Long 좌석 ID userId Long 유저 ID
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공401 Unauthorized
: 인증 실패400 Bad Request
: 잘못된 요청500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 result String 결과 코드 (200 : 성공 / 그 외 : 실패) message String 결과 메시지 data Object 예약 결과 데이터 -
data 정보 파라미터
항목 Type 설명 비고 reservationId Long 예약 ID -
응답 예시
{ "result": "200", "message": "Success", "data": { "reservationId": 123 } }
-
-
Error
-
401 Unauthorized: 유효하지 않은 토큰
-
응답 예시
{ "result": "401", "message": "Invalid or expired token" }
-
-
400 Bad Request: 필수 파라미터 누락 또는 잘못된 데이터 형식
-
응답 예시
{ "result": "400", "message": "Missing or invalid parameters" }
-
-
500 Internal Server Error: 서버 오류
- 응답 예시
{ "result": "500", "message": "Internal server error" }
-
Authorization: 유저 토큰 필요
-
Authorization Header:
Authorization: Bearer randomUUID
-
-
잔액 충전
-
Endpoint
- URL:
/api/balance/charge
- Method:
PATCH
- 설명: 유저의 잔액을 충전합니다.
- URL:
-
Request
-
Body:
항목 Type 설명 비고 userId Long 유저 ID amount Double 충전 금액
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공400 Bad Request
: 잘못된 요청500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 balance Double 현재 잔액 -
응답 예시
{ "balance": 5000.00 }
-
-
Error
-
400 Bad Request: 필수 파라미터 누락 또는 잘못된 데이터 형식
-
응답 예시
{ "result": "400", "message": "Missing or invalid parameters" }
-
-
500 Internal Server Error: 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 없음
-
-
잔액 조회
-
Endpoint
- URL:
/api/balance
- Method:
GET
- 설명: 유저의 현재 잔액을 조회합니다.
- URL:
-
Request
-
Query Parameters:
항목 Type 설명 비고 userId Long 유저 ID
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공400 Bad Request
: 잘못된 요청500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 balance Double 현재 잔액 -
응답 예시
{ "balance": 5000.00 }
-
-
Error
-
400 Bad Request: 필수 파라미터 누락 또는 잘못된 데이터 형식
-
응답 예시
{ "result": "400", "message": "Missing or invalid parameters" }
-
-
500 Internal Server Error: 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 없음
-
-
Endpoint
- URL:
/api/pay
- Method:
POST
- 설명: 결제 요청
- URL:
-
Request
-
Body:
항목 Type 설명 비고 token String 유저 토큰 reservationId Long 예약 ID amount Double 결제 금액
-
-
Response
-
HTTP Status Codes:
200 OK
: 성공401 Unauthorized
: 인증 실패400 Bad Request
: 잘못된 요청500 Internal Server Error
: 서버 오류
-
Body:
항목 Type 설명 비고 result String 결과 코드 (200 : 성공 / 그 외 : 실패) message String 결과 메시지 data Object 결제 결과 데이터 -
data 정보 파라미터
항목 Type 설명 비고 paymentId Long 결제 ID -
응답 예시
{ "result": "200", "message": "Success", "data": { "paymentId": 456 } }
-
-
Error
-
401 Unauthorized: 유효하지 않은 토큰
-
응답 예시
{ "result": "401", "message": "Invalid or expired token" }
-
-
400 Bad Request: 필수 파라미터 누락 또는 잘못된 데이터 형식
-
응답 예시
{ "result": "400", "message": "Missing or invalid parameters" }
-
-
500 Internal Server Error: 서버 오류
-
응답 예시
{ "result": "500", "message": "Internal server error" }
-
-
-
Authorization: 유저 토큰 필요
-
Authorization Header:
Authorization: Bearer randomUUID
-
-
- 서버: 로컬 머신
- Redis: Docker 컨테이너로 실행된 Redis 인스턴스
- Spring Boot: 버전 3.3.1
- Java: 버전 17
비관적 락 (Pessimistic Locking)
- 성능 결과:
- 락 획득 및 작업 완료 시간: 약 100ms - 200ms
- 데이터베이스 락을 사용하기 때문에 성능 저하가 발생할 수 있음
- 전체 성능: 느림
낙관적 락 (Optimistic Locking)
- 성능 결과:
- 락 충돌이 없을 때: 약 20ms - 50ms
- 락 충돌이 발생할 때: 약 50ms - 100ms (재시도 포함)
- 전체 성능: 보통
Redis 분산 락 (Distributed Lock)
- 성능 결과:
- 락 획득 및 작업 완료 시간: 약 25ms - 30ms
- 인메모리 데이터베이스인 Redis를 사용하여 빠른 성능 제공
- 전체 성능: 빠름
비관적 락 (Pessimistic Locking)
- 정합성: 매우 높음
- 데이터베이스 레벨에서 락을 사용하여 모든 트랜잭션이 순차적으로 실행됨
- 충돌이 발생하지 않음
낙관적 락 (Optimistic Locking)
- 정합성: 높음
- 데이터베이스에서 버전 필드를 사용하여 충돌을 감지하고, 충돌 시 재시도 로직으로 처리
- 일부 트랜잭션이 실패할 수 있으나, 원하는 결과를 보장
Redis 분산 락 (Distributed Lock)
- 정합성: 매우 높음
- 분산 환경에서도 일관성을 유지하며, 하나의 트랜잭션만 성공적으로 실행됨
- 락을 획득하지 못한 트랜잭션은 실패
비관적 락 (Pessimistic Locking)
- Deadlock 발생 여부: 발생 가능
- 여러 트랜잭션이 서로를 기다리면서 데드락 발생 가능성 존재
- 처리 방법:
- 타임아웃 설정 및 재시도 로직 추가
- 데드락 발생 시 트랜잭션 롤백 및 재시도
낙관적 락 (Optimistic Locking)
- Deadlock 발생 여부: 발생하지 않음
- 충돌이 발생할 경우 예외를 발생시키고, 재시도 로직을 통해 처리
- 처리 방법:
- 충돌 시 예외 처리 및 재시도 로직
Redis 분산 락 (Distributed Lock)
- Deadlock 발생 여부: 발생하지 않음
- 락 획득에 타임아웃을 설정하여 데드락 발생을 방지
- 처리 방법:
- 락 획득 실패 시 예외 처리 및 재시도 로직
비관적 락 (Pessimistic Locking)
- 사용 편의성: 낮음
- 데이터베이스 레벨에서 락을 관리해야 하므로 설정 및 유지보수가 복잡
- 데드락 방지 및 타임아웃 설정 필요
낙관적 락 (Optimistic Locking)
- 사용 편의성: 높음
- 간단한 설정으로 사용할 수 있으며, 충돌 시 재시도 로직만 추가하면 됨
- @Version 필드만 추가하면 구현 가능
Redis 분산 락 (Distributed Lock)
- 사용 편의성: 보통
- Redis 설정 및 관리 필요
- 락 획득 및 해제를 위한 로직이 추가로 필요하지만, 코드 구현은 간단
비교 항목 | 비관적 락 | 낙관적 락 | Redis 분산 락 |
---|---|---|---|
성능 | 느림 | 보통 | 빠름 |
정합성 | 매우 높음 | 높음 | 매우 높음 |
Deadlock 발생 여부 | 발생 가능 | 발생하지 않음 | 발생하지 않음 |
Deadlock 처리 방법 | 타임아웃 설정 및 재시도 로직 | 충돌 시 예외 처리 및 재시도 로직 | 타임아웃 설정 및 재시도 로직 |
사용 편의성 | 낮음 | 높음 | 보통 |
- Redis 분산 락은 특히 분산 시스템에서의 동시성 제어와 확장성 측면에서 매우 강력한 도구로, 데이터베이스의 비관적 락이나 낙관적 락보다 더 유연하고 성능이 뛰어날 수 있음. 사용 편의성 측면에서는 낙관적 락이 가장 간단하며, 비관적 락은 설정과 관리가 복잡하지만 일관성을 강하게 보장.
- 비관적 락은 데이터 충돌이 빈번한 환경에서 일관성을 보장할 수 있지만, 성능 저하와 Deadlock 발생 가능성 때문에 주의가 필요
- 낙관적 락은 데이터 충돌이 드문 환경에서 성능이 뛰어나며, Deadlock 발생 가능성이 낮다.
- Redis 분산 락은 성능과 확장성 측면에서 매우 우수하며, 분산 환경에서 락을 구현하는 데 적합.
- 포인트 충전 및 사용: 비관적 락을 사용하여 데이터 정합성을 보장하면서도 성능 저하가 발생할 수 있다.
- 나머지 트랜잭션: 낙관적 락을 사용하여 높은 성능을 유지하면서도 데이터 정합성을 보장할 수 있다.
- 전체 트랜잭션 성능: Redis 분산 락을 사용하여 빠른 성능과 높은 정합성을 유지할 수 있다.
비관적 락 테스트
- 포인트 충전 유즈케이스
-
비관적 락 선택 이유: 한 유저가 포인트를 여러번 충전할때 한번의 트랜잭션이 처리가 끝난후 처리되는게 맞다고 판단하여 비관적 락으로 처리
-
최종 사용자 포인트: 1000.0
-
총 실행 시간: 194ms
-
평균 실행 시간: 19.4ms
- 포인트 사용 유즈케이스
- 최종 사용자 포인트: 9000.0
- 총 실행 시간: 222ms
- 평균 실행 시간: 22.2ms
낙관적 락 테스트
- 좌석 예약 유즈 케이스
-
낙관적 락 선택 이유: 좌석에 대해서 한명만 성공하면 그 이후로 대기할 필요 없기 때문에 낙관적 락을 통해 뒤에 신청한 사용자는 에러를 띄워 주도록 처리
-
성공한 예약 수: 1
-
실패한 예약 수: 9
-
총 실행 시간: 329ms
-
평균 실행 시간: 32.9ms
- 결제 유즈케이스
-
낙관적 락 선택 이유: 하나의 예약에 대한 결제이면서 본인의 예약을 결제하기 때문에 충돌이 적을 것으로 판단 결제 성공 건에 대해서만 처리하고 나머지는 충돌이 발생하면 에러로 띄워 주게 처리
-
성공한 결제 수: 1
-
실패한 결제 수: 9
-
총 실행 시간: 609ms
평균 실행 시간: 60.9ms