본문 바로가기
프로그래밍/C#

C# EF Core 환경에서 데드락 방지 방법

by bantomak 2025. 5. 12.
반응형

EF Core 환경에서의 동시성 문제

  • EF Core는 DbContext 단위에서 추적한 객체만 SaveChanges() 매서도 호출 시에 DB에 변경사항을 반영한다.
  • 동시에 두 유저가 같은 데이터를 수정하려고 하면 경합 상태(Race condition)가 발생할 수 있다.
  • 문제 발생 예시
    • A 유저가 B 유저를 공격해서 B.HP -= 10 수행 중
    • 동시에 C 유저도 B 유저를 공격해서 B.HP -= 20
    • 결과적으로 B의 최종 HP가 -10 감소 또는 -20 감소로 덮어쓰기될 가능성이 있음 (논리적 오류 발생)

그래서 보통의 일반적인 경우 내 데이터에 대한 변경은 나만 수행하는게 경합 상태를 발생시키지 않는 방법이다. 하지만 보통의 경우에는 유저들 사이의 상호작용은 빈번하게 일어나기 때문에 경합 상태는 필연적이다. 그래서 이를 해결하기 위해서 보통의 경우 락(Lock)을 도입하여 사용한다.

락(Lock) 도입으로 인한 부작용

락을 도입하면 경합 상태를 해결하고 논리적 오류의 발생을 해결할 수 있지만 잘못 설계하면 데드락이라는 무서운 결과를 초래할 수 있다. 

  • 경합 상태(Race condition)을 막기 위해 락을 도입했는데, 락 순서를 잘못 설계하면 데드락 발생!
  • 즉, 경합 상태를 막기 위한 조치가 잘못되면 데드락으로 이어질 수 있음

EF Core에서는 await dbContext.SaveChanges()와 같은 저장 매서드를 호출하면 그동안의 변경사항들을 추적해서 DB에 반영한다. 이때 데이터에 대한 변경이 발견되면 락을 걸어서 데이터들을 보호한 상태에서 변경이 이뤄진다.

  • 변경된 엔티티들을 감지
  • 각 엔티티에 대해 적절한 UPDATE, INSERT, DELETE SQL 쿼리 생성
  • 이 쿼리를 하나의 트랜잭션으로 DB에 실행
  • 이때, DB는 각 SQL이 영향을 주는 대상 레코드에 락을 건다.

즉, 트랜젝션의 일관성과 원자성(ACID)을 지키기 위해 락을 걸 수밖에 없다.

데드락(Deadlock) 시나리오

두 유저가 서로 친구 요청을 수락하는 상황을 생각해보자.

-- A 먼저 락
SELECT * FROM Players WHERE Id = 1 FOR UPDATE;

-- 약간의 처리 후 B 락 시도
SELECT * FROM Players WHERE Id = 2 FOR UPDATE;
-- B 먼저 락
SELECT * FROM Players WHERE Id = 2 FOR UPDATE;

-- 약간의 처리 후 A 락 시도
SELECT * FROM Players WHERE Id = 1 FOR UPDATE;
  • 트랜잭션 1은 A를 잠금 -> B를 기다림
  • 트랜잭션 2는 B를 잠금 -> A를 기다림
  • 서로 상태방이 잠근 리소스를 기다리는 상황이 되면서 데드락 발생!

이를 방지하기 위해서 항상 같은 순서로 락을 걸어야 한다.

// 오름차순으로 비교후 항상 동일한 순서로 락을 걸도록 한다.
if (a.Id < b.Id)
{
    -- A 먼저 락
    SELECT * FROM Players WHERE Id = a.Id FOR UPDATE;

    -- 약간의 처리 후 B 락 시도
    SELECT * FROM Players WHERE Id = b.Id FOR UPDATE;
}
else
{
    -- B 먼저 락
    SELECT * FROM Players WHERE Id = b.Id FOR UPDATE;

    -- 약간의 처리 후 A 락 시도
    SELECT * FROM Players WHERE Id = a.Id FOR UPDATE;
}
  • 동시에 두 유저가 같은 데이터를 변경하려고 하면 경합 상태가 발생 > 이를 해결하기 위해서 락을 도입하면 데드락 발생 위험이 존재함
  • 데드락의 발생을 막기 위해서 항상 동일한 순서로 락을 걸어서 락 획득 순서를 동일하게 유지할 필요가 있음(ex. Id 오름차순)
  • 항상 동일한 순서로 락을 획득하기 때문에 처리 중이라면 기다리고 획득 이후에 작업을 진행하면 된다.

기본적으로 데드락은 특정 데이터를 동시에 접근해서 수정하는 경우에 발생하기 때문에 테스트 환경에서는 발생 확률이 낮다. 하지만 구조적으로 데드락 발생 가능성이 내포되어 있고, 시스템이 성장하거나 트래픽이 늘어나면 발생한 가능성이 증가한다.

댓글