← 문제로

5. 예외 경로의 커넥션/트랜잭션 누수 (풀 고갈 / 잠금 장기 보유)

난이도 하
내 리뷰 · C#
해설 · C#

해설 — 예외 경로의 커넥션/트랜잭션 누수 (풀 고갈 / 잠금 장기 보유)

난이도: 하

요약

SaveCharacter 는 커넥션 대여(A)·트랜잭션 시작(B) 후 곧장 UPDATE 들을 실행하고, 반납(E)과 커밋(D)을 정상 흐름에만 둔다. 중간 Execute(C)가 예외를 던지면 (D)(E)에 도달하지 못해, 트랜잭션이 롤백되지 않고 커넥션이 풀로 반납되지 않는다. 이런 저장이 반복되면 풀의 커넥션이 하나씩 새어나가 결국 풀 고갈 → 이후 모든 Rent() 가 대기/타임아웃하며 저장 전체가 멈춘다. 더해 열린 채 버려진 트랜잭션이 행 잠금을 계속 쥐고 있어 다른 트랜잭션이 막히고 데드락이 번진다.

문제점

1. (자원 누수) 예외 시 커넥션 미반납 — (A)(E)

  • 증상: Execute 예외 → _pool.Return(conn) 미실행. 커넥션이 "대여 중" 상태로 영영 남는다.
  • 재현조건: 중간 UPDATE 가 데드락 피해자/타임아웃/제약위반으로 throw. 간헐적이어도 누적된다.
  • 근본원인: 정리(반납)가 try/finally/using 으로 보장되지 않고 정상 경로에만 있다.

2. (트랜잭션 누수 / 잠금 보유) 예외 시 미롤백 — (B)(D)

  • 증상: 커밋도 롤백도 안 된 트랜잭션이 열린 채 남아 행/인덱스 잠금을 유지. 다른 저장이 그 행을 못 건드려 블로킹·데드락 확산.
  • 근본원인: 실패 시 tx.Rollback() 을 부르는 경로가 없다.

3. (연쇄 장애) 풀 고갈 → 가용성 붕괴

  • 누수가 쌓이면 풀이 비어 Rent() 가 타임아웃. 일부 저장의 일시적 오류가 전체 저장 불능으로 증폭된다(부분 장애의 전면화).

수정안

using/try-finally 로 커밋 여부와 무관하게 항상 롤백·반납을 보장한다.

public void SaveCharacter(Character c)
{
    IDbConnection conn = _pool.Rent();
    try
    {
        IDbTransaction tx = conn.BeginTransaction();
        try
        {
            conn.Execute(tx, "UPDATE stats ...", c.Stats);
            conn.Execute(tx, "UPDATE inventory ...", c.Inventory);
            conn.Execute(tx, "UPDATE quests ...", c.Quests);
            tx.Commit();
        }
        catch
        {
            tx.Rollback();   // 실패 시 잠금 즉시 해제
            throw;
        }
    }
    finally
    {
        _pool.Return(conn);  // 성공/실패/예외 모두 반납
    }
}
  • 더 깔끔하게는 IDisposable 을 구현해 using var conn = _pool.Rent();(Dispose 가 반납), using var tx = ...(Dispose 가 미커밋 시 롤백) 패턴으로 정리한다.
  • 커넥션을 풀에 되돌릴 때 트랜잭션이 깨끗한 상태인지(미완료 트랜잭션 없음) 검증하거나, 의심되면 커넥션을 폐기·재생성한다.

더 나은 설계

  • 풀 대여/반납과 트랜잭션 커밋/롤백을 RAII/using 으로 캡슐화한 단위 작업(UnitOfWork) 으로 감싸 호출부에서 실수할 여지를 없앤다.
  • Rent() 에 타임아웃을 두고, 풀 사용률·대여 시간·누수 의심(오래 점유된 커넥션)을 메트릭으로 관측해 누수를 조기 발견.
  • 일시적 오류(데드락/타임아웃)는 짧은 백오프 후 재시도하되, 재시도도 같은 정리 보장 안에서 수행.

면접 포인트

  1. 빌린 자원(커넥션·트랜잭션·락)의 해제는 정상 경로가 아니라 finally/using/RAII 에 둬야 예외에도 보장된다.
  2. 열린 채 버려진 트랜잭션은 잠금을 계속 쥐어 국소 오류를 시스템 전역 블로킹으로 키운다 — 실패 시 즉시 롤백이 중요.
  3. 커넥션 풀 누수는 부분 장애를 전면 장애로 증폭한다. 정리 보장 + 풀 관측(대여 시간/사용률)으로 막는다.