지난 글
이전 글 에서는 페이지 네이션, 캐싱을 적용한 상품 리스트 조회를 구현하면서 고민했던 부분에 대해 적어보았었다.
https://yubi5050.tistory.com/220
이번 글 에서는 '조회 서비스 - 구독 상품 상세 조회'의 조회수를 구현하면서 이슈가 되었던 부분과 해결 방법에 대해 이야기 해보려고 한다. (해당 글에서는 첫번째 이슈에 대해서만 설명)
조회수 Counting을 구현시 문제가 되었던 부분은 크게 두 가지 였다.
- 동시에 여러 유저(n명)가 같은 구독 상품 글에 접근시 조회수가 n만큼 증가하지 않는 문제
- 짧은 시간내 반복적으로 동일 유저가 접근시 조회수가 계속 오르는 문제
동시 접근 이슈 - Django ORM의 값 조작 원리
Django의 ORM을 사용하면, 비즈니스 로직으로 DB의 값을 조작 할 수 있는 장점이 있다. 예로 아래 사진을 보면 현재 구독상품 DB의 views(조회수)값을 가져오고 +1 후 save() 로직을 거쳐 값을 손쉽게 수정 할 수 있다.
위의 과정을 좀 더 자세히 풀면 아래와 같은 절차를 거친게 된다.
- 1. DB의 값을 Python 메모리 변수 (detail_products라는 객체의 views 변수)로 가져옴
- 2. 파이썬 메모리 변수에 1을 더해줌
- 3. 변경된 메모리 변수를 DB에 저장
만약 다수의 사용자가 위 로직에 동시에 접근한다면 어떻게 될 까?
Python 메모리를 거치는 위 로직은 큰 문제점이 존재 하는데, 예로 사용자 A의 스레드가 DB에서 값을 읽어 Python 메모리 변수에 저장하고 해당 변수의 +1의 연산 과정을 거치는 동안, 더 늦게 방문한 B의 스레드는 DB에서, A가 읽은 값과 동일한 n의 값을 읽게 된다.
이 상황에서 A가 먼저 DB에 n+1의 값을 저장하더라도 B는 DB에 n+2가 아닌 A와 같은 n+1을 저장하게 되고, 결과적으로 2명의 사용자가 접근했음에도, 조회수는 1밖에 올라가지 않는 상황이 발생하는 것이다.
동시 접근 이슈 - 문제 해결 방법
Django에는 Python 메모리 변수로 가져오지 않고 바로 Query문으로 변환해 DB에서 직접 연산을 수행하여 반영하는 F 객체가 있다.
아래 코드의 과정을 좀 더 자세히 풀면
1. F 객체로 조회수(views)를 감싸면 Python 메모리 변수에서 연산을 진행하지 않고, 데이터베이스에서 연산 진행
2. 저장된 데이터 베이스의 값을 재호출함 (refresh_from_db())
동시 접근 이슈 - DB의 트랜잭션 Isolation Level
왜 DB에서 직접 진행하는 것만으로 해결되는가?
사실 Python 메모리로만 가져오지 않아 더 절차가 간소화 된 것 뿐인데, 왜 문제가 해결되는지 의문일 수 있다. 막말로 A스레드에서 DB에서 1개의 값을 수정하는 트랜잭션을 수행하는 동안, 더 짧은 사이에 다른 스레드에서도 해당 DB에 접근하여 READ한다면 같은 문제가 발생하지 않을까? 라고 생각 할 수 있다.
그 이유는 DB에는 트랜잭션들이 동시적으로 접근하는 것에 대해 관리를 해주는 Isolation Level이 존재하기 때문이다.
격리 레벨과 관련해서는 일전에 아래 링크에서 정리하였었다.
https://yubi5050.tistory.com/135
'모아 구독' 프로젝트에서는 READ COMMITTED라는 격리 레벨을 가지는 PostgreSQL DB를 사용하였다.
READ COMMITTED 격리 레벨의 가장 큰 특징은 "트랜잭션의 변경 내용이 COMMIT 되어야만 다른 트랜잭션에서 조회 가능" 하다는 것
따라서 F 객체를 사용하여 다수의 스레드가 DB를 업데이트 하려고 시도해도, DB단에서 먼저 온 트랜잭션이 COMMIT 되어야지만, 다음 트랜잭션이 값을 조회할 수 있도록 제어해주기 때문에, 안정적으로 변경된 값을 얻어 정상적으로 조회수가 구현되는 것이라고 할 수 있다.
'프로젝트 경험기 > MSA 경험기' 카테고리의 다른 글
[MSA 경험기] 모아 구독 (6) 검색 히스토리 서비스 (by. NoSQL, Singleton) (1) | 2022.11.27 |
---|---|
[MSA 경험기] 모아 구독 (5) Query Profiling을 통한 최적화 (0) | 2022.11.25 |
[MSA 경험기] 모아 구독 (4) 조회수 구현 2 (by. 쿠키와 동시성) (1) | 2022.11.23 |
[MSA 경험기] 모아 구독 (2) 페이지 네이션, 캐싱을 적용한 상품 조회 (2) | 2022.11.23 |
[MSA 경험기] 모아 구독 (1) 프로젝트 기획 및 설계 (0) | 2022.11.21 |