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++
// ============================================================================
// 시나리오 (서버 내부)
// ----------------------------------------------------------------------------
// DB 커넥션 풀. 여러 워커 스레드가 커넥션을 빌려(Rent) 쿼리를 실행한 뒤
// 반납(Return)한다. 풀은 커넥션을 재사용해 매번 새로 여는 비용을 줄인다.
//
// DB 서버나 중간 방화벽/로드밸런서에는 유휴(idle) 커넥션을 일정 시간 뒤
// 자동으로 끊는 정책이 흔하다. 이 경우 TCP 연결 자체는 서버 쪽에서 이미
// 끊겼지만, 클라이언트(이 풀) 프로세스는 별다른 이벤트 없이는 그 사실을
// 알아채지 못한다.
//
// 요구사항:
// - 풀에서 커넥션을 빌려줄 때, 그 커넥션은 실제로 사용 가능한 상태여야
// 한다.
// - 유휴 시간 동안 서버 쪽에서 이미 끊어진 커넥션을 그대로 빌려줘 워커의
// 첫 쿼리가 연결 오류로 실패하는 일이 없어야 한다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.
// ============================================================================
#include <functional>
#include <memory>
#include <stack>
#include <string>
class IDbConnection
{
public:
virtual bool IsOpen() const = 0;
virtual int Execute(const std::string& sql) = 0; // 실패 시 예외를 던진다고 가정
virtual void Close() = 0;
virtual ~IDbConnection() = default;
};
class ConnectionPool
{
public:
using ConnectionFactory = std::function<std::shared_ptr<IDbConnection>()>;
explicit ConnectionPool(ConnectionFactory factory) : factory_(std::move(factory)) {}
// 커넥션을 빌린다.
std::shared_ptr<IDbConnection> Rent()
{
// (A) 유휴 스택에 남은 커넥션이 있으면 그대로 돌려준다.
// 이 커넥션이 실제로 아직 살아있는지는 확인하지 않는다.
if (!idle_.empty())
{
auto conn = idle_.top();
idle_.pop();
return conn;
}
return factory_();
}
// 사용이 끝난 커넥션을 반납한다.
void Return(std::shared_ptr<IDbConnection> conn)
{
// (B) 반납된 커넥션을 그대로 유휴 스택에 넣는다.
idle_.push(std::move(conn));
}
private:
std::stack<std::shared_ptr<IDbConnection>> idle_;
ConnectionFactory factory_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.