개발일기장

Connection 을 왜 더 이상 끌어오지 못하는 걸까 (GenericObjectPool) 본문

JAVA

Connection 을 왜 더 이상 끌어오지 못하는 걸까 (GenericObjectPool)

게슬 2024. 9. 16. 12:23
728x90

연휴라서 시간이 남는다.

이것저것 하느라 바빠서 이제서야 고민해볼 시간이 생긴것 같다..

 

아직도 의문인게 왜 회사에서 직접 프레임워크를 만들어쓰는지다. 뭐 특별한게 있으면 모르겠는데 몇몇 부분 빼면 죄다 Springboot에서 구현이 가능함


Application에서 DB접속 관련해서도 라이브러리를 그냥 사용하지 않고 상속받아서 커스텀을 한다. 의미있는지는 모르겠는데 모니터링 용도로 그렇게 만든 것 같다.

 

DataSource를 상속받아서 직접 만들고, Apache Common Pool을 가져다가 GenericObjectPool을 이용해서 커스텀이 되어있는 상태다.

 

회사소스를 가지고 올 수 없기 때문에 대충 Copilot한태 만들라고 했다. 

Custom GenericObjectPool 구현

import org.apache.commons.pool2.impl.GenericObjectPool;
import java.sql.Connection;

public class CustomConnectionPool extends GenericObjectPool<Connection> {
    public CustomConnectionPool(ConnectionFactory factory, GenericObjectPoolConfig<Connection> config) {
        super(factory, config);
    }

    @Override
    public Connection borrowObject() throws Exception {
	Object obj = super.borrowObject();
        if (obj instanceof Connection) {
            conn = (Connection) obj;
            // CustomDataSource로 변환하여 특정 작업 
        }
        return obj;
    }

    @Override
    public void returnObject(Connection conn) {
        // CustomDataSource로 변환하여 특정 작업 수행
        super.returnObject(conn);
    }

}

일단 의심되는 부분에 대해서 물어보자.

1. Custom 한 영역에서 경합이나 Exception 처리를 제대로 하지 못해서 문제가 발생한 것일까?

2. Connection을 획득하는 시점에서 DeadLock이 걸릴 수 있을까?


1. Custom 한 영역에서 Exception 처리를 제대로 하지 못해서 문제가 발생한 것일까?

커스텀 영역에서 문제가 발생했을 때 문제가 되지 않는지에 대해서 물어보니깐 예외처리 코드를 만들어준다.

예외 처리 예시

import org.apache.commons.pool2.impl.GenericObjectPool;
import java.sql.Connection;

public class CustomConnectionPool extends GenericObjectPool<Connection> {
    public CustomConnectionPool(ConnectionFactory factory, GenericObjectPoolConfig<Connection> config) {
        super(factory, config);
    }

    @Override
    public Connection borrowObject() throws Exception {
        Connection conn = null;
        try {
            Object obj = super.borrowObject();
            if (obj instanceof Connection) {
                conn = (Connection) obj;
                // CustomDataSource로 변환하여 특정 작업 수행
                initializeConnection(conn);
            }
            return conn;
        } catch (Exception e) {
            if (conn != null) {
                super.returnObject(conn); // 예외 발생 시 연결 반환
            }
            System.err.println("Failed to borrow a connection: " + e.getMessage());
            throw e;
        }
    }

    @Override
    public void returnObject(Connection conn) {
        try {
            // CustomDataSource로 변환하여 특정 작업 수행
            cleanupConnection(conn);
            super.returnObject(conn);
        } catch (Exception e) {
            System.err.println("Failed to return a connection: " + e.getMessage());
            try {
                super.invalidateObject(conn); // 예외 발생 시 연결 무효화
            } catch (Exception ex) {
                System.err.println("Failed to invalidate the connection: " + ex.getMessage());
            }
        }
    }

    private void initializeConnection(Connection conn) {
        // 커넥션 초기화 로직
    }

    private void cleanupConnection(Connection conn) {
        // 커넥션 정리 로직
    }
}

커스텀 영역에서 Exception 발생 시 예외처리를 할 때

1-1. borrowObject에서 Exception 발생 시 그냥 obejct를 return 해준다.

1-2. returnObject에서는 Exception 발생 시 connection 을 정리는 해준다.

 

경합에 대해서 물어보니깐 아래되 같은 답변을 해준다.

>> GenericObjectPool 자체가 스레드 안전성을 제공하지만, 커스텀 로직을 추가할 때는 동기화를 통해 경합을 방지할 수 있습니다.

그러면서 borrowObject와 returnObject에 synchronized를 붙여서 안전하게 사용하라고 한다. 나도 요부분이 문제같긴 한대 이거는 직접 로그 찍어서 확인을 하지 않으면 확신 할 수 없을 것 같다.

 

1에 대한 결론 

> 일단 try - catch 달고 borrow해오는 connection의 hash값에 로그를 찍어서 문제지점을 확인해보자

> 이후에 예외처리로직 넣고

> 문제가 없으면 동시성 문제도 해결하자.


2. Connection을 획득하는 시점에서 DeadLock이 걸릴 수 있을까?

이부분에 대해서 물어보니깐 이렇게 답변을 해준다.

예시

borrowObject와 returnObject 메서드에서 경합 처리를 하지 않으면, 다음과 같은 상황이 발생할 수 있습니다:

  • 스레드 A가 borrowObject를 호출하여 커넥션을 빌리고, 특정 작업을 수행하는 동안 스레드 B가 returnObject를 호출하여 커넥션을 반환하려고 합니다.
  • 스레드 A가 작업을 완료하지 못하고 예외가 발생하면, 스레드 B는 커넥션을 반환하지 못하고 대기 상태에 빠질 수 있습니다.
  • 이 상태가 지속되면, 다른 스레드들도 동일한 리소스를 기다리게 되어 데드락이 발생할 수 있습니다.

해결 방법

  1. 동기화: synchronized 키워드를 사용하여 메서드나 블록을 동기화합니다.
  2. 타임아웃: 특정 시간 내에 작업이 완료되지 않으면 예외를 발생시켜 데드락을 방지합니다.
  3. 데드락 회피 알고리즘: 리소스 할당 그래프를 사용하여 데드락을 회피하는 알고리즘을 구현합니다.

예시만 들어보면 그럴듯한대 이게 또 생성형AI가 환각? 뭐시기랍시고 또 지들이 모르면 이것저것 헛소리 한다고 들어서 신뢰하기가 힘들지만 뭐 방법이 있나? 내가 부족하니 이런거라도 믿고 테스트해봐야지.

 

결론

Quartz를 이용해서 동시에 여러개의 Transaction이 생성되고 Connection을 획득 / 반환하는 시점에 문제가 발생할것이라고 예상은 하고 있는데 직접 로그를 찍어서 확인하지 않으면 안될 것 같다.

그리고 GenericObjectPool에 구현되어있는 borrow / return 로직에 대해서 한번 더 확인을 해봐야할 것 같다. 당연히 예외처리는 되어 있겠지만 그걸 super로 호출해서 그대로 사용하는거니깐 문제 발생하면 바로 위에까지 터지는게 맞지

해결이 되었으면 좋겠다!

 

728x90
Comments