운영중인 앱에서 출석체크 이벤트를 도입하게 되었다.
출석체크 이벤트는 사용자들에게 매일 자정에 접속하여 출석체크를 하면 보상을 제공하는 간단한 메커니즘이다.
하지만...
예상했던 것 이상으로 수만 명의 사용자들로부터 동시에 들어오는 출석체크 요청의 폭풍에 직면했다.
동시성 문제
수만 명의 사용자들로부터 동시에 들어오는 요청을 처리하는 것은 결코 간단한 일이 아니었다.
서버의 자원은 한정되어 있고, 동시에 너무 많은 요청이 처리될 경우
데이터베이스 충돌, 성능 저하, 심지어는 서비스 다운에 이르기도 한다.
이는 단순히 기술적인 도전을 넘어, 우리 서비스의 안정성과 사용자의 신뢰성에 대한 문제였다.
CloudWatch를 확인해보면 출석체크가 시작되는 밤 12시 (UTC 15:00)에
API 요청이 수만 건으로 확 치솟는 게 보일 것이다.
동시성 문제를 해결하기 위해선 먼저 문제의 본질을 이해해야 했다.
동시에 들어오는 수많은 요청이 데이터베이스에 동일한 쓰기 작업을 요구할 때,
이는 데이터의 무결성을 해치고, 충돌이나 데이터 손실로 이어질 수 있다.
출석체크 이벤트에 동시에 참여하려는 수만 명의 사용자들이 이 문제를 발생시키기엔 충분했다.
추가로 우리 시스템이 서버가 4대로 분리되어 있다는 특성도 큰 영향을 미쳤다.
서버 4대가 분리되어 있다는 것은, 각 서버가 동시에 독립적으로 같은 데이터에 접근하려 할 때
데이터 일관성을 유지하는 것이 더욱 어렵다는 것을 의미한다.
이는 각 서버 간에 상태를 동기화하는 복잡성을 증가시키며,
단순히 한 대의 서버에서만 동시성을 관리하는 것보다 훨씬 더 많은 고려 사항을 필요로 한다.
이제 이 문제점들을 어떻게 해결했는지 알아보자.
Pessimistic Locking
동시성 문제에 대응하기 위해 먼저 TypeORM의 Pessimistic Locking(비관적 잠금 기능)을 활용하기로 했다.
Pessimistic Locking은 특정 데이터에 대한 변경이나 조회를 시도할 때,
해당 데이터를 잠가 다른 트랜잭션의 접근을 차단하는 방법입니다.
이는 데이터의 일관성을 유지하고, 동시성 문제에서 발생할 수 있는 데이터 무결성 위반을 방지하는 데 큰 도움이 된다.
예를 들어, 두 사용자가 동시에 같은 계정의 잔액을 수정한다고 해보자.
Pessimistic Lock을 사용하면, 첫 번째 사용자가 해당 데이터에 접근하여 잠금을 걸면,
두 번째 사용자는 첫 번째 사용자의 작업이 완료되고 잠금이 해제될 때까지 기다려야 한다.
이렇게 하면 데이터의 동시 수정으로 인한 충돌이나 불일치를 방지할 수 있다.
이런식으로 TypeORM에서 Pessimistic Lock을 사용할 수 있다.
추가로 Optimistic Locking(낙관적 잠금 기능)도 존재 하지만 Pessimistic Lock을 선택한 이유는
포인트 지급과 같은 중요한 작업의 데이터 일관성과 무결성을 최우선으로 고려했기 때문이다.
Optimistic Locking으로는 충돌 해결 과정에서 발생할 수 있는 데이터 일관성 문제를 효과적으로 관리하기 어렵다.
Unique 제약 조건
유니크 키는 특정 컬럼(또는 컬럼들의 조합)에 대해 각 행의 값이 전체 테이블에서 유일함을 보장하는 제약 조건이다.
이를 통해 사용자의 출석체크 데이터에서 같은 사용자가 같은 이벤트에 두 번 이상 참여하는 것을 방지할 수 있다.
이는 우발적인 중복 요청이나 네트워크 지연으로 인한 재시도 요청으로부터도 데이터의 무결성을 보호하는 역할을 했다.
ALTER TABLE user_attend_check
ADD UNIQUE UNIQ_user_attend_check_user (user_id, event_id, check_date);
이런식으로 MySQL DB에 Unique 제약 조건을 걸 수 있다.
해결 완료
Pessimistic Lock과 Unique 제약 조건을 활용한 접근 방식은 우리 서비스의 동시성 문제를 해결하는 데 큰 도움이 되었다.
특히, 서버 4대에서 동시에 처리되는 요청들 사이에서 데이터의 일관성과 정확성을 유지할 수 있게 되었다.
이로 인해 사용자 경험이 크게 개선되었고, 시스템의 안정성도 높아졌다.
이 모든 과정을 통해, 나는 단순히 동시성 문제를 해결하는 것을 넘어,
분산 시스템 환경에서도 데이터의 일관성과 무결성을 유지할 수 있는 방법을 찾아냈다.
앞으로도 이러한 경험을 바탕으로 더 나은 서비스와 유저 경험을 제공하도록 노력해야겠다!
'Web > TypeOrm' 카테고리의 다른 글
[TypeOrm] loadRelationCountAndMap() - 관계된 데이터의 갯수를 쉽게 세는 방법 (0) | 2023.11.05 |
---|---|
[TypeOrm] TypeORM으로 upsert(), save() 안될 시 해결방법 (2) | 2023.03.18 |
[TypeOrm] IN조건으로 배열 검색 "Typeorm find where in array" (0) | 2023.02.15 |
[TypeOrm] timezone: 'z' charset UTC시간 설정 (0) | 2023.01.28 |
[TypeOrm] 차이를 알고쓰자 save() , insert() , update() (0) | 2023.01.25 |