# 외부 DB 연결 풀 관리 가이드 ## 📋 개요 외부 DB 연결 풀 서비스는 여러 외부 데이터베이스와의 연결을 효율적으로 관리하여 **연결 풀 고갈을 방지**하고 성능을 최적화합니다. ### 주요 기능 - ✅ **자동 연결 풀 관리**: 연결 생성, 재사용, 정리 자동화 - ✅ **연결 풀 고갈 방지**: 최대 연결 수 제한 및 모니터링 - ✅ **유휴 연결 정리**: 10분 이상 사용되지 않은 풀 자동 제거 - ✅ **헬스 체크**: 1분마다 모든 풀 상태 검사 - ✅ **다중 DB 지원**: PostgreSQL, MySQL, MariaDB - ✅ **싱글톤 패턴**: 전역적으로 단일 인스턴스 사용 --- ## 🏗️ 아키텍처 ``` ┌─────────────────────────────────────────┐ │ NodeFlowExecutionService │ │ (외부 DB 소스/액션 노드) │ └──────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ ExternalDbConnectionPoolService │ │ (싱글톤 인스턴스) │ │ │ │ ┌─────────────────────────────────┐ │ │ │ Connection Pool Map │ │ │ │ ┌──────────────────────────┐ │ │ │ │ │ ID: 1 → PostgresPool │ │ │ │ │ │ ID: 2 → MySQLPool │ │ │ │ │ │ ID: 3 → MariaDBPool │ │ │ │ │ └──────────────────────────┘ │ │ │ └─────────────────────────────────┘ │ │ │ │ - 자동 풀 생성/제거 │ │ - 헬스 체크 (1분마다) │ │ - 유휴 풀 정리 (10분) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ External Databases │ │ - PostgreSQL │ │ - MySQL │ │ - MariaDB │ └─────────────────────────────────────────┘ ``` --- ## 🔧 연결 풀 설정 ### PostgreSQL 연결 풀 ```typescript { max: 10, // 최대 연결 수 min: 2, // 최소 연결 수 idleTimeoutMillis: 30000, // 30초 유휴 시 연결 해제 connectionTimeoutMillis: 30000, // 연결 타임아웃 30초 statement_timeout: 60000, // 쿼리 타임아웃 60초 } ``` ### MySQL/MariaDB 연결 풀 ```typescript { connectionLimit: 10, // 최대 연결 수 waitForConnections: true, queueLimit: 0, // 대기열 무제한 connectTimeout: 30000, // 연결 타임아웃 30초 } ``` --- ## 📊 연결 풀 라이프사이클 ### 1. 풀 생성 ```typescript // 첫 요청 시 자동 생성 const pool = await poolService.getPool(connectionId); ``` **생성 시점**: - 외부 DB 소스 노드 첫 실행 시 - 외부 DB 액션 노드 첫 실행 시 **생성 과정**: 1. DB 연결 정보 조회 (`external_db_connections` 테이블) 2. 비밀번호 복호화 3. DB 타입에 맞는 연결 풀 생성 (PostgreSQL, MySQL, MariaDB) 4. 이벤트 리스너 등록 (연결 획득/해제 추적) ### 2. 풀 재사용 ```typescript // 기존 풀이 있으면 재사용 if (this.pools.has(connectionId)) { const pool = this.pools.get(connectionId)!; pool.lastUsedAt = new Date(); // 사용 시간 갱신 return pool; } ``` **재사용 조건**: - 동일한 `connectionId`로 요청 - 풀이 정상 상태 (`isHealthy()` 통과) ### 3. 자동 정리 **유휴 시간 초과 (10분)**: ```typescript const IDLE_TIMEOUT = 10 * 60 * 1000; // 10분 if (now - pool.lastUsedAt.getTime() > IDLE_TIMEOUT) { await this.removePool(connectionId); } ``` **헬스 체크 실패**: ```typescript if (!pool.isHealthy()) { await this.removePool(connectionId); } ``` --- ## 🔍 헬스 체크 시스템 ### 주기적 헬스 체크 ```typescript const HEALTH_CHECK_INTERVAL = 60 * 1000; // 1분마다 setInterval(() => { this.pools.forEach(async (pool, connectionId) => { // 유휴 시간 체크 const idleTime = now - pool.lastUsedAt.getTime(); if (idleTime > IDLE_TIMEOUT) { await this.removePool(connectionId); } // 헬스 체크 if (!pool.isHealthy()) { await this.removePool(connectionId); } }); }, HEALTH_CHECK_INTERVAL); ``` ### 헬스 체크 조건 #### PostgreSQL ```typescript isHealthy(): boolean { return this.pool.totalCount > 0 && this.activeConnections < this.maxConnections; } ``` #### MySQL/MariaDB ```typescript isHealthy(): boolean { return this.activeConnections < this.maxConnections; } ``` --- ## 💻 사용 방법 ### 1. 외부 DB 소스 노드에서 사용 ```typescript // nodeFlowExecutionService.ts private static async executeExternalDBSource( node: FlowNode, context: ExecutionContext ): Promise { const { connectionId, tableName } = node.data; // 연결 풀 서비스 사용 const { ExternalDbConnectionPoolService } = await import( "./externalDbConnectionPoolService" ); const poolService = ExternalDbConnectionPoolService.getInstance(); const sql = `SELECT * FROM ${tableName}`; const result = await poolService.executeQuery(connectionId, sql); return result; } ``` ### 2. 외부 DB 액션 노드에서 사용 ```typescript // 기존 createExternalConnector가 자동으로 연결 풀 사용 const connector = await this.createExternalConnector(connectionId, dbType); // executeQuery 호출 시 내부적으로 연결 풀 사용 const result = await connector.executeQuery(sql, params); ``` ### 3. 연결 풀 상태 조회 **API 엔드포인트**: ``` GET /api/external-db-connections/pool-status ``` **응답 예시**: ```json { "success": true, "data": { "totalPools": 3, "activePools": 2, "pools": [ { "connectionId": 1, "dbType": "postgresql", "activeConnections": 2, "maxConnections": 10, "createdAt": "2025-01-13T10:00:00.000Z", "lastUsedAt": "2025-01-13T10:05:00.000Z", "idleSeconds": 45 }, { "connectionId": 2, "dbType": "mysql", "activeConnections": 0, "maxConnections": 10, "createdAt": "2025-01-13T09:50:00.000Z", "lastUsedAt": "2025-01-13T09:55:00.000Z", "idleSeconds": 600 } ] }, "message": "3개의 연결 풀 상태를 조회했습니다." } ``` --- ## 🚨 연결 풀 고갈 방지 메커니즘 ### 1. 최대 연결 수 제한 ```typescript // 데이터베이스 설정 기준 max_connections: config.max_connections || 10; ``` 각 외부 DB 연결마다 최대 연결 수를 설정하여 무제한 연결 방지. ### 2. 연결 재사용 ```typescript // 동일한 connectionId 요청 시 기존 풀 재사용 const pool = await poolService.getPool(connectionId); ``` 매번 새 연결을 생성하지 않고 기존 풀 재사용. ### 3. 자동 연결 해제 ```typescript // PostgreSQL: 30초 유휴 시 자동 해제 idleTimeoutMillis: 30000; ``` 사용되지 않는 연결은 자동으로 해제하여 리소스 절약. ### 4. 전역 풀 정리 ```typescript // 10분 이상 미사용 풀 제거 if (idleTime > IDLE_TIMEOUT) { await this.removePool(connectionId); } ``` 장시간 사용되지 않는 풀 자체를 제거. ### 5. 애플리케이션 종료 시 정리 ```typescript process.on("SIGINT", async () => { await ExternalDbConnectionPoolService.getInstance().closeAll(); process.exit(0); }); ``` 프로세스 종료 시 모든 연결 정상 종료. --- ## 📈 모니터링 및 로깅 ### 연결 이벤트 로깅 ```typescript // 연결 획득 pool.on("acquire", () => { logger.debug(`[PostgreSQL] 연결 획득 (2/10)`); }); // 연결 반환 pool.on("release", () => { logger.debug(`[PostgreSQL] 연결 반환 (1/10)`); }); // 에러 발생 pool.on("error", (err) => { logger.error(`[PostgreSQL] 연결 풀 오류:`, err); }); ``` ### 정기 상태 로깅 ```typescript // 1분마다 상태 출력 logger.debug(`📊 연결 풀 상태: 총 3개, 활성: 2개`); ``` ### 주요 로그 메시지 | 레벨 | 메시지 | 의미 | | ------- | ---------------------------------------------------------- | --------------- | | `info` | `🔧 새 연결 풀 생성 중 (ID: 1)...` | 새 풀 생성 시작 | | `info` | `✅ 연결 풀 생성 완료 (ID: 1, 타입: postgresql, 최대: 10)` | 풀 생성 완료 | | `debug` | `✅ 기존 연결 풀 재사용 (ID: 1)` | 기존 풀 재사용 | | `info` | `🧹 유휴 연결 풀 정리 (ID: 2, 유휴: 620초)` | 유휴 풀 제거 | | `warn` | `⚠️ 연결 풀 비정상 감지 (ID: 3), 재생성 중...` | 헬스 체크 실패 | | `error` | `❌ 쿼리 실행 실패 (ID: 1)` | 쿼리 오류 | --- ## 🔒 보안 고려사항 ### 1. 비밀번호 보호 ```typescript // 비밀번호 복호화는 풀 생성 시에만 수행 config.password = PasswordEncryption.decrypt(config.password); ``` 메모리에 평문 비밀번호를 최소한으로 유지. ### 2. 연결 정보 검증 ```typescript if (config.is_active !== "Y") { throw new Error(`비활성화된 연결입니다 (ID: ${connectionId})`); } ``` 비활성화된 연결은 사용 불가. ### 3. 타임아웃 설정 ```typescript connectionTimeoutMillis: 30000, // 30초 statement_timeout: 60000, // 60초 ``` 무한 대기 방지. --- ## 🐛 트러블슈팅 ### 문제 1: 연결 풀 고갈 **증상**: "Connection pool exhausted" 오류 **원인**: - 동시 요청이 최대 연결 수 초과 - 쿼리가 너무 오래 실행되어 연결 점유 **해결**: 1. `max_connections` 값 증가 (`external_db_connections` 테이블) 2. 쿼리 최적화 (인덱스, LIMIT 추가) 3. `query_timeout` 값 조정 ### 문제 2: 메모리 누수 **증상**: 메모리 사용량 지속 증가 **원인**: - 연결 풀이 정리되지 않음 - 헬스 체크 실패 **해결**: 1. 연결 풀 상태 확인: `GET /api/external-db-connections/pool-status` 2. 수동 재시작으로 모든 풀 정리 3. 로그에서 `🧹 유휴 연결 풀 정리` 메시지 확인 ### 문제 3: 연결 시간 초과 **증상**: "Connection timeout" 오류 **원인**: - DB 서버 응답 없음 - 네트워크 문제 - 방화벽 차단 **해결**: 1. DB 서버 상태 확인 2. 네트워크 연결 확인 3. `connection_timeout` 값 증가 --- ## ⚙️ 설정 권장사항 ### 소규모 시스템 (동시 사용자 < 50) ```typescript { max_connections: 5, connection_timeout: 30, query_timeout: 60, } ``` ### 중규모 시스템 (동시 사용자 50-200) ```typescript { max_connections: 10, connection_timeout: 30, query_timeout: 90, } ``` ### 대규모 시스템 (동시 사용자 > 200) ```typescript { max_connections: 20, connection_timeout: 60, query_timeout: 120, } ``` --- ## 📚 참고 자료 - [PostgreSQL Connection Pooling](https://node-postgres.com/features/pooling) - [MySQL Connection Pool](https://github.com/mysqljs/mysql#pooling-connections) - [Node.js Best Practices - Database Connection Management](https://github.com/goldbergyoni/nodebestpractices) --- ## 🎯 결론 외부 DB 연결 풀 서비스는 다음을 보장합니다: ✅ **효율성**: 연결 재사용으로 성능 향상 ✅ **안정성**: 연결 풀 고갈 방지 ✅ **자동화**: 생성/정리/모니터링 자동화 ✅ **확장성**: 다중 DB 및 대규모 트래픽 지원 **최소한의 설정**으로 **최대한의 안정성**을 제공합니다! 🚀