프로젝트 경험기/MSA 경험기

[MSA 경험기] 모아 구독 (3) 조회수 구현 1 (by. 쿠키와 동시성)

yubi5050 2022. 11. 23. 16:40

지난 글

이전 글 에서는 페이지 네이션, 캐싱을 적용한 상품 리스트 조회를 구현하면서 고민했던 부분에 대해 적어보았었다.

https://yubi5050.tistory.com/220

 

[MSA 경험기] 모아 구독 (2) 페이지 네이션, 캐싱을 적용한 상품 조회

지난 글 이전 글 에서는 MSA 프로젝트를 시작한 계기와 프로젝트 기획 및 설계에 대해 작성했었다. https://yubi5050.tistory.com/218 [MSA 경험기] 모아 구독 (1) 프로젝트 기획 및 설계 MSA 프로젝트 시작 계

yubi5050.tistory.com

 

이번 글 에서는 '조회 서비스 - 구독 상품 상세 조회'의 조회수를 구현하면서 이슈가 되었던 부분과 해결 방법에 대해 이야기 해보려고 한다. (해당 글에서는 첫번째 이슈에 대해서만 설명)

 

조회수 Counting을 구현시 문제가 되었던 부분은 크게 두 가지 였다.

  • 동시에 여러 유저(n명)가 같은 구독 상품 글에 접근시 조회수가 n만큼 증가하지 않는 문제    
  • 짧은 시간내 반복적으로 동일 유저가 접근시 조회수가 계속 오르는 문제

 

동시 접근 이슈 - Django ORM의 값 조작 원리

Django의 ORM을 사용하면, 비즈니스 로직으로 DB의 값을 조작 할 수 있는 장점이 있다. 예로 아래 사진을 보면 현재 구독상품 DB의 views(조회수)값을 가져오고 +1 후 save() 로직을 거쳐 값을 손쉽게 수정 할 수 있다.

 

ORM으로 조회수(views) 값 조작하는 코드

 

위의 과정을 좀 더 자세히 풀면 아래와 같은 절차를 거친게 된다.

  • 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())

 

F 객체를 활용한 조회수(views) 값 DB 반영

 

동시 접근 이슈 - DB의 트랜잭션 Isolation Level

왜 DB에서 직접 진행하는 것만으로 해결되는가?

사실 Python 메모리로만 가져오지 않아 더 절차가 간소화 된 것 뿐인데, 왜 문제가 해결되는지 의문일 수 있다. 막말로 A스레드에서 DB에서 1개의 값을 수정하는 트랜잭션을 수행하는 동안, 더 짧은 사이에 다른 스레드에서도 해당 DB에 접근하여 READ한다면 같은 문제가 발생하지 않을까? 라고 생각 할 수 있다.

 

그 이유는 DB에는 트랜잭션들이 동시적으로 접근하는 것에 대해 관리를 해주는 Isolation Level이 존재하기 때문이다. 

격리 레벨과 관련해서는 일전에 아래 링크에서 정리하였었다.

https://yubi5050.tistory.com/135

 

[DB] Transaction 격리 수준 종류 (Isolation Level)

Transaction 격리 수준 이란? Transaction이 동시에 처리될 때 transaction끼리 얼마나 고립되어 있는지를 나타냄 RDBMS는 transaction의 격리 수준을 제어 가능하며 일반적인 DBMS는 READ COMMITTED나 REPEATABLE READ를

yubi5050.tistory.com

 

'모아 구독' 프로젝트에서는 READ COMMITTED라는 격리 레벨을 가지는 PostgreSQL DB를 사용하였다. 

 

READ COMMITTED 격리 레벨의 가장 큰 특징은 "트랜잭션의 변경 내용이 COMMIT 되어야만 다른 트랜잭션에서 조회 가능" 하다는 것

 

따라서 F 객체를 사용하여 다수의 스레드가 DB를 업데이트 하려고 시도해도, DB단에서 먼저 온 트랜잭션이 COMMIT 되어야지만, 다음 트랜잭션이 값을 조회할 수 있도록 제어해주기 때문에, 안정적으로 변경된 값을 얻어 정상적으로 조회수가 구현되는 것이라고 할 수 있다.