[서비스 환경]
- 프레임 워크: .NET Framework 4.6.1
- 데이터베이스: Microsoft SQL Server 2016 (MSSQL 16)
- 웹 서버: IIS
서비스 운영 중 특정 시간 이후 모든 API 요청이 무한로딩 상태로 빠지는 현상이 발생했다.
문제를 분석한 결과, DB 연결을 적절히 닫아주지 않은 문제로 인해 IIS가 먹통이 되는 상황이었다.
이 포스트에서는 해당 문제의 원인과 해결 방법을 단계적으로 설명한다.
1. 문제 증상
서비스가 정상적으로 시작되지만, 약 1시간 후부터 일부 API 요청이 무한 로딩 상태에 빠지며, 연결이 끊어지는 문제가 발생했다.
다만, HTML, CSS, JS와 같은 정적 파일 요청은 정상적으로 처리되었다.
즉, API 요청 처리에서 문제가 발생한 것이다.
2. 문제 분석
초기 분석 결과, API 요청이 MSSQL과의 연결을 시도하는 과정에서 문제가 발생했음을 확인했다.
주요 원인은 다음과 같았다.
- DB 연결 후 닫아주지 않음
일부 코드에서 SqlConnection을 클래스의 멤버 변수로 선언한 후 명확히 닫아주지 않아 연결이 계속 유지되었다.
이로 인해 연결 풀(Connection Pool)이 고갈되어 새로운 DB 연결이 불가능한 상태가 되었다.
(DB Connection Pool에 대해서는 이전포스팅을 참고하자)
- 연결 풀 고갈
SqlConnection을 적절히 닫지 않아 사용 가능한 연결이 점점 줄어들었고, 결국 추가적인 DB 연결이 차단되었다.
3. 해결 방안
문제를 해결하기 위해 DB 연결을 올바르게 관리하는 방식으로 코드를 수정했다.
1) using 문을 활용한 명확한 연결 관리
using문을 사용하면 SqlConnection 객체가 자동으로 해제되며, 연결 풀에 반환된다.
using (SqlConnection connection = new SqlConnection("YourConnectionStringHere"))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT * FROM YourTable", connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["YourColumnName"].ToString());
}
}
}
}
2) SqlConnection을 멤버 변수로 사용할 경우 명확한 관리가 필요
SqlConnection을 클래스 멤버 변수로 사용할 수도 있지만, 반드시 명확하게 닫아줘야 한다.
특히 연결을 여러 번 수행해야 하는 경우, 객체를 재사용하는 것이 성능적으로 유리할 수 있다.
그러나 이를 올바르게 관리하지 않으면 연결이 계속 열린 상태로 남아 리소스 누수가 발생할 수 있다.
🚫 잘못된 코드 (멤버 변수 사용 + Close 누락)
public class DatabaseService
{
private SqlConnection _connection = new SqlConnection("YourConnectionStringHere");
public void ExecuteQuery()
{
_connection.Open(); // ❌ 여러 번 열지만 닫지 않음
using (SqlCommand command = new SqlCommand("SELECT * FROM YourTable", _connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["YourColumnName"].ToString());
}
}
}
}
}
✅ 올바른 코드 (멤버 변수 사용 시 명확한 Close 처리)
public class DatabaseService : IDisposable
{
private SqlConnection _connection;
public DatabaseService()
{
_connection = new SqlConnection("YourConnectionStringHere");
}
public void ExecuteQuery()
{
if (_connection.State != ConnectionState.Open)
{
_connection.Open();
}
using (SqlCommand command = new SqlCommand("SELECT * FROM YourTable", _connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["YourColumnName"].ToString());
}
}
}
}
public void Dispose()
{
if (_connection != null && _connection.State == ConnectionState.Open)
{
_connection.Close();
_connection.Dispose();
}
}
}
✔ 클래스 멤버 변수로 SqlConnection을 사용할 때는 Dispose()를 명확하게 구현해야 한다.
✔ 이 방식은 여러 개의 DB 쿼리를 실행하는 동안 동일한 연결을 유지할 수 있어 성능적으로 유리할 수 있다.
3) Max Pool Size 설정하여 연결 풀 고갈 방지
연결 풀이 고갈되지 않도록 Max Pool Size를 적절하게 설정한다.
"Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;Max Pool Size=100;"
4) 명시적인 연결 종료 (Close() 및 Dispose())
SqlDataReader와 SqlCommand를 사용한 후에는 명시적으로 Dispose() 또는 Close() 를 호출해 자원을 해제한다.
reader.Close();
command.Dispose();
connection.Close();
4. 결과
위와 같은 방식으로 DB 연결 관리를 개선한 후, IIS에서 발생한 무한로딩 문제를 해결할 수 있었다.
API 요청이 정상적으로 처리되었으며, 연결 풀 고갈 문제도 사라졌다.
5. 마무리
- DB 연결은 꼭 닫아야 한다.
SqlConnection 을 사용한 후 반드시 닫아주지 않으면 연결 풀이 고갈될 수 있다.
- 클래스 멤버 변수로 SqlConnection을 사용할 수 있지만, 명확한 관리가 필요하다.
연결을 여러 번 수행해야 하는 경우, SqlConnection 을 멤버 변수로 선언할 수 있다.
그러나 반드시 Dispose() 를 명확히 구현해야 하며, 불필요한 Open() 상태가 유지되지 않도록 해야 한다.
- using문을 적극 활용하자.
using 문을 사용하면 자동으로 연결이 닫히므로 실수로 연결을 해제하지 않는 문제를 방지할 수 있다.
- 연결 풀과 타임아웃 설정을 신경 쓰자.
Max Pool Size를 적절히 설정하고, 불필요한 연결 유지가 없도록 코드를 관리해야 한다.
이 포스트가 IIS와 MSSQL을 사용하는 환경에서 발생하는 API 요청 지연 문제를 해결하는 데 도움이 되었기를 바랍니다.
틀린 내용이 있다면 댓글로 남겨주세요!
'Record > Trubble Shooting' 카테고리의 다른 글
[Python] pip 명령어 문제 해결: Fatal error in launcher: Unable to create process using '"' (1) | 2023.10.07 |
---|---|
C# ASP.NET에서 JavaScriptSerializer maxJsonLength 초과 에러 발생 해결 (0) | 2023.06.26 |
[Postgresql14] postgres service가 에러로인해 시작되지 않던 문제 해결 (0) | 2023.02.01 |
[NPM] 사용했던 Package가 업데이트되면서 발생되던 버그 해결 (0) | 2022.12.08 |
API 서버에 설치한 postgresql 서비스가 간헐적으로 죽는 문제 해결 (0) | 2022.08.20 |