[PEAUTY] 리팩토링 방향 정리 - 동시성 문제

Ureca.·2025년 1월 14일
post-thumbnail

PEAUTY

미용견 중개 서비스를 프로젝트로 진행하면서, 해당 건은 TODO로 만들고 이후에 리팩토링을 하면서 꼭 건드려봐야겠다는 생각을 했다.
프로젝트 3개를 하면서 회고를 작성하며 코드 정리를 해보지도 않았기 때문에 포트폴리오에 올릴 것이 없었기 때문에, 이를 시작으로 채워보자라는 마음이 있었다.

그래서 리팩토링으로 내가 우선적으로 해결해 볼 문제는 동시성 문제였다.
간단하게 설명을 하자면, 손님과 미용사를 중개하는 서비스로 손님의 자신의 반려견에 대한 미용을 완료하면 해당 미용사의 미용실에 대한 리뷰를 할 수 있는 것이다.
이 때, 반영되는 값으로 리뷰 평점과 리뷰 갯수가 있다. 이는 미용실을 조회했을 때 모든 유저들이 볼 수 있는 것이기 때문에 DB에 잘 들어오는 것이 중요하다고 볼 수 있다.
그런데, 이게 왜 지금 문제일까? 라고 생각을 한다면 다음과 같은 상황을 예시로 들 수 있겠다.


다음은 노션에 정리한 것을 그대로 옮긴 것으로, 나중에 다시 읽기 좋게 문체 등을 수정할 예정입니다.

  • 리뷰 동시성 문제 해결
    • 현재 문제 : reviewRatingreviewCount를 업데이트하는 로직에서의 문제가 있을 가능성이 매우 높다. Race Condition이 발생하게 된다.
    • 발생이 예상되는 문제
    • reviewCount = 10을 읽음.
    • A가 reviewCount를 11로 업데이트 후 저장.
    • B가 reviewCount를 11로 업데이트 후 저장.
    • 결과적으로, 하나의 리뷰가 무시된다.
    • 작성 API 뿐만이 아니라, 업데이트 및 삭제에서도 같은 상황이 발생할 것으로 예상.
  • 해결 방안으로 모색한 것들?
    1. Pessimistic Locking(비관적 락)
      1. Pessimistic Locking을 사용해 동시에 같은 workspace 데이터를 수정하지 못하도록 설정한다.
      2. 리뷰 갱신 전에 workspace 엔티티에 락을 걸어 동시 수정이 불가능한 상태가 되도록 한다.
      3. 단, 다른 명령대기 상태가 되므로 성능 저하가 있을 수 있다.
    2. Optimistic Locking(낙관적 락)
      1. 엔티티에 버전 필드를 만들고, 업데이트 시점을 관찰해 변경이 됐는지를 확인한다.
      2. 이 기법은 Hibernate에서 자동으로 업데이트 시 버전을 검증한다.
      3. 충돌이 발생하면 OptimisticLockException이 발생해 트랜잭션을 재시도 할 수 있다. 이에 대한 것은 더 많은 구글링이 필요.
    3. 원자적 연산 사용(Atomic)
      1. 갱신 로직을 데이터베이스 쿼리로 직접 수행해 동시성 문제를 방지한다.
    4. CQRS(Command Query Responsibility Segregation)
      1. 리뷰 통계를 실시간 계산하는 것이 아닌, 별도의 테이블에 리뷰 통계 데이터를 저장하거나 조회 시에 on-the-fly로 계산을 한다.
      2. 동시성 문제가 발생하지 않는다는 장점이 있으나, 조회 성능이 저하된다는 단점이 있다.
  • 동시성 문제를 해결할 때 반드시 고려해야 하는 데드락(DeadLock)
  1. 낙관적 락에서의 문제점
  2. 비관적 락에서의 문제점

주의 : 해당 논리 전개는 약간의 비약적 전개가 이뤄졌다. 그럼에도 이렇게 남겨둔 이유는, 후에 뭐가 잘못됐던 것인지를 다시 한 번 상기해보자는 이유이다.

리뷰에서의 동시성을 해결하기 위해 가장 적절한 기법은 무엇일까?

해당 서비스의 트래픽이 많을 것이라고 생각되지 않으며, 갱신 로직이 단순하다고 할 수 있다.

따라서 고려해볼 기법은 낙관적 락이라고 할 수 있다. 그 외 많은 사용자가 동시에 작성하고 수정할 가능성이 높을 때에는, 비관적 락을 고려해볼 것 같다.

낙관적 락 기법을 선택한 이유

기존에 낙관적 락을 사용해봐야겠다고 결정한 이유 : 트래픽이 적다는건 리뷰를 적을 유저풀이 적다는 이야기고, 그렇다는건 리뷰를 작성하고 수정하는 사람들 또한 적어서 겹치는 일이 많이 없다고 생각을 했다.

여기서 주요 논지

팀장 : 인과관계가 잘못된거 같은데. 트래픽이 많을 때 DB락을 사용하면 성능에 크게 문제가 되는건가?

DB락을 사용했기 때문에 트래픽이 많은 서비스일 시 성능이 문제가 되는걸텐데?

오히려 DB락을 사용하면 트래픽 서비스가 적은 우리 서비스에서 오히려 적합하지 않을까?

본인 :

뭔가 잘못됐다는 생각이 들었다.

그래서 내가 왜 이렇게 논리를 전개했는지 생각을 다시 해봤고 그 결과 다음과 같은 흐름이 되지 않았을까라는 생각이 든다.

이를 다시 정리해보면,

1.1 동시성 로직을 고민하는 근본적인 이유는 왜 하고 있을까 → 어떤 복수의 사용자가 미용실에 대한 리뷰를 동시에 작성했다. 이 때 동시성 문제가 생긴다.

1.2 그런데 이러한 일이 많이 일어날까? → 아니, 절대 그러지는 않을 것 같다. 미용실은 기본적으로 병렬적으로 한 번에 주문하고 하는 그러한 쇼핑몰 서비스와의 개념과는 다른 직렬적인 서비스를 제공한다. (쉽게 말하면, 시간에 따른 서비스 제공이기 때문에 순차적 서비스를 제공한다.)

1.3 이 의미는 손님들의 리뷰 작성 시간은 자유롭긴 해도, 어느 정도의 필터링이 된다는 것을 의미한다.

1.4 이 논리를 적용했을 때, 동시성 충돌은 매우 낮은 확률로 발생할 것이다.

2.1 트래픽은 어떨까?(여기서부터가 문제가 됐던 논리 전개이다.)

2.2 반려견 미용중개 서비스라는 것이 많은 트래픽이 발생하진 않을 것이다. 시장이 너무 한정적이다.

2.3 동시성 발생 가능성이 낮으면서 트래픽이 크게 문제가 되지 않기 때문에 낙관적 락을 사용하면 되겠다.(이상하게 합쳐졌다.)

다시 위로 돌아와서, 비관적 락을 사용해서 문제가 크게 발생하는 건 “트래픽이 많을 때이다.”라는 것이다.

그러니까, “동시성 발생이 많이 일어나지 않는다.”가 낙관적 락의 사용이유는 될 수 있다.

그러나, “트래픽이 적을 것이다.”가 낙관적 락의 사용이유는 될 수 없다.

아니, 없다기보다는 타당한 이유가 아니다.

그럼에도 불구하고, 팀원의 생각으로는

“낙관적 락을 사용하는 것이 좋겠다.” 이다.

이에 따라 내가 “왜지? 오히려 재시도 로직도 만들어야 하는 공수가 힘이 들 수 있는데 그럼에도 불구하고 낙관적 락을 들이미는 이유는 뭐지?”라고 생각했었고,

이렇게 질문을 했었다.

이 글을 보면서 정리했을 때, 다음과 같이 정리를 좀 할 수 있을 것 같았다.

사용 이유에 대해서 거의 정리가 됐다.


낙관적 락을 사용해야 하는 이유에 대해서는 우리 팀원 전체가 인지를 했다.

이제는 진짜 어떻게 낙관적 락을 적용해서 리팩토링을 마무리할지다.

profile
한 편의 주마등이 망작이 될 수는 없잖아.

0개의 댓글