-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DB Connection Pool 그것이 알고싶다 #12
Comments
1. DB ConnectionDB Connection Pool 에 대해 알아보기에 앞서, 간단히 DB Connection 에 대해 정리하고자 한다. private DBUtil util = DBUtil.getUtil();
@Override
public Product select(String num) throws SQLException {
Product prod = null;
String sql = "select * from product where num=?";
try(Connection con = util.getConnection();
PreparedStatement ps = con.prepareStatement(sql);){
ps.setString(1, num);
try(ResultSet rs = ps.executeQuery()){
if(rs.next()) {
prod = new Product(num,rs.getString(2),rs.getInt("price"));
}
}
}
return prod;
} 이 때, 사용자가 요청을 할 때마다 JDBC Driver 를 로드하고 Connection 객체를 받아오는 것은 귀찮을 뿐더러 매우 비효율적이다. Connection을 획득하는 과정은 아래의 순서로 진행되어 비용이 많이 드는 작업이기 때문이다.
2. DB Connection Pool(DBCP)이런 비효율적인 과정을 해결하기 위해 나온 개념이
웹 컨테이너(WAS)가 실행되면서 추가 장점
하지만 종류대표적인 DBCP 프레임워크는 아래와 같다.
이 중 가장 많이 사용되는 HikariCP 를 중점적으로 알아보고, 다른 프레임워크는 간단히 언급만 하려고 한다. 3. HikariCP
동작 방식
또한, 만약, 가능한 Thread는 내부 코드워낙 구현이 복잡하게 되어 있어서, 전체 코드가 궁금한 사람은 GitHub를 참고하면 좋을 것 같고 여기서는 몇가지 눈에 띄는 사항들만 정리하고자 한다. public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener
{
private final Logger logger = LoggerFactory.getLogger(HikariPool.class);
public static final int POOL_NORMAL = 0;
public static final int POOL_SUSPENDED = 1;
public static final int POOL_SHUTDOWN = 2;
public volatile int poolState;
private final long aliveBypassWindowMs = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", MILLISECONDS.toMillis(500));
private final long housekeepingPeriodMs = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
private static final String EVICTED_CONNECTION_MESSAGE = "(connection was evicted)";
private static final String DEAD_CONNECTION_MESSAGE = "(connection is dead)";
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator();
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
// Thread 가 pool을 이용하는 주체이므로 ThreadPool을 사용
private final ThreadPoolExecutor addConnectionExecutor;
private final ThreadPoolExecutor closeConnectionExecutor;
// 내부적으로 ConcurrentBag 사용
private final ConcurrentBag<PoolEntry> connectionBag;
private final ProxyLeakTaskFactory leakTaskFactory;
private final SuspendResumeLock suspendResumeLock;
private final ScheduledExecutorService houseKeepingExecutorService;
private ScheduledFuture<?> houseKeeperTask;
/**
* Construct a HikariPool with the specified configuration.
*
* @param config a HikariConfig instance
*/
public HikariPool(final HikariConfig config)
{
super(config);
this.connectionBag = new ConcurrentBag<>(this);
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
checkFailFast();
if (config.getMetricsTrackerFactory() != null) {
setMetricsTrackerFactory(config.getMetricsTrackerFactory());
}
else {
setMetricRegistry(config.getMetricRegistry());
}
setHealthCheckRegistry(config.getHealthCheckRegistry());
handleMBeans(this, true);
ThreadFactory threadFactory = config.getThreadFactory();
final int maxPoolSize = config.getMaximumPoolSize();
// BlockingQueue를 사용하여 관리
LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new CustomDiscardPolicy());
this.closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) {
addConnectionExecutor.setMaximumPoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
addConnectionExecutor.setCorePoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
final long startTime = currentTime();
while (elapsedMillis(startTime) < config.getInitializationFailTimeout() && getTotalConnections() < config.getMinimumIdle()) {
quietlySleep(MILLISECONDS.toMillis(100));
}
addConnectionExecutor.setCorePoolSize(1);
addConnectionExecutor.setMaximumPoolSize(1);
}
}
/**
* Get a connection from the pool, or timeout after connectionTimeout milliseconds.
*
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public Connection getConnection() throws SQLException
{
return getConnection(connectionTimeout);
}
/**
* Get a connection from the pool, or timeout after the specified number of milliseconds.
*
* @param hardTimeout the maximum time to wait for a connection from the pool
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public Connection getConnection(final long hardTimeout) throws SQLException
{
suspendResumeLock.acquire();
final var startTime = currentTime();
// TimeOut 이 존재하고, 이를 넘어가면 오류를 발생
try {
var timeout = hardTimeout;
do {
var poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final var now = currentTime();
if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && isConnectionDead(poolEntry.connection))) {
closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
timeout = hardTimeout - elapsedMillis(startTime);
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry));
}
} while (timeout > 0L);
metricsTracker.recordBorrowTimeoutStats(startTime);
throw createTimeoutException(startTime);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
}
finally {
suspendResumeLock.release();
}
}
// Thread의 작업을 종료시키는 shutdown 메소드에서 close() 구현
public synchronized void shutdown() throws InterruptedException
{
try {
poolState = POOL_SHUTDOWN;
if (addConnectionExecutor == null) { // pool never started
return;
}
logPoolState("Before shutdown ");
if (houseKeeperTask != null) {
houseKeeperTask.cancel(false);
houseKeeperTask = null;
}
softEvictConnections();
addConnectionExecutor.shutdown();
if (!addConnectionExecutor.awaitTermination(getLoginTimeout(), SECONDS)) {
logger.warn("Timed-out waiting for add connection executor to shutdown");
}
destroyHouseKeepingExecutorService();
connectionBag.close();
final var assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection assassinator",
config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
try {
final var start = currentTime();
do {
abortActiveConnections(assassinExecutor);
softEvictConnections();
} while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(10));
}
finally {
assassinExecutor.shutdown();
if (!assassinExecutor.awaitTermination(10L, SECONDS)) {
logger.warn("Timed-out waiting for connection assassin to shutdown");
}
}
shutdownNetworkTimeoutExecutor();
closeConnectionExecutor.shutdown();
if (!closeConnectionExecutor.awaitTermination(10L, SECONDS)) {
logger.warn("Timed-out waiting for close connection executor to shutdown");
}
}
finally {
logPoolState("After shutdown ");
handleMBeans(this, false);
metricsTracker.close();
}
}
} 속성
Deadlock
DBCP 를 사용할 때는
적정 사이즈위와 같은 상태를 방지하기 위해서는 적절한 Pool Size를 지정해야 한다.
또한,
thread 최대 개수가 10개고, 동시에 필요한 위의 공식은 단순히 참고용일 뿐, 애플리케이션에 따라 추가적인 기준이 필요하다. 이때, 4. 기타 DBCPApache Commons DBCPApache 에서 제공하는 대표적인 DBCP 프레임워크이다. 아래와 같이 4개의 속성이 대표적으로 존재한다.
가장 중요한 성능 요소는 Tomcat DBCPtomcat 에 내장되어 사용되고 있는 프레임워크이다. Apache Commons DBCP를 바탕으로 만들어져 있으며, Spring 2.0 하위 버전의 기본 DBCP이다. 참고자료https://velog.io/@mooh2jj/%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80Connection-pool%EC%9D%80-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B0%80 |
👍 문제
12장에서
DB Connection Pool
이라는 키워드가 나왔습니다. 저는 Spring Framework 를 사용할 때 자주본 키워드인데HikariCP
를 사용하는 것만 알고 있을 뿐 동작 방식에 대해서 자세히 알지 못하고 있습니다! 그래서DB Connection Pool
의 종류와 동작 방식에 대해서 설명해주시면 좋을 것 같습니다!키워드와 간단한 설명만 등장하였는데 자세히 알아보면 좋을 것 같고
Spring Framework
에서도 자주 확인할 수 있는 키워드여서 한번 자세히 공부하면 좋을 것 같아서 선정하였습니다!📺 관련 챕터 및 레퍼런스
🐳 비고
The text was updated successfully, but these errors were encountered: