부분범위 처리
- DBMS가 클라이언트에게 데이터를 전송할 때, 일정량씩 나눠 전송
- 전체 결과집합 중 아직 전송하지 않은 분량이 많이 남아있어도 서버 프로세스는 클라이언트로부터 추가 Fetch Call을 받기 전까지 그대로 멈춰 있음
- OTLP 환경에서 대용량 데이터를 빠르게 핸들링할 수 있는 중요한 원리
pivate void execute(Connection con) throws Exception {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT NAME FROM TBL");
for(int i = 0; i < 100; ++i) {
if(rs.next()) System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
}
- TBL이 1억 건에 이르는 대용량 테이블이어도 실행 결과는 매우 빠르게 출력
- DBMS가 데이터를 모두 읽어 한 번에 전송하지 않고 먼저 읽은 데이터부터 일정량(Array Size)을 전송하고 멈추기 때문
- 데이터를 전송하고 나면 서버 프로세스는 CPU를 OS에 반환하고 대기 큐에서 Sleep
- 다음 Fetch Call을 받으면 대기 큐에서 나와 그 다음 데이터부터 일정량 읽어 전송
- 전체 쿼리 결과집합을 쉼 없이 연속적으로 전송하지 않고 사용자로부터 Fetch Call이 있을 때마다 일정량씩 나눠 전송하는 것이 부분범위 처리
- 위 소스 수행 매커니즘
- 최초 rs.next() 호출 시 Fetch Call을 통해 DB 서버로부터 전송받은 데이터를 클라이언트 캐시에 저장
- 이후 rs.next() 호출 시 Fetch Call 없이 캐시에서 데이터 읽음
- 캐시에 저장한 데이터를 모두 소진한 상태에서 rs.next() 호출 시 추가 Fetch Call
- 위 과정 반복
- 100개 레코드를 전송받아 콘솔에 출력하고는 곧바로 ResultSet과 Statement 객체를 닫았으므로 위 프로그램은 테이블에 데이터가 아무리 많아도 오래 걸리지 않음
정렬 조건이 있을 때 부분범위 처리
- DB 서버는 모든 데이터를 다 읽어 정렬한 후 클라이언트에 데이터 전송 시작
- 전체범위처리임
- Sort Area와 Temp 테이블스페이스까지 이용해 정렬을 마치고 나면, 일정량씩 나눠 클라이언트에게 전송
- 정렬 기준 컬럼이 선두인 인덱스가 있다면 부분범위 처리 가능
- 인덱스는 항상 정렬된 상태이므로, 정렬 작업 없이 정렬된 상태의 결과집합을 얻을 수 있기 때문
Array Size 조정을 통한 Fetch Call 최소화
- 대량 데이터를 파일로 내려받는다면 데이터를 모두 전송해야 하므로 가급적 Array Size가 큰 것이 유리
- 전송량이 줄진 않으나, Fetch Call 횟수가 줄어듦
- 앞쪽 일부 데이터만 Fetch하다가 멈추는 프로그램이라면 Array Size를 작게 설정하는 것이 유리
- 불필요하게 많은 데이터를 전송하고 버리는 비효율을 줄일 수 있음
부분범위 처리 구현
public class AllRange {
public static void execute(Connection con) throws Exception {
int arraySize = 10;
String SQLStmt = "SELECT ID, NAME FROM TBL";
Statement stmt = con.createStatement();
stmt.setFetchSize(arraySize);
ResultSet rs = stmt.executeQuery(SQLStmt);
while(rs.next()) {
System.out.println(rs.getLong(1) + " " + rs.getString(2));
}
rs.close();
stmt.close();
}
public static void main(String[] args) throws Exception {
Connection con = getConnection();
execute(con);
releaseConnection(con);
}
}
public class PartitialRange {
public static int fetch(Resultset rs, int arraySize) throws Exception {
int i = 0;
whlie(rs.next()) {
System.out.println(rs.getLong(1) + " " + rs.getString(2));
if(++i >= arraySize) return i;
}
return i;
}
public static void execute(Connection con) throws Exception {
int arraySize = 10;
String SQLStmt = "SELECT ID, NAME FROM TBL";
Statement stmt = con.createStatement();
stmt.setFetchSize(arraySize);
ResultSet rs = stmt.executeQuery(SQLStmt);
while(true) {
int r = fetch(rs, arraySize);
if(r < arraySize) break;
System.out.println("Enter to Continue... (Q)uit?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String input = in.readLine();
if(input.equals("Q")) break;
}
rs.close();
stmt.close();
}
public static void main(String[] args) throws Exception {
Connection con = getConnection();
execute(con);
releaseConnection(con);
}
}
OLTP 환경에서 부분범위 처리에 의한 성능개선 원리
- OLTP(온라인 트랜잭션)은 일반적으로 소량의 데이터를 읽고 갱신
- OLTP에서 수천 ~ 수만건을 인덱스를 이용해 조회하려면 만족할만한 성능을 내기 어려울 수 있음
- 많은 테이블 랜덤 엑세스 때문
- 버퍼캐시히트율이 좋다면 빠른 성능을 보일 수도 있지만, 그렇지 않을 수도 있음
- OLTP성 업무에서 쿼리 결과 집합이 아주 많을 때, 사용자가 모든 데이터를 일일이 다 확인하지는 않음
- 특정한 정렬 순서로 상위 일부 데이터만 확인
- 항상 정렬 상태를 유지하는 인덱스를 활용하면, 정렬 작업을 생략하고 앞쪽 일부 데이터를 아주 빠르게 보여줄 수 있음
멈출 수 있어야 의미있는 부분범위 처리