Apache Commons Pool2를 이용한 중량급 객체 풀링 구현

시스템 개발 시 생성 비용이 큰 '중량급 객체'를 빈번하게 만들고 파괴하는 과정은 성능 저하의 주요 원인이 됩니다. 객체 풀링(Object Pooling) 기법은 초기화 단계에서 일정 수의 객체를 미리 생성해 두고, 필요할 때마다 풀에서 객체를 빌려(Borrow) 사용한 뒤 다시 반납(Return)하는 방식을 취합니다. 이를 통해 객체 생성 오버헤드를 줄이고 시스템 리소스를 효율적으로 관리할 수 있습니다.

1. 의존성 추가

Apache Commons Pool2 라이브러리를 사용하기 위해 pom.xml에 다음과 같은 의존성을 추가합니다.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

2. 풀링 대상 객체 정의

풀에서 관리할 대상인 중량급 리소스 클래스를 정의합니다. 여기서는 ManagedResource라는 이름으로 정의하겠습니다.

public class ManagedResource {
    private String resourceId;
    private boolean available;

    public ManagedResource(String resourceId) {
        this.resourceId = resourceId;
        this.available = true;
    }

    public String getResourceId() {
        return resourceId;
    }

    public boolean isAvailable() {
        return available;
    }

    public void setAvailable(boolean available) {
        this.available = available;
    }

    public void clear() {
        // 리소스 상태 초기화 로직
    }
}

3. 객체 팩토리 구현

풀에서 객체의 생성, 검증, 파괴 등을 담당할 팩토리 클래스가 필요합니다. BasePooledObjectFactory를 상속받아 구현하는 것이 일반적입니다.

import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;

public class ResourceFactory extends BasePooledObjectFactory<ManagedResource> {

    @Override
    public ManagedResource create() throws Exception {
        // 실제 객체 생성 로직
        return new ManagedResource("Res-" + System.nanoTime());
    }

    @Override
    public PooledObject<ManagedResource> wrap(ManagedResource obj) {
        // 생성된 객체를 PooledObject로 감싸기
        return new DefaultPooledObject<>(obj);
    }

    @Override
    public void destroyObject(PooledObject<ManagedResource> p) throws Exception {
        p.getObject().clear();
    }

    @Override
    public boolean validateObject(PooledObject<ManagedResource> p) {
        return p.getObject().isAvailable();
    }
}

4. 커스텀 객체 풀 정의

GenericObjectPool을 확장하여 특정 타입에 맞는 풀을 구성합니다.

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class ManagedResourcePool extends GenericObjectPool<ManagedResource> {
    public ManagedResourcePool(ResourceFactory factory, GenericObjectPoolConfig<ManagedResource> config) {
        super(factory, config);
    }
}

5. Spring Boot 연동 및 설정

외부 설정 파일(application.yml 등)로부터 설정을 주입받기 위한 프로퍼티 클래스와 자동 설정 빈을 구성합니다.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "app.resource-pool")
public class PoolSettings {
    private int maxTotal = 10;
    private int maxIdle = 5;
    private int minIdle = 1;
    private int initialSize = 2;

    // Getter, Setter 생략
}

다음은 빈 설정을 담당하는 구성 클래스입니다. Spring Boot 환경에서 JMX 충돌을 방지하기 위해 setJmxEnabled(false) 설정이 권장됩니다.

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;

@Configuration
public class ResourcePoolConfig {

    private ManagedResourcePool resourcePool;

    @Bean
    public ManagedResourcePool managedResourcePool(PoolSettings settings) {
        ResourceFactory factory = new ResourceFactory();
        
        GenericObjectPoolConfig<ManagedResource> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(settings.getMaxTotal());
        config.setMaxIdle(settings.getMaxIdle());
        config.setMinIdle(settings.getMinIdle());
        config.setTestOnBorrow(true);
        config.setJmxEnabled(false); // 중요: Spring Boot JMX 중복 등록 방지

        this.resourcePool = new ManagedResourcePool(factory, config);
        
        // 초기 객체 로딩
        try {
            for (int i = 0; i < settings.getInitialSize(); i++) {
                resourcePool.addObject();
            }
        } catch (Exception ignored) {}

        return resourcePool;
    }

    @PreDestroy
    public void closePool() {
        if (resourcePool != null) {
            resourcePool.close();
        }
    }
}

6. 풀 활용 예제

서비스 레이어에서 풀을 주입받아 객체를 대여하고 반납하는 기본적인 사용법입니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BusinessService {

    @Autowired
    private ManagedResourcePool resourcePool;

    public void processTask() {
        ManagedResource resource = null;
        try {
            // 풀에서 객체 대여
            resource = resourcePool.borrowObject();
            
            // 비즈니스 로직 수행
            System.out.println("Using resource: " + resource.getResourceId());
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (resource != null) {
                // 사용 완료 후 반드시 풀에 반납
                resourcePool.returnObject(resource);
            }
        }
    }
}

Apache Commons Pool2의 주요 파라미터는 다음과 같습니다.

  • maxTotal: 풀에서 관리하는 최대 객체 수
  • maxIdle: 풀에 보관할 수 있는 최대 유휴 상태 객체 수
  • minIdle: 풀에서 유지해야 하는 최소 유휴 상태 객체 수
  • testOnBorrow: 객체를 빌릴 때 유효성 검사 여부
  • blockWhenExhausted: 풀이 가득 찼을 때 대기 여부

태그: commons-pool2 spring-boot java object-pool Optimization

7월 31일 03:11에 게시됨