동시에 들어온 여러 요청 처리하기

dev_Hyun·2024년 4월 22일
0

새로운 이슈 발생 : 동시 요청 제어

Babpool 프로젝트의 고도화를 위해, 기획 또는 사용자 경험상 미비한 부분을 보강하기 위한 미팅을 진행했습니다. 그 중 밥 약속 요청 플로우에서 개선의 여지를 발견하였고 변경된 내용은 다음과 같습니다.

Sender(요청을 보내려는 사용자) A, B와 Receiver(요청을 받으려는 사용자) C가 있음을 가정하겠습니다. 이전에는 A, B가 동일한 날짜/시간 여부와 관계 없이 최대 3개의 일정을 C에게 요청을 보내고 있습니다. 시퀀스 다이어그램으로 나타내면 다음과 같습니다.

비포+시퀀스다이어그램

이전 플로우는 기획적 관점에서 "한번에 하나의 요청만 상세 보기를 통해 시간을 열람하기 때문에, C 사용자가 동일 시간에 A, B 로부터 요청이 왔음을 파악하기 어렵다"는 문제가 있었습니다.

따라서 요청을 보내려는 사용자는 1개의 날짜 및 시간을 선택하여 요청을 받으려는 사용자에게 발송되도록 기획을 변경했습니다.

이때, 이전에는 고려하지 않아도 되었던 동시성 문제가 대두되었습니다. "만약, A, B가 동일한 날짜/시간에 C에게 요청을 보냈을 경우, 가장 먼저 요청을 발송한 사용자의 요청만 C에게 도달하도록 한다." 라는 요구사항을 충족하기 위해 "동일한 날짜 및 시간에 대해서는 한 명의 요청만 허용" 되어야 합니다.

동시 요청 제어를 고려해 위 프로세스를 시퀀스 다이어그램으로 나타내면 아래와 같습니다.

애프터+시퀀스다이어그램

Database를 활용한 동시성 제어 기법 비교

동시성 제어를 위해 여러 가지 대안을 고려해 보았고, 최종적으로 데이터의 정합성을 최우선으로 하여 비관적 락(Pessimistic Lock)을 선택하게 되었습니다. 이 선택을 하기까지의 과정을 설명하며, 각 기법의 장단점을 정리해보겠습니다.

Synchronized, 평생 서버 1대만 둘 건 아니니까

Synchronized 키워드를 이용하여 동시성을 제어할 수 있는 방법은 간단하지만, 서버가 단일 인스턴스일 때만 유효한 해결책입니다. 만약 서버를 확장하여 여러 대의 서버가 요청을 처리하게 된다면, 각 인스턴스에서 별도의 메모리를 사용하기 때문에 락이 분산되지 않습니다. 따라서 서버를 확장할 계획이 있는 경우에는 적합하지 않은 방법입니다.

비관적 락 (Pessimistic Lock)

실제로 데이터에 Lock 을 걸어서 다른 트랜잭션이 접근하지 못하도록 하여 정합성을 맞추는 방법입니다. exclusive lock 을 걸게되며 다른 트랜잭션에서는 lock 이 해제되기전에 데이터를 가져갈 수 없게됩니다.
SELECT ... FOR UPDATE 같은 쿼리를 사용하여 데이터의 정합성을 보장합니다.
데드락이 걸릴 수 있기때문에 주의하여 사용하여야 합니다.

장점

  • 데이터 정합성을 철저히 보장합니다.
    가장 먼저 락을 획득한 요청만 처리되므로 충돌 가능성을 원천 차단합니다.

단점

  • 데드락(Deadlock) 발생 가능성이 있어 주의가 필요합니다.
    락을 걸고 있는 동안 다른 요청이 대기해야 하므로 성능 저하가 발생할 수 있습니다.

낙관적 락 (Optimistic Lock)

실제로 Lock 을 이용하지 않고 버전을 이용함으로써 정합성을 맞추는 방법입니다. 먼저 데이터를 읽은 후에 update 를 수행할 때 현재 내가 읽은 버전이 맞는지 확인하며 업데이트 합니다. 내가 읽은 버전에서 수정사항이 생겼을 경우에는 application에서 다시 읽은후에 작업을 수행해야 합니다.

네임드 락 (Named Lock)

이름을 가진 metadata locking 입니다. 이름을 가진 lock 을 획득한 후 해제할때까지 다른 세션은 이 lock 을 획득할 수 없도록 합니다. 주의할점으로는 transaction 이 종료될 때 lock 이 자동으로 해제되지 않습니다. 별도의 명령어로 해제를 수행해주거나 선점시간이 끝나야 해제됩니다.

Redis Lock

DataBase Lock를 이용하면 추가적인 인프라 구성 없이 동시성 문제를 해결할 수 있습니다. 하지만 Lock 획득을 위해 대기하는 Connection 이 증가할 수 있고, 이건 높은 트래픽 상황에서 부하로 이어집니다.

반면, Redis를 이용한 Distributed Lock은 DataBase Connection 증가를 방지할 수 있지만, 별도의 관리가 필요합니다. Redis 관리에는 메모리 최적화, 장애 복구, 데이터 일관성 유지 등의 과제가 부가적으로 필요하다는 의미입니다. 따라서 트래픽이 많아 트랜잭션이 많이 일어나거나, 다중 서버 환경인 경우 Redis를 이용한 Lock 기법을 고려해볼 수 있습니다.

분산 락

분산 락의 목적은 여러 분산된 서버가 존재할 때, 여러 서버에서 공유 자원에 접근하여 발생하는 Race Condition 자체를 없앱니다. 즉, 공유 자원 자체에 Lock을 설정하는 비관적 락/낙관적 락과 다르게 임계 영역(critical section)에 Lock을 설정합니다.

Redis를 사용한 분산 락에는 다음과 같이 2가지 방식이 존재합니다.

  1. Lettuce : Spin Lock으로 지속적으로 Redis 서버에 요청을 보내서 Lock 여부 확인
  2. Redisson : RedLock 알고리즘을 사용해서 Lock 획득-해제를 pub-sub 구조로 수행

참고 중인 링크

profile
공룡, 다람쥐 그리고 돌고래!

0개의 댓글