DB Connection Pool 분리




순서
BootStrap.groovy 에서 DB Connection Pool 생성 시 필요한 DB Properties 를 Map 에 저장함.
private static final ConcurrentHashMap<String, Properties> dbPropertiesMap = [:]
static final
키워드는 해당 필드가 클래스의 인스턴스 간에 공유되며, 한번 초기화 되면 그 값이 변경 되지 않음을 의미함.
즉, 변수 자체의 참조가 변경되지 않는다는 것이고, 변수가 참조하는 객체의 내부 상태는 변경될 수 있음.
그 후에 Connection Pool 을 생성하고 있지 않다가 GameService 같은 곳에서 DynamicConnectionPoolManager에 있는 getDataSource 호출 시
if (!dataSources.containsKey(dataSourceName) || dataSources.get(dataSourceName) == null) {
Properties dbProps = dbPropertiesMap.get(dataSourceName)
if (dbProps != null) {
try {
DataSource dataSource = createDataSource(dbProps)
dataSources.put(dataSourceName, dataSource)
} catch (Exception e) {
dataSources.put(dataSourceName, null) // 실패 시 null로 설정
throw new RuntimeException("Failed to create DataSource for $dataSourceName: ${e.message}", e)
}
} else {
throw new IllegalStateException("No database properties found for: $dataSourceName")
}
}else {
이 if 절에 걸려서 createDataSource 로 Connection Pool 을 생성하게 됨.
PoolProperties p = new PoolProperties()
String dataSourceName = dbProps.get("dataSourceName") // 실제 사용되는 db 이름
String dbType = dbProps.get("dbType") // mysql, oracle, postgresql, mssql
p.setUrl(dbProps.get("url").toString())
p.setDriverClassName(dbProps.get("dbDriver").toString())
p.setUsername(dbProps.get("username").toString())
p.setPassword(dbProps.get("password").toString())
def connectionProperty =[:]
if(Environment.current.name == 'test'){
connectionProperty = dbType == 'oracle'? DataSourceConfig.dbPropertiesForTestOracle.call() : DataSourceConfig.dbPropertiesForTest.call()
}else if (Environment.current.name == 'production') {
connectionProperty = dbType == 'oracle'? DataSourceConfig.dbPropertiesForLiveOracle.call() : DataSourceConfig.dbPropertiesForLive.call()
}
if(!connectionProperty.isEmpty()){
...
}
return new DataSource(p)
이렇게 처음에 dbPropertiesMap 에 저장된 값을 dataSourceName 으로 가져와서 Connection Pool을 맺는 과정을 거침.
이미 있는 Connection Pool 은?
데이터베이스가 점검날 개발사에서 점검을 해버리면 Connection Pool 은 잡고 있는 상태로 네트워크만 끊어져 Connection Pool 이 이상하게 된다.
그렇게 다시 DB 연결이 되서 재시도 하게 되면 이미 닫힌 Connection 에 연결을 넣는다는 error 가 나오게 된다.
그러므로 getDataSource 를 할때
}else {
// 이미 생성된 DataSource에 대한 유효성 검사
Properties dbProps = dbPropertiesMap.get(dataSourceName)
DataSource dataSource = dataSources.get(dataSourceName)
String dbType = dbProps.get("dbType").toString()
/*
Connection 이 있다가 네트워크 문제로 끊겼다가 재연결 될때 Exception 이 나옴.
create 하다가 터졌을 때 dataSource Map 에서 삭제
valid 통과 시에는 그냥 넘김
*/
if (!isConnectionValid(dataSource,dbType)) {
// 유효하지 않은 경우, DataSource 재생성
try {
dataSource = createDataSource(dbProps)
dataSources.put(dataSourceName, dataSource)
} catch (Exception e) {
dataSources.remove(dataSourceName) // 실패 시 DataSource 제거
throw new RuntimeException("Failed to recreate DataSource for $dataSourceName: ${e.message}", e)
}
}
}
이 부분에서 isConnectionValid 를 호출해서 Connection Pool 이 정상적인지 확인하게 된다.
isConnectionValid 에서는
private static boolean isConnectionValid(DataSource dataSource, String dbType) {
String query = "SELECT 1"
if(dbType && dbType == 'oracle'){
query = "SELECT 1 FROM DUAL" // oracle 은 DUAL 테이블
}
try {
Connection conn = dataSource.getConnection()
Statement stmt = conn.createStatement()
stmt.executeQuery(query)
conn.close()
return true
} catch (SQLException e) {
return false
}
}
MySQL 의 경우에는 SELECT 1
, ORACLE 에 경우에는 SELECT 1 FROM DUAL
이라는 validation query 를 날려서 Connection 을 확인하게 됨.
좀 높은 버전에서는 isValid() 라는 녀석이 있지만 버전이 낮아서 SELECT 1
을 날려야함.
SELECT 1
or SELECT 1 FROM DUAL
을 했을 때 안되면 다시 createDataSource
를 하게 됨.
하지만 뭐 할때마다 SELECT 1 을 날리면?
SELECT 1
이나 SELECT 1 FROM DUAL
의 경우는 많은 요청이 올 시 성능 문제를 일으킬 수 있기 때문에 해당 부분에서는 Connection Pool 생성 시에 옵션 validationTimeout 이 있는지 확인 후에 소스 수정이 필요할 것으로 보임.
Connection Pool 라이브러리를 다시 사용하여 ValidationTimeout 기능을 사용
HealthCheck 를 자동
연결 유효성 검사를 자동화하고, 연결 상태를 실시간으로 모니터링할 수 있는 시스템을 구축하는 것이 좋을듯함. 예를 들어 k8s 환경에서는 Liveness Probe 와 Readiness Probe 를 설정하여 컨테이너의 상태를 주기적으로 체크하고, 문제가 발견되면 자동으로 재시작하도록 설정할 수 있음.
테스트 준비
DB Connection Pool 제거 부하 테스트
호출하는 API : testApi/checkExistIngameAccount
호출 가는 DB : TESTGAMEACCOUNT(MySQL)
SELECT * FROM account;
Connection 확인 한 쿼리
SHOW STATUS LIKE 'Threads_connected';
테스트 시작
분리 미적용
쿼리 실행 전

1만건 이상 요청 시

시간 : 1분
분리 적용
쿼리 실행 전

1만건 이상 요청 시

시간 : 6분
여기에는 문제가 있음.
왜냐하면 StartUp 시 Connection Pool 을 잡기 전에 들어간거여서 시간이 오래 걸린 거였음..
아래는 Connection Pool 을 미리 다 잡은 상태에서 테스트 시의 결과임.

시간 : 1분 30초
결론
초반만 Connection Pool 을 생성하는 작업 때문에 시간이 걸리지만 Connection Pool 을 잡고 하면 Connection 수는 늘어나지만 속도는 조금 더 빨라짐.
추가적으로 다른 Game DB 의 네트워크 연결이 안되어도 본사 플랫폼 DB 만 연결이 된다면 start up 이 가능해짐.
점검 날 DB 점검 유무 상관 없이 배포를 할 수 있음.
Last updated
Was this helpful?