1. 개요
Oracle DB에서 SQL 실행 중 "Recoverable Exception"이 발생하는 경우가 있습니다. 이 오류는 주로 "시스템 Noise" 문제로 발생하며, 이는 운영 체제(OS)가 바쁜 상태에서 필요한 난수를 충분히 빠르게 생성하지 못할 때 발생할 수 있습니다. 특히, /dev/random 디바이스는 높은 품질의 엔트로피(entropy) 생성이 요구되는 경우 사용되는데, 이때 OS의 바쁜 상태로 인해 난수 생성이 지연되면 Oracle SQL이 데이터를 암호화하지 못하고 실패하는 현상이 발생합니다.
이 문제를 해결하기 위해 일반적으로 다음과 같은 방법을 사용합니다:
2. 내용
- Recoverable Exception 문제의 원인
- Oracle DB에서 SQL 실행 시, 난수 생성이 필요한 암호화 작업이 포함될 수 있습니다.
- 리눅스 OS의 /dev/random은 블록(block) 상태에서 높은 품질의 엔트로피를 생성하지만, 시스템이 바쁘거나 충분한 엔트로피가 부족할 경우, 난수 생성이 지연됩니다.
- 이로 인해 Oracle DB의 암호화 작업이 지연되거나 실패하면서 "Recoverable Exception" 오류가 발생할 수 있습니다.
- Linux의 /dev/random과 /dev/urandom 차이
- /dev/random: 블록(blocking) 디바이스로, 충분한 엔트로피가 모일 때까지 대기합니다. 보안성이 높은 난수를 생성하지만, 엔트로피가 부족한 경우 난수 생성이 지연될 수 있습니다.
- /dev/urandom: 논블록(non-blocking) 디바이스로, 엔트로피가 충분하지 않은 경우에도 내부 상태를 기반으로 난수를 계속 생성합니다. 속도가 빠르고, 엔트로피가 부족할 때에도 사용 가능하지만, 보안성이 /dev/random에 비해 상대적으로 낮을 수 있습니다.
- 문제 해결 방안
- /dev/urandom 사용 설정: Oracle DB가 사용하는 난수 생성 소스를 /dev/random에서 /dev/urandom으로 변경합니다. 이를 통해 난수 생성 지연 문제를 완화할 수 있습니다.
- JAVA 기반의 애플리케이션 서버에서 실행되는 Oracle JDBC 드라이버에 대해, -Djava.security.egd=file:/dev/./urandom 옵션을 설정하여 난수 생성 소스를 /dev/urandom으로 변경합니다.
- 엔트로피 풀(entropy pool) 크기 증가: 시스템의 엔트로피 소스를 추가하여 /dev/random의 엔트로피 풀 크기를 늘립니다. 이를 통해 난수 생성 지연 문제를 개선할 수 있습니다.
- OS 성능 최적화: 서버의 성능을 최적화하여 시스템이 바쁜 상태를 최소화합니다. 예를 들어, 불필요한 프로세스를 제거하거나, 시스템 자원의 사용을 조절하여 난수 생성이 지연되지 않도록 합니다.
- /dev/urandom 사용 설정: Oracle DB가 사용하는 난수 생성 소스를 /dev/random에서 /dev/urandom으로 변경합니다. 이를 통해 난수 생성 지연 문제를 완화할 수 있습니다.
- 결론
- "Recoverable Exception" 오류는 난수 생성 지연으로 인해 발생하는 문제로, /dev/random과 /dev/urandom의 차이를 이해하고 적절히 사용하는 것이 중요합니다. 특히, Oracle DB와 같은 데이터베이스 환경에서는 보안성과 성능 간의 균형을 고려하여 난수 생성 소스를 선택해야 하며, 이를 통해 시스템 성능을 최적화하고 예외 발생을 최소화할 수 있습니다.
3. 조치 방법
1) JAVA.OPTS 에 다음을 추가 한다.
-Djava.security.egd=file:/dev/urandom
2) 테스트 코드 *용도에 맞게 변경하여, 테스트 한다.
package kr.co.drsoft;
import org.apache.commons.dbcp2.BasicDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
public class TestBasicDataSource extends Thread {
private int LIMIT_COUNT = 100;
private static BasicDataSource sharedDataSource;
private BasicDataSource thBasicDataSource;
static {
/*
* oracle.jdbc.driver.OracleDriver\
* oracle.jdbc.OracleDriver
* */
/**********************************************************************
* 기본 계정 정보 및 설정 정보를 입력 합니다.
*********************************************************************/
// 드라이버 설정
String driver = "oracle.jdbc.OracleDriver";
// JDBC 커넥션 정보 기입
//SID
boolean isSid = true;
String connection = "jdbc:oracle:thin:@{아이피}:{포트}:{SID}";
//서비스 네임
if (!isSid) {
connection = "jdbc:oracle:thin:@{아이피}:{포트}/{서비스네임}";
}
String userId = "DB 아이디";
String passwd = "DB 비밀번호";
/**********************************************************************
* Connection Pool 을 구성합니다.
*********************************************************************/
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName(driver);
basicDataSource.setUrl(connection);
basicDataSource.setUsername(userId);
basicDataSource.setPassword(passwd);
// 최초 실행 시 생성할
basicDataSource.setInitialSize(5);
basicDataSource.setMinIdle(5);
basicDataSource.setMaxIdle(5);
basicDataSource.setMaxOpenPreparedStatements(5);
// POOL - 타임아웃
basicDataSource.setTimeBetweenEvictionRunsMillis(60000);
basicDataSource.setMinEvictableIdleTimeMillis(60000);
// 테스팅 옵션
basicDataSource.setTestOnBorrow(true);
basicDataSource.setTestOnReturn(true);
basicDataSource.setTestWhileIdle(true);
// Validation Query
basicDataSource.setValidationQuery("SELECT 1 FROM DUAL");
sharedDataSource = basicDataSource;
}
public TestBasicDataSource(BasicDataSource basicDataSource) {
this.thBasicDataSource = basicDataSource;
}
public static void main(String[] args) {
List<TestBasicDataSource> threadLst = new LinkedList<>();
for (int i = 0; i < 50; i++) {
TestBasicDataSource testBasicDataSource = new TestBasicDataSource(sharedDataSource);
testBasicDataSource.setName("TEST-TH-" + i);
testBasicDataSource.start();
threadLst.add(testBasicDataSource);
}
for (TestBasicDataSource testBasicDataSource : threadLst) {
try {
testBasicDataSource.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void init() throws SQLException {
while (this.LIMIT_COUNT-- > 0) {
// Connection Pool에서 Connection 가져오기
try (Connection conn = this.thBasicDataSource.getConnection()) {
System.out.println("Connection obtained: " + conn);
// 실제로는 여기서 SQL 쿼리 등의 작업을 수행할 수 있습니다.
// 예를 들어,
String sql = "SELECT 'TEST' AS T FROM DUAL";
try (PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
// 데이터 처리 로직
System.out.println("Data: " + rs.getString("T"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
try {
this.init();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
'Programming > 기본 (Baisc)' 카테고리의 다른 글
[APM/제니퍼,스카우터 등] Class Redefine 으로 인한 (0) | 2024.06.27 |
---|---|
[톰캣/AJP] X-Requested-With 헤더 상이한 현상 (0) | 2024.06.18 |
[Oracle Linux8] 오라클 리눅스 8 Korean Language Pack 설치 (0) | 2024.02.02 |
[ORACLE][오라클][SQLPLUS][ORA-28014] Oracle 12c ~ 19c 계정삭제 (0) | 2023.10.22 |
[HTML] ul > li 구조에서 세로 가운데 정렬(Vertical-align) 하는 방법 (0) | 2023.10.16 |