# DB Connection Pool 정리

겪었던 이슈 중 하나가 **배포 시점에 DB Connection Pool 생성이 실패하면서 애플리케이션이 정상적으로 올라오지 않던 문제** 였다. 당시 이 문제를 해결하면서 단순히 "연결이 안 된다" 는 현상만 보는 게 아니라, **애플리케이션 초기화 시점과 외부 DB 상태에 대한 의존성** 을 함께 고려해야 한다는 점을 배웠다.

당시 운영 환경에서는 배포 과정에서 애플리케이션이 기동되면서 DB Connection Pool 을 생성하는 구조였다. 문제는 특정 시점에 외부 DB 상태가 불안정하거나 점검 중이면, 애플리케이션이 시작되는 순간 Connection Pool 생성 자체가 실패하고, 그 결과 배포가 정상적으로 끝나지 못한다는 점이었다.<br>

겉으로 보기에는 단순히 DB 연결 실패처럼 보이지만, 실제로는 **애플리케이션의 시작과 DB 연결이 너무 강하게 결합되어 있었던 것** 이 문제의 핵심이었다.

즉, DB 가 잠깐이라도 응답하지 않으면 서비스 전체가 아예 올라오지 못하는 구조였고, 운영에서는 이런 구조가 배포 안정성을 크게 떨어뜨렸다.<br>

#### 왜 이게 문제였는가

이 이슈가 특히 불편했던 이유는 다음과 같았다.

* 배포시점에 DB 상태가 좋지 않으면 애플리케이션 기동 자체가 실패한다.
* 외부 환경 이슈인데도 배포 실패로 이어져 운영 일정이 꼬인다.
* 코드 변경과 무관한 외부 변수 때문에 배포가 막히므로 원인 파악이 늦어진다.
* 일시적인 DB 점검이나 연결 불안정이 전체 서비스 가용성에 영향을 줄 수 있다.

운영개발 관점에서 보면 이런 구조는 좋지 않다.

실서비스에서는 항상 모든 외부 자원이 완벽하게 준비된 상태만 가정할 수 없기 때문이다.\
특히 배포는 가능한 한 **예측 가능하고 재현 가능해야** 하는데, 외부 DB 상태에 따라 성공 여부가 흔들리면 안정적인 운영이 어렵다.

#### 해결방향

이 문제를 해결하면서 내가 본 핵심은 두 가지였다.

1. **Connection Pool 생성 시점을 통제할 것**
2. **외부 DB 상태에 의해 배포 전체가 막히지 않게 할 것**

&#x20;이 과정에서 **Singleton 패턴 기반의 Custom DataSource** 를 적용하는 방식으로 구조를 정리했다.

핵심은 DataSource 를 무분별하게 생성하거나 초기화 과정에 종속시키지 않고, **애플리케이션 내부에서 보다 일관된 방식으로 관리** 하도록 만든 것이었다.&#x20;

<figure><img src="/files/kidYFeDzKV7M88kzrpCA" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fXPguUPVEzJLJ52P56t7" alt=""><figcaption></figcaption></figure>

```groovy
DataSourceHelper.metaClass.methods.each { MetaMethod method -> 
    if(method.static && method.returnType == Properties) {
        Properties dbProps = method.invoke(null) // call static method
        String dataSourceName = dbProps.get("dataSourceName").toString()
        DynamicConnectionPoolManager.addDbProperties(dataSourceName, dbProps)
    }
}
```

***

#### 순서

Grails 실행 ▶️ `BootStrap.groovy` 안에 코드들 실행 ▶️ 위 코드 처럼 DataSourceHepler 의 `static 이면서 return 타입이 Properties` 인 메서드들만 호출해서 `Properties` 들을 세팅함.

`DataSourceHelper`

```groovy
public static Properties get<GameFullName>ConnectionInfo() {
        Properties dbConnectionInfo = new Properties()
        String databaseName = ""
        String server = ""
        String gname = "gname"
        try{
            if(Environment.current == Environment.TEST) {
                databaseName = "asdf"
                server = "<ip>"
                dbConnectionInfo.put("password","password")
            }else{
                databaseName = "asdf"
                server = "<ip>"
                dbConnectionInfo.put("password","password")
            }
            dbConnectionInfo.put("url","jdbc:jtds:sqlserver://"+server+":1433;databaseName="+databaseName)
            dbConnectionInfo.put("username", "asdf")
            dbConnectionInfo.put("databaseName", databaseName)
            dbConnectionInfo.put("dbDriver","net.sourceforge.jtds.jdbc.Driver")
            dbConnectionInfo.put("dbType","mssql")
            dbConnectionInfo.put("dataSourceName","dataSource_${gname}")
            dbConnectionInfo.put("gname", gname)
        }catch(Exception e){
            saveErrorLogToDB(e.getMessage(), 'get${game_full_name}ConnectionInfo', gname)
            e.printStackTrace()
        }
​
        return dbConnectionInfo
    }
```

간략하게 DataSourceHelper 는 위와 같은 설정값들을 세팅하는 static 메서드들 밖에 없음.

이렇게 method 들을 `Properties dbProps = method.invoke(null)` 이런식으로 호출 후 `dataSourceName = dbProps.get("dataSourceName").toString()` 문자열에 dataSourceName 을 넣어준 후 `DynamicConnectionPoolManager` 의 `addDbProperties` 를 한다.

***

#### `addDbProperties`

```groovy
// connection pool 생성 완료된 Connection Pool 을 Map에 담아놓음
private static final ConcurrentHashMap<String, DataSource> dataSources = [:]
// connection Pool 생성을 위한 DB 정보를 담아둠.
private static final ConcurrentHashMap<String,Properties> dbPropertiesMap = [:]
...
static void addDbProperties(String dataSourceName, Properties dbProps) {
        String msg = ""
        try{
            dbPropertiesMap.putIfAbsent(dataSourceName, dbProps)
        }catch(Exception e) {
            def gname = dbProps.get("gname").toString()
            msg = "로그남길메시지넣"
            saveErrorLogToDB(msg,'addDbProperties', gname)
            Logger.log3(msg,'DynamicConnectionPoolManager')
            e.getStackTrace()
        }
    }
```

***

#### DB 호출하는 메서드 실행 시

```groovy
String query = String.format("실행할 쿼리 WHERE col = $s" , "sadfads")

String dataSourceName = 'dataSource_${gname}'

String logPath = "해당 메서드 이름 또는  저장하고싶은이름"

def result = commonGameService.gameQueryExecuteRowConnectionPool(dataSourceName, query, logPath)
```

**CommonGameService.groovy**

**gameQueryExecuteRowConnectionPool**

```groovy
def gameQueryExecuteRowConnectionPool(String dataSourceName, String query, String logPath) {
    Connection conn = null
    Sql sql = null
    def map = [:]
    try {
        DataSource dataSource = null
        if(dataSourceName) {
            dataSource = DynamicConnectionPoolManager.getDataSource(dataSource)
        } else {
               return map.put("Return", false)    
        }

    } catch(Exception e) {
        // 예외 처리
    }
}
```

위 방식으로 DataSource 의 Pool 을 가져온다.

**`DynamicConnectionPoolManager.getDataSource(dataSource)`**

```groovy
static DataSource getDataSource(String dataSourceName) {
    /*
    dataSource Map 에 db 이름에 맞는 dataSource 가 있는지 없는지 또는 dataSource Map 에서 DB 이름에 맞는 
    DataSource 가 null 로 들어가 있지 않은지
    create 하다가 터지면 dataSource Map 에 null 로 들어감.
    */
    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 로 설정
                thorw new RuntimeException("Failed to create DataSource For ~~~", e)
            }
        } else {
            throw new IllegalStateException("No Database Properties found for :: sadfadsfasd")
        }
    } else {
        Properties dbProps = dbProperties.get(dataSourceName)
        DataSource dataSource = dataSource.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 ::: ~~~", e)
            }
        }
    }
    return dataSources.get(dataSourceName)
}
```

**해당 게임 DB 의 Connection Pool 이 없을 때**

`DynamicConnectionPoolManager.createDataSource`

```groovy
private static DataSource createDataSource(Properties dbProps) {
        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()){
            p.setJmxEnabled((Boolean)connectionProperty.get("jmxEnabled"))
            p.setInitialSize(connectionProperty.get("initialSize").toString().toInteger())
            p.setMaxActive(connectionProperty.get("maxActive").toString().toInteger())
            p.setMinIdle(connectionProperty.get("minIdle").toString().toInteger())
            p.setMaxIdle(connectionProperty.get("maxIdle").toString().toInteger())
            p.setMaxWait(connectionProperty.get("maxWait").toString().toInteger())
            p.setMaxAge(connectionProperty.get("maxAge").toString().toInteger())
          p.setTimeBetweenEvictionRunsMillis(connectionProperty.get("timeBetweenEvictionRunsMillis").toString().toInteger())
           p.setMinEvictableIdleTimeMillis(connectionProperty.get("minEvictableIdleTimeMillis").toString().toInteger())
            p.setValidationQuery(connectionProperty.get("validationQuery").toString())
           p.setValidationQueryTimeout(connectionProperty.get("validationQueryTimeout").toString().toInteger())
            p.setValidationInterval(connectionProperty.get("validationInterval").toString().toInteger())
            p.setTestOnBorrow((Boolean)connectionProperty.get("testOnBorrow"))
            p.setTestWhileIdle((Boolean)connectionProperty.get("testWhileIdle"))
            p.setTestOnReturn((Boolean)connectionProperty.get("testOnReturn"))
            p.setJdbcInterceptors(connectionProperty.get("jdbcInterceptors").toString())
       p.setDefaultTransactionIsolation(connectionProperty.get("defaultTransactionIsolation").toString().toInteger())
        }
        return new DataSource(p)
    }
```

### 결과

이후에는 배포 시 DB 연결 상태 때문에 Connection Pool 생성이 실패하던 문제를 줄일 수 있었고, 적어도 "외부 DB 상태 문제 때문에 애플리케이션이 무조건 배포 실패로 이어지는 상황" 을 완화할 수 있었다.

이 경험을 통해 단순히 DB 연결을 붙이는 기술 자체보다도, **배포 시 초기화 구조와 외부 의존성 관리가 운영 안정성에 얼마나 큰 영향을 주는지** 를 체감했다.

**배운 점**

이 이슈를 겪고 나서 내가 얻은 교훈은 명확했다.

* 외부 자원에 대한 의존성은 초기화 단계에서 특히 조심해야 한다.
* 배포 실패는 코드 오류만이 아니라 환경 구조의 문제일 수도 있다.
* 운영개발에서는 기능구현 보다 **실패 지점을 예측 가능하게 만드는 설계** 가 중요하다.
* "연결이 안 된다\*\* 는 현상 뒤에는 생성 시점, 초기화 구조, 의존성 결합 문제가 숨어 있을 수 있다.

결국 이 경험은 단순한 트러블 슈팅 하나가 아니라, **배포 안전성과 외부 의존성 관리에 대한 시각을 바꿔준 문제 해결 경험** 이었다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wjddustkd45.gitbook.io/organizeme/company-org/db-connection-pool.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
