개발자 블로그

[coFi 부트캠프 개발자 커뮤니티 웹사이트] 4. 좋아요 및 조회수 동시성 문제 본문

프로젝트/coFi 개발자 커뮤니티

[coFi 부트캠프 개발자 커뮤니티 웹사이트] 4. 좋아요 및 조회수 동시성 문제

hayongwoon 2022. 5. 13. 13:27

동시성, Concurrency란?

DB에서의 동시성에 대해, DB에 다수의 사용자가 동시에 접근하는 상황에서 동시성은 transaction이 순차적으로 실행되는 것이 아니라, 순서에 상관없이 동시에 실행되는 것을 의미한다.

 

예를 들어보자.

"비행기 티켓팅을 한다고 생각해 봅시다.
비행기에 자리가 하나 남은 상황.

A와 B는 같은 비행기를 예매하려고 합니다.
A, B 둘 다 비행기의 잔여좌석을 확인하기 DB를 확인하게 됩니다.
이때 동시에 접근하여 한자리 남은 것을 확인하고 둘 다 예매에 성공한다면 어떤 일이 일어날까요?

비행기 자리 하나에 둘 모두 예매가 되어버리는 상황이 발생합니다. "

 

위와 같은 이유로 동시성을 고려하여 관리해야한다.

개발자가 동시성을 어떻게 관리해야 하는지 알아보기 전에 RDBMS는 어떻게 처리하는지 transaction의 격리 수준을 알아야하는데, transaction의 격리 수준이란, 여러 transaction이 동시에 처리될 때, 서로 얼마나 독립성을 지키는지를 나타내는 것으로 아래 4가지가 있다. 

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

위 4가지의 격리 수준을 조정함으로써 동시성을 해결하는 방법이 있고, select for update로 해결하는 방법, redis를 활용하는 방법이 있다고 한다. 이 부분에 대해서는 아래 블로그를 참고하여 따로 정리를 해야겠다. 

참고 : https://medium.com/deliverytechkorea/db-concurrency-%EC%96%B4%EB%94%94%EA%B9%8C%EC%A7%80-%EC%95%8C%EA%B3%A0-%EC%9E%88%EB%8B%88-559bfc4f59ee

 

좋아요, 조회수 count 업데이트의 동시성 문제

동시성 문제에 대해 찾아보다 DB에서의 동시성 문제에 대해서까지 글을 작성을 해봤는데, 좋아요 그리고 조회수 카운트를 업데이트 하는 작업에서는 쿼리 표현식을 개선함으로써 동시성 문제를 해결했다. 

 

Django의 F()객체를 활용한 리펙터링

F() 객체는 모델 필드의 값을 나타냅니다. 데이터베이스에서 파이썬 메모리로 데이터를 가져 오지 않고 모델 필드 값을 참조하고 사용하여 데이터베이스 작업을 수행 할 수 있습니다. 대신 장고는 F() 객체를 사용하여 데이터베이스 수준에서 필요한 작업을 설명하는 SQL 표현식을 생성합니다.

 

아래 예시에서 F() 객체의 사용법을 알 수 있다.

article = Article.objects.filter(pk=article_id).get()
ArticleLikes.objects.create(user_id=user_id, article_id=article_id)
article.like_cnt += 1
article.save()

 위 파이썬 구문은 article.like_cnt_filed의 값을 데이터베이스에서 메모리로 가져와 파이썬 연산자를 사용하여 조작 한 다음 데이터베이스에 다시 저장했습니다. 아래는 F() 객체를 사용하여 위와 동일한 결과를 생성하는 구문이다.

from django.db.models import F


User.objects.filter(id=user_id).get()
Article.objects.filter(id=article_id).get()

like = ArticleLikes.objects.create(user_id=user_id, article_id=article_id)
Article.objects.filter(id=article_id).update(like_cnt=F("like_cnt") + 1)

F()를 사용하면 race condition을 피할수 있다

F()의 또 다른 유용한 이점은 파이썬이 아닌 데이터베이스가 필드 값을 업데이트하면 race condition을 피할 수 있다.

예를 들어, 첫 번째 예시(F()를 사용하지 않은 예시)를 두개의 파이썬 thread를 사용해 실행하면 A라는 thread가 값을 받아 파이썬 메모리에 저장할 때 B라는 thread는 값을 추출, 증가, 저장할 수 있습니다. A라는 thread는 값을 증가, 저장하게 되면 B 라는 thread가 작업했던 내용이 손실된다. (A는 B의 작업 이전에 값을 가져왔기 때문..)

F()를 사용하면 위 문제를 해결할 수 있다. 메모리에 가져온 값을 기반으로 작업하는 것이 아닌 save() 나 update()가 실행될 때, 데이터베이스의 필드 값을 기준으로 작업하기 때문이다.

 

**앞으로의 과제**

 여기서 다룬 내용들은 우선 어떻게 개선했는지를 중점적으로 적어봤다. Fexpression과 DBtransaction는 좀 더 자세하게는 기록해둘 필요가 있고, 이 부분에 대해서는 Django orm을 정리하면서 더 자세히 다뤄보겠다.