-
Notifications
You must be signed in to change notification settings - Fork 2
2차: Skip Locked 기반 락 병목 해결
김현준 edited this page Sep 3, 2024
·
2 revisions
![ERD](https://private-user-images.githubusercontent.com/102659136/363471748-c5da2a98-42a1-4049-8235-76649b10c087.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcxNzQ4LWM1ZGEyYTk4LTQyYTEtNDA0OS04MjM1LTc2NjQ5YjEwYzA4Ny5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0xYjFhNWM1N2NkNTFkNGMzZDQ1ZDMzMjA5YzYwYzE4YTIzYmE2ZGI4Y2UwNDk3ZTJlNzVjN2Y1NWJhOTA2ZjIyJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZcAEaUzPRmR5oPUAw8KUsPS5j-RN2FECPkULs01A4EY)
문제 상황의 ERD
- 하나의 축제(festival)은 여러 개의 티켓(ticket) 종류를 가짐 (OneToMany)
- 예를 들면 티켓도 혜택 종류에 따라 가격이 다른 티켓을 가질 수 있다는 상황을 가정
- 티켓(ticket)은 하나의 티켓 재고(ticket_stock)를 가짐 (OneToOne)
- 실질적인 티켓 재고는 remain_ticket_stock에서 int로 하나씩 차감하는 형태로 관리가 됨
- 하나의 컬럼으로 관리
- 선착순 로직 상 동시 예매가 일어날 것이 분명했고 이에 따라 낙관적 락보다는 비관적 락을 이용해 재고에 대해 Exclusive Lock을 걸어 동시성 문제를 해결했음
- 문제는 재고를 하나의 컬럼으로 관리 했기 때문에 아래의 그림과 같이 락이 걸리게 되면 그 락이 풀릴 때까지 모든 락이 기다리게 되었음
![Lock Situation](https://private-user-images.githubusercontent.com/102659136/363471757-cee64801-97c6-4d85-b9bd-f26d57030588.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcxNzU3LWNlZTY0ODAxLTk3YzYtNGQ4NS1iOWJkLWYyNmQ1NzAzMDU4OC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iNTYwYzIyYWUyMWI2NTk1Y2YyOTA3OGQxY2QyMDc5MTZhMTAxMjNiMGFjYTZkMWIyOThiYThiMzUzMWYzNWNjJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.4F73-r_JPfLUZYagGm2l7hc9j66EghPEaOW2uGOvjYM)
- 위의 문제로 락을 기다리는 커넥션이 늘고, 커넥션의 응답을 기다리는 쓰레드가 많아짐
- 커넥션 풀과 쓰레드 풀에서 병목 현상이 발생함
- 분산락 사용
- 데이터베이스 수준이 아닌 애플리케이션 수준에서 제어하는 방법
- 추가적인 인프라 필요, 단일 서버에서 필요 없는 네트워크 지연 overhead 발생
- DB 락 없이 자바 큐 사용해 배치 처리
- 자바 수준에서 제어 가능
- 순차적인 메시지 순서를 보장할 경우 추가 로직 필요, 추후 서버 수평 확장시 변경 가능성 높음
- MySQL SKIP LOCKED
- 데이터베이스 수준에서 제공되는 기능으로 구현이 상대적으로 간단함
- 동시성 처리 향상 및 락 대기 시간 감소
- MySQL 8.0 이상에서만 동작하는 기능으로 DB에 종속적임
따라서, 추가적인 인프라 도입 없이 서버의 DB 또한 MySQL이였기에 MySQL SKIP LOCKED을 해결 방안으로 채택
- 일반적인 경우
- 잠긴 행을 포함하는 요청 시 트랜잭션은 해당 행 잠금이 해제 될 때까지 기다림
- 행 잠김이 해제되면 그때 해당 행을 포함한 결과를 반환
- SKIP LOCKED 경우
- 잠긴 행을 포함하는 요청 시 트랜잭션은 행 잠금을 해제 할 때까지 기다리지 않고 건너뜀
- 잠긴 행을 제외한 결과 값을 반환
- 따라서 잠긴 행에 따라 요청 시 일관적이지 않은 결과를 보여줌
- 여러 세션이 동일한 대기열과 같은 테이블에 액세스할 때 잠금 경합을 피하기 위해 사용
따라서 아래와 같은 방식으로 동작
![SKIP LOCKED Principle](https://private-user-images.githubusercontent.com/102659136/363471772-7a315799-9d94-420f-b396-b0ee005677ab.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcxNzcyLTdhMzE1Nzk5LTlkOTQtNDIwZi1iMzk2LWIwZWUwMDU2NzdhYi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iYzE1YzY2MDc1MjkwMGNhZjg1NjUwZTllNGI2ZDFlNDQ5YmI1NWQzY2NhYjAwMTU0NjU1YzUwMzViYmNkNTVkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.fnsO5Y2Ou4Y23jM4YlM_8k4QYqRg_HSUpq-0GNKAWA4)
![Updated ERD](https://private-user-images.githubusercontent.com/102659136/363471783-175df4b0-49f0-4aaa-a985-31684213826a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcxNzgzLTE3NWRmNGIwLTQ5ZjAtNGFhYS1hOTg1LTMxNjg0MjEzODI2YS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT03ZDdkMTE3ODM1NDA1MDQ4OThmNzYxNGEwY2NlODg5ZTIzZTAyY2IwZDU0YmZlYzVmYzY3ODUwMDA3NzM0ZTcwJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZN4diVRBCgQf1swAUw5Cje69Ew955GdYCQIyYV8USe0)
변경된 ERD
- 하나의 축제(festival)은 여러 개의 티켓(ticket) 종류를 가짐 (OneToMany)
- 티켓(ticket)은 여러 개의 티켓 재고(ticket_stock)를 가짐 (OneToMany)
- 티켓 재고만큼 ticket_stock에서 레코드가 생성됨
- ticket_stock_member_id엔 티켓 재고를 점유한 member_id가 들어감
- 따라서 아래의 그림과 같이 티켓 재고를 얻는 행위에서 다른 유저가 점유한 레코드의 잠금 해제를 기다리지 않고 잠금이 걸리지 않은 1개의 행을 빠르게 얻어 옴
![New Locking Mechanism](https://private-user-images.githubusercontent.com/102659136/363873513-3fbd80fa-ed5a-4e87-a445-e31c540826c1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzODczNTEzLTNmYmQ4MGZhLWVkNWEtNGU4Ny1hNDQ1LWUzMWM1NDA4MjZjMS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iMjgxOWQzNGFmYjdlMzFlMzVlZTZhN2U0OTczMjcxNDMzY2I5NTZhZmZkMjg2YjA3MjdlNmMwMzYzNmU5NWY4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.73_I2PTVX_mvZfph3K_F2nwdrg1gABTpfZxxdxc7vvc)
- 결과적으로 잠금에 대한 경합이 일어나지 않기 때문에 쓰레드와 커넥션에 대한 병목이 감소할 것으로 보였음
- 3000, 5000명 기준
- 상세 페이지 조회
- 티켓 목록 조회
- 티켓 결제 가능 여부 조회
- 결제 정보 미리보기 페이지 조회
- 티켓 결제
![3000 Users Comparison](https://private-user-images.githubusercontent.com/102659136/363472537-d96f84be-45cb-415f-9d60-6f8acb3d1918.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcyNTM3LWQ5NmY4NGJlLTQ1Y2ItNDE1Zi05ZDYwLTZmOGFjYjNkMTkxOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0wODE3Y2JmZmQ1ODU2Yjg4NGQ2NTdhMTc4NTJmNzI2MzUwY2ZhNDUxNTRhOTE1YTIyZTViZmFmODY1NmEwNTdiJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.WGIMVHBUprSa7MU3CBlXYiw44FZMKuSpyoDJnueQ0FA)
![5000 Users Comparison](https://private-user-images.githubusercontent.com/102659136/363471840-206a4ae4-2c6a-4381-a295-6a4cbd7eb5c3.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyNjI3MzgsIm5iZiI6MTczOTI2MjQzOCwicGF0aCI6Ii8xMDI2NTkxMzYvMzYzNDcxODQwLTIwNmE0YWU0LTJjNmEtNDM4MS1hMjk1LTZhNGNiZDdlYjVjMy5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjExJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMVQwODI3MThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kZjI1YzkxMWE5ZmYwYmE3YWU5M2U0NTdhNmQ3OWE3YWY5YjUyODhiMmMxNjhmMDhhMmNhODBhMjY3MDk2ODUzJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.gCxVZFJdeBQ_d2lceaJXO2Q0vDwyPys2_yyn1NnkkUs)
https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html