좋아요 동시성 문제가 발생한다?

waterlyn·2023년 7월 5일
0
post-thumbnail

여행기를 기록할 수 있는 travelzip 서비스를 개발하면서 좋아요 기능을 담당해서 개발했다. (마블리 엄지척)

인스타그램의 좋아요 기능을 생각하며 처음 요청에는 좋아요 생성, 동일한 요청이 다시 들어올 경우 취소되는 방식으로 빠르게 구현했었다.

좋아요도 동시성 문제가 있대요!

개발을 빠르고 간단하게 잘 했다고 생각했는데, 팀원이 좋아요도 동시성 문제가 발생할 수 있다고 알려주어서 한번 테스트를 해봐야겠다 생각했고, 실제로 5번의 좋아요 요청이 동시에 이루어지면 어떤 결과가 나오는지 확인해봤다.

동일한 사용자가 동시에 5번의 요청을 보내면 동일한 좋아요 5개가 생성되는 결과가 나오고 말았다....

사실 한 명의 사용자가 동시에 5번의 좋아요 요청을 보내는 경우가 있을 가능성이 매우 희박할 것이고, 좋아요 라는 기능 자체가 고도의 정확성을 요구하는 작업이 아니기 때문에 동시성 문제를 무겁게 다루지 않아도 될 거라고 생각하긴 했다.

하지만 사용자가 일명 따-닥 요청을 보내게 되면 DB에 동일한 좋아요 데이터가 쌓이게 되고, 이후 삭제 요청이 들어왔을 때 어떤 데이터를 찾아서 돌려줘야 할 지 찾아오지 못해 문제가 발생하기 때문에 이를 해결해보기로 했다.

DB 락, 트랜잭션 고립 등으로 동시성은 해결할 수 있다.

동시성 문제를 해결할 수 있는 여러 방식을 알아보면서 DB 락, 트랜잭션 고립, 원자성 등 다양한 기술을 알게 되었다. 사실 이러한 방식을 거의 적용해보았고, 비관적 락을 사용해서 동시성을 해결하는 것까지 도달했었다.

좋아요라는 기능이 수행되기 위해서는 게시글과 멤버 엔티티가 필요했고, 그래서 이를 조회할 때 비관적 락을 걸어주었는데, 이는 사실 효율적인 방법은 아니라는 생각이 들었다.

이렇게 되면 좋아요 요청이 들어가는 순간 (물론 그 순간은 매우 짧겠지만) 해당 게시글, 해당 멤버에 대해 락이 걸리기 때문에 성능적으로 문제가 발생할 수 있을 것이라 생각했다. 다시 말해, 좋아요 요청이 들어가면 해당 게시글은 다른 트랜잭션이 접근할 수 없게 되고, 다른 사용자가 단순 조회하는 순간에도 대기하게 되는 일이 발생하는 상황이 일어날 수 있는 것이다.

그래서 과연 이렇게 동시성을 막는 것이 맞는 일일까? 라는 고민을 하기 시작했다.

하나의 URI, 하나의 HTTP 메소드로 생성과 삭제 책임을 가져야할까?

동시성 문제를 해결하기 위해 우선적으로 고민했던 부분은, 좋아요의 기능 흐름을 URI의 요청 순서에 맡기는 것이 과연 적절한 것인가? 였다.

현재는 PUT 메소드로 요청을 보내면 요청 흐름에 따라서 생성과 삭제를 수행하도록 했는데, 생성이면 생성, 삭제면 삭제에만 집중할 수 있도록 구분하고자 했다. 이렇게 구분해내면 이후에 따닥 요청시 중복 생성되는 것에만 집중해서 해결하면 되고, 사용자가 보낸 요청이 생성인지, 삭제인지를 고려할 필요가 없어지기 때문에 더욱 간결해질 수 있다고 생각했다.

그래서 이후 POST 요청일 경우 좋아요를 생성해주고, DELETE 요청일 경우 좋아요를 삭제해주는 방식으로 변경했다.
(게시물을 조회할 때 해당 게시물에 좋아요가 있는지 판단하기 때문에 요청 자체를 분리하는 것이 가능하다고 판단했다.)

이제 중복 생성을 막아보자 ⚠️

결국 이렇게 되니 좋아요 동시성 문제라고 하기보다 좋아요의 데이터 중복 생성 문제를 해결하는 것으로 포커스가 바뀌었다.

중복되는 데이터가 없어야 한다라는 것은 DB에 중복된 데이터가 존재하지 않도록 하는 것이고, 좋아요 테이블에 유니크 컬럼을 걸어주기로 했다.
좋아요 테이블에 포함되는 게시물 PK사용자 PK를 활용하여 복합 유니크 컬럼을 만들어주었고, DB에서 중복 생성 예외가 발생할 때 이를 어플리케이션에서 처리해주는 방식으로 변경하였다.

만약 중복 생성 예외가 발생했다면, 결국 사용자는 좋아요를 생성하기를 원하는 목적을 가지고 요청한 것일거고, 이미 DB에 잘 존재하고 있다면 생성이 잘 되어 좋아요 처리가 된 것이기 때문에 예외를 전달하여 알릴 필요도 없다고 생각했다. 그래서 중복 생성 요청이 들어와 예외가 발생할 경우 로그를 통해 경고를 남기는 정도로 처리하는 것으로 변경했다.

이렇게 하니 중복 요청이 들어와도 데이터가 중복해서 생성되지 않기 때문에 걱정했던 상황도 발생하지 않게 되고, 가장 리소스를 덜 쓰면서 잘 대처한 결과가 나왔다고 생각한다 ! 😆

동시성 문제를 제대로 처리해보고 싶었는데, 동시성 문제라고 생각했던 것이 실제로는 동시성 문제는 아니었고, 내가 생각했던 흐름과 전혀 다른 방법으로 해결할 수 있는 것도 경험하게 되어 좋았다 !!

profile
Hello there 🖤

0개의 댓글