13. DB 커넥션 풀의 유휴 커넥션 사일런트 종료 (stale connection)

난이도 하 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오 (서버 내부): DB 커넥션 풀. 여러 워커가 커넥션을 빌려(Rent) 쿼리를
// 실행한 뒤 반납(Return)한다. 풀은 커넥션을 재사용해 매번 새로 여는 비용을
// 줄인다.
//
// DB 서버나 중간 방화벽/로드밸런서에는 유휴(idle) 커넥션을 일정 시간 뒤
// 자동으로 끊는 정책이 흔하다. 이 경우 TCP 연결 자체는 서버 쪽에서 이미
// 끊겼지만, 클라이언트(이 풀) 프로세스는 별다른 이벤트 없이는 그 사실을
// 알아채지 못한다.
//
// 요구사항:
//   - 풀에서 커넥션을 빌려줄 때, 그 커넥션은 실제로 사용 가능한 상태여야
//     한다.
//   - 유휴 시간 동안 서버 쪽에서 이미 끊어진 커넥션을 그대로 빌려줘 워커의
//     첫 쿼리가 연결 오류로 실패하는 일이 없어야 한다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public interface IDbConnection
{
    bool IsOpen { get; }
    Task<int> ExecuteAsync(string sql);
    Task CloseAsync();
}

public sealed class ConnectionPool
{
    private readonly Stack<IDbConnection> _idle = new();
    private readonly Func<Task<IDbConnection>> _createConnection;

    public ConnectionPool(Func<Task<IDbConnection>> createConnection)
    {
        _createConnection = createConnection;
    }

    // 커넥션을 빌린다.
    public async Task<IDbConnection> RentAsync()
    {
        // (A) 유휴 큐에 남은 커넥션이 있으면 그대로 돌려준다.
        //     이 커넥션이 실제로 아직 살아있는지는 확인하지 않는다.
        if (_idle.Count > 0)
        {
            return _idle.Pop();
        }

        return await _createConnection();
    }

    // 사용이 끝난 커넥션을 반납한다.
    public void Return(IDbConnection conn)
    {
        // (B) 반납된 커넥션을 그대로 유휴 큐에 넣는다.
        _idle.Push(conn);
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.