커넥션 풀의 “내부 풀링(Internal Pooling)“은 애플리케이션 내부에서 직접 연결 풀을 관리하는 방식으로, 주로 데이터베이스 드라이버나 ORM(Object-Relational Mapping) 프레임워크에 내장된 기능을 통해 구현된다. 이 방식은 외부 의존성 없이 애플리케이션 자체에서 연결 재사용을 최적화한다.
내부 풀링은 간편성과 통합성을 중시하는 환경에 적합하다. ORM이나 데이터베이스 드라이버에 내장된 기능을 활용해 빠르게 구현할 수 있으나, 대규모 분산 시스템에서는 연결 관리의 비효율성이 발생할 수 있다. Hibernate의 C3P0 통합이나 MySQL Connector/J의 자체 풀링이 대표적 예시이며, minSize와 maxSize를 트래픽 패턴에 맞게 조정하는 것이 성능 개선의 핵심이다.
// Hibernate 설정 파일 (hibernate.cfg.xml)<propertyname="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property><propertyname="hibernate.c3p0.min_size">5</property><propertyname="hibernate.c3p0.max_size">50</property><propertyname="hibernate.c3p0.timeout">300</property><propertyname="hibernate.c3p0.idle_test_period">150</property>
# SQLAlchemy 내장 풀 구현 예시fromsqlalchemyimportcreate_enginefromsqlalchemy.ormimportsessionmakerclassDatabaseManager:def__init__(self):# 엔진 생성 및 풀 설정self.engine=create_engine('mysql://user:password@localhost/mydb',pool_size=5,# 초기 풀 크기max_overflow=10,# 추가 생성 가능한 최대 연결 수pool_timeout=30,# 연결 대기 시간pool_recycle=3600,# 연결 재사용 주기echo=True# SQL 로깅 활성화)# 세션 팩토리 생성self.Session=sessionmaker(bind=self.engine)defget_session(self):returnself.Session()
// Sequelize 내장 풀 구현 예시
const{Sequelize}=require('sequelize');classSequelizeManager{constructor(){this.sequelize=newSequelize('mydb','user','password',{host:'localhost',dialect:'mysql',// 풀 설정
pool:{max:5,// 최대 연결 수
min:0,// 최소 연결 수
acquire:30000,// 연결 획득 타임아웃
idle:10000// 유휴 시간
}});}asynctestConnection(){try{awaitthis.sequelize.authenticate();console.log('데이터베이스 연결 성공');}catch(error){console.error('데이터베이스 연결 실패:',error);}}}
외부 풀링(External Pooling)은 데이터베이스 연결 관리를 위해 독립적인 라이브러리나 미들웨어를 활용하는 방식으로, 애플리케이션 코드나 서버 인프라와 분리되어 유연성과 성능을 균형 있게 제공한다. 내부 풀링이나 컨테이너 관리 방식과 달리 특정 환경에 종속되지 않으며, 다양한 애플리케이션 환경에 적용 가능하다.
외부 풀링은 복잡한 엔터프라이즈 환경에서 연결 관리의 효율성을 극대화하는 최적의 선택이다. HikariCP와 같은 라이브러리는 검증된 성능으로 Spring Boot 2.0+에서 기본 채택되었으며, maxPoolSize와 idleTimeout을 서버 부하에 맞게 동적으로 조절하는 것이 성능 향상의 핵심이다. 다만, 초기 설정과 모니터링을 통해 연결 누수나 과도한 풀 크기로 인한 자원 낭비를 방지해야 한다.
// HikariCP 설정HikariConfigconfig=newHikariConfig();config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");config.setUsername("user");config.setPassword("password");config.setMaximumPoolSize(20);// 최대 연결 수config.setMinimumIdle(5);// 최소 유휴 연결 수config.setIdleTimeout(30000);// 유휴 연결 유지 시간(30초)config.setConnectionTestQuery("SELECT 1");// 검증 쿼리HikariDataSourcedataSource=newHikariDataSource(config);// 연결 사용try(Connectionconn=dataSource.getConnection()){// 데이터베이스 작업 수행}
<!-- context.xml --><Resourcename="jdbc/myDB"auth="Container"type="javax.sql.DataSource"driverClassName="com.mysql.cj.jdbc.Driver"url="jdbc:mysql://localhost:3306/mydb"username="user"password="password"maxTotal="100"<!--최대연결수--> maxIdle="20" <!-- 유휴 연결 최대 수 --> minIdle="5" <!-- 유휴 연결 최소 수 --> maxWaitMillis="30000" <!-- 연결 대기 시간(30초) --> validationQuery="SELECT 1"
testOnBorrow="true" <!-- 사용 전 연결 검증 -->/>
- 서버 통합 관리: WAS의 모니터링 도구와 연동해 실시간 상태 추적 가능 - 간편한 설정: XML/관리 콘솔에서 중앙 집중식 설정 - 안정성: WAS의 안정성 보장 메커니즘(예: 장애 시 자동 재연결) 적용 - 성능 최적화: 서버 레벨에서 연결 풀을 최적화하여 고성능 유지
단점
- WAS 종속성: 서버 변경 시 설정 재구성 필요 - 유연성 부족: 외부 라이브러리(HikariCP 등)보다 고급 기능 제한적 - 확장성 제한: 클라우드 환경에서의 탄력적 확장에 어려움
importthreadingimportqueueimporttimeimportmysql.connectorfromtypingimportOptional,Listfromdataclassesimportdataclassimportlogging@dataclassclassPooledConnection:connection:mysql.connector.MySQLConnectionlast_used:floatcreated_at:floatis_busy:bool=FalseclassConnectionPool:def__init__(self,host:str,user:str,password:str,database:str,min_connections:int=5,max_connections:int=10,connection_timeout:int=30,idle_timeout:int=300,validation_interval:int=60):# 기본 설정 초기화self.db_config={"host":host,"user":user,"password":password,"database":database}self.min_connections=min_connectionsself.max_connections=max_connectionsself.connection_timeout=connection_timeoutself.idle_timeout=idle_timeoutself.validation_interval=validation_interval# 연결 풀 및 잠금 장치 초기화self.pool:queue.Queue=queue.Queue()self.active_connections:List[PooledConnection]=[]self.lock=threading.Lock()self.last_validation=time.time()# 모니터링을 위한 메트릭 초기화self.metrics={"total_connections":0,"active_connections":0,"waiting_requests":0}# 로깅 설정self.logger=logging.getLogger(__name__)# 초기 연결 생성self._initialize_pool()def_initialize_pool(self):"""초기 연결 풀 생성"""for_inrange(self.min_connections):self._add_connection()def_add_connection(self)->Optional[PooledConnection]:"""새로운 데이터베이스 연결 생성"""try:connection=mysql.connector.connect(**self.db_config)pooled_conn=PooledConnection(connection=connection,last_used=time.time(),created_at=time.time())self.pool.put(pooled_conn)self.active_connections.append(pooled_conn)self.metrics["total_connections"]+=1returnpooled_connexceptExceptionase:self.logger.error(f"연결 생성 실패: {str(e)}")returnNonedef_validate_connection(self,conn:PooledConnection)->bool:"""연결 유효성 검사"""try:conn.connection.ping(reconnect=True)returnTrueexcept:returnFalsedefget_connection(self)->Optional[PooledConnection]:"""풀에서 연결 획득"""self.metrics["waiting_requests"]+=1start_time=time.time()# 연결 검증 주기 확인iftime.time()-self.last_validation>self.validation_interval:self._validate_all_connections()whileTrue:try:conn=self.pool.get(timeout=self.connection_timeout)# 연결 상태 검증ifnotself._validate_connection(conn):self._remove_connection(conn)iflen(self.active_connections)<self.max_connections:self._add_connection()continueconn.is_busy=Trueconn.last_used=time.time()self.metrics["active_connections"]+=1self.metrics["waiting_requests"]-=1returnconnexceptqueue.Empty:# 타임아웃 발생 시 새 연결 생성 시도iflen(self.active_connections)<self.max_connections:new_conn=self._add_connection()ifnew_conn:continueiftime.time()-start_time>self.connection_timeout:self.logger.error("연결 획득 타임아웃")self.metrics["waiting_requests"]-=1returnNonedefrelease_connection(self,conn:PooledConnection):"""연결 반환"""conn.is_busy=Falseself.metrics["active_connections"]-=1self.pool.put(conn)def_validate_all_connections(self):"""모든 연결의 유효성 검사"""withself.lock:self.last_validation=time.time()forconninself.active_connections[:]:ifnotself._validate_connection(conn):self._remove_connection(conn)def_remove_connection(self,conn:PooledConnection):"""손상된 연결 제거"""try:conn.connection.close()except:passself.active_connections.remove(conn)self.metrics["total_connections"]-=1defcleanup(self):"""오래된 유휴 연결 정리"""current_time=time.time()withself.lock:forconninself.active_connections[:]:if(notconn.is_busyandcurrent_time-conn.last_used>self.idle_timeoutandlen(self.active_connections)>self.min_connections):self._remove_connection(conn)defget_metrics(self):"""풀 상태 메트릭 반환"""return{**self.metrics,"pool_size":len(self.active_connections),"available_connections":self.pool.qsize()}
constmysql=require('mysql2/promise');constEventEmitter=require('events');classConnectionPoolextendsEventEmitter{constructor(config){super();// 기본 설정
this.config={host:config.host,user:config.user,password:config.password,database:config.database,minConnections:config.minConnections||5,maxConnections:config.maxConnections||10,connectionTimeout:config.connectionTimeout||30000,idleTimeout:config.idleTimeout||300000,validationInterval:config.validationInterval||60000};// 풀 상태 관리
this.pool=[];this.waiting=[];this.metrics={totalConnections:0,activeConnections:0,waitingRequests:0};// 초기화
this.initialize();// 주기적인 작업 설정
setInterval(()=>this.validateConnections(),this.config.validationInterval);setInterval(()=>this.cleanup(),this.config.idleTimeout);}asyncinitialize(){try{// 초기 연결 생성
for(leti=0;i<this.config.minConnections;i++){awaitthis.createConnection();}}catch(error){console.error('풀 초기화 실패:',error);throwerror;}}asynccreateConnection(){try{constconnection=awaitmysql.createConnection(this.config);// 연결 객체 래핑
constpooledConnection={connection,lastUsed:Date.now(),createdAt:Date.now(),isBusy:false};this.pool.push(pooledConnection);this.metrics.totalConnections++;// 연결 이벤트 처리
connection.on('error',async(error)=>{console.error('연결 오류:',error);awaitthis.removeConnection(pooledConnection);awaitthis.createConnection();});returnpooledConnection;}catch(error){console.error('연결 생성 실패:',error);throwerror;}}asyncgetConnection(){// 대기 요청 추가
this.metrics.waitingRequests++;try{// 사용 가능한 연결 찾기
constconnection=this.pool.find(conn=>!conn.isBusy);if(connection){returnthis.prepareConnection(connection);}// 새 연결 생성 가능 여부 확인
if(this.pool.length<this.config.maxConnections){constnewConnection=awaitthis.createConnection();returnthis.prepareConnection(newConnection);}// 연결 대기
returnnewPromise((resolve,reject)=>{consttimeout=setTimeout(()=>{this.waiting=this.waiting.filter(w=>w.resolve!==resolve);this.metrics.waitingRequests--;reject(newError('연결 타임아웃'));},this.config.connectionTimeout);this.waiting.push({resolve,reject,timeout});});}finally{this.metrics.waitingRequests--;}}asyncprepareConnection(pooledConnection){try{// 연결 유효성 검사
awaitthis.validateConnection(pooledConnection);pooledConnection.isBusy=true;pooledConnection.lastUsed=Date.now();this.metrics.activeConnections++;returnpooledConnection;}catch(error){awaitthis.removeConnection(pooledConnection);throwerror;}}asyncreleaseConnection(pooledConnection){pooledConnection.isBusy=false;pooledConnection.lastUsed=Date.now();this.metrics.activeConnections--;// 대기 중인 요청 처리
if(this.waiting.length>0){const{resolve,timeout}=this.waiting.shift();clearTimeout(timeout);resolve(awaitthis.prepareConnection(pooledConnection));}}asyncvalidateConnection(pooledConnection){try{awaitpooledConnection.connection.query('SELECT 1');returntrue;}catch(error){thrownewError('연결 검증 실패');}}asyncvalidateConnections(){console.log('연결 유효성 검사 시작');for(constconnectionofthis.pool){if(!connection.isBusy){try{awaitthis.validateConnection(connection);}catch(error){awaitthis.removeConnection(connection);}}}}asyncremoveConnection(pooledConnection){try{awaitpooledConnection.connection.end();}catch(error){console.error('연결 종료 실패:',error);}this.pool=this.pool.filter(conn=>conn!==pooledConnection);this.metrics.totalConnections--;// 최소 연결 수 유지
if(this.pool.length<this.config.minConnections){awaitthis.createConnection();}}asynccleanup(){constnow=Date.now();constidleConnections=this.pool.filter(conn=>!conn.isBusy&&(now-conn.lastUsed>this.config.idleTimeout)&&this.pool.length>this.config.minConnections);for(constconnectionofidleConnections){awaitthis.removeConnection(connection);}}getMetrics(){return{…this.metrics,poolSize:this.pool.length,availableConnections:this.pool.filter(conn=>!conn.isBusy).length};}}