제어관리 노드 작동 방식 수정
This commit is contained in:
491
docs/외부_DB_연결_풀_가이드.md
Normal file
491
docs/외부_DB_연결_풀_가이드.md
Normal file
@@ -0,0 +1,491 @@
|
||||
# 외부 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<any[]> {
|
||||
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 및 대규모 트래픽 지원
|
||||
|
||||
**최소한의 설정**으로 **최대한의 안정성**을 제공합니다! 🚀
|
||||
Reference in New Issue
Block a user