이전 아티클에서는 database insert, delete speedup에 대하여 알아보았다. 이번 편에서는 데이터베이스
transation중 가장 비용이 많이 드는 update 문장에 대한 batch처리 방법을 오라클을 기준으로
이용하여 알아보겠다.
▶ Batch에 대한 이야기를 시작하며
앞서 이미 배치의 기본개념과 JDBC 2.0에 추가되었던 addBatch()메소드와 executeBatch()메소드를
이용하여 driver가 어떻게 작동하는지와 그 API를 사용한 코딩방법에 대하여 살펴보았다.
이미 배치에 관련된 프로젝트한지는 상당히 되었으나, 대부분의 작업은 한쪽의 database의
select를 한후 다른 database의 insert를 하는 트랜잭션이었다.
현재 수행하는 프로젝트에서는 적게는 30만건정도에서 많게는 200만건정도의 레코드를
select해온후 각각의 레코드를 이용하여 billing을 하는 경우인데 위에서 말한 30~200만건의
레코드가 바로 update 대상이었다.
보통 2개의 데이터베이스에서 5개정도의 테이블을 한꺼번에 transaction을 일으킨다는 것은
쉬운 일이 아니다. 게다가 그중에 update처리를 해야 하는 테이블이 있을 경우 tuning을 하지
않는다면 speed가 현저하게 떨어지는(보통 떨어지는 것이 아니라, 아예 멈춰있는 것처럼 보인다)
것을 금새 느낄 수 있으리라~
사실 JDBC API의 standard batching을 사용할 때 이미 update에 대한 부분까지도 모두 빠른
transaction을 사용할 수 있었다면 이번과 같은 update batching에 대한 글은 insert, delete작업과
함께 끝났어야 했다.
자, 이제 전체를 아우르는 개념설명부터 시작하여 각각의 경우에 대한 처리방법에 대하여
살펴보도록 하자.
▶Update Batching
데이터베이스로의 퍼포먼스 향상 및 불필요한 작업의 수를 줄이려한다면 그냥 생각하기에도
매번 발생하는 INSERT, DELETE, UPDATE처리를 한번의 배치작업으로 처리할 수 있게끔 만드는 것이
최선으로 방법으로 생각이 되어질 수 있을 것이다.
이러한 것을 반영시켜놓은 것이 Sun의 JDBC 2.0 Specification이었는데, 지금 여기서 논하고자 하는
오라클에서는 약간의 용어를 다르게 쓰고 있다.
크게 2가지의 모델을 가져다 쓰는데
1. Standard model
Oracle8i release 8.1.6과 Sun의 JDBC 2.0 Specification을 구현한 것을 말한다.
2. Oracle-specific model
릴리즈 8.1.5부터 지원되기 시작했으며, Sun의 JDBC2.0Spec과는 완전 독립적으로 처리하는 배치를
가르켜 Oracle-specific모델이라 말한다.
note. 여기서 한가지 주의할 점은 위의 두가지 형태를 섞어서 사용할 수 없는데, 소위 또 호기심이
발동해서 사용해보겠다고 하면 해보라. 당장 exception이 발생할테니.. ^^
위에서 모델 두가지를 설명했고 빨간 글씨로 주의를 한번 줬는데 그럼 이렇게 생각할지도 모르겠다.
"놀새야~! 그럼 도대체 뭘가지고 쓰란 말이냐"라고 이야기한다면 대답은 이렇다.
" 상황에 맞게 적절한 걸 가져다 써라" 라는 것인데 해당 기간업무가 펴~~엉생~ 오라클로만 돌아가면
오라클 모델로 사용해도 될테고, 범용성 문제가 제기 된다면 stanard모델을 사용하면 될터이고..
오라클에서 이야기하는 그 상황에 대하여 한번 보자. - 오라클모델을 쓰게 되면 이식성은 떨어지지만, 결과적으로는 엄청난 퍼포먼스 향상을 이룰것이다.
- Standard모델은 이식성은 아주 좋지만, 약간의 성능향상을 이룰수 있을 것이다.
아~ 평생 오라클로 할것인가? 사실 앞선 테스트만 하더라도 standard insert, delete 배치성능은 상당했다. 그런데 자기네 모델을 쓰면 쨉도 안되는다는 것인데 당신이 직접 오라클 모델을 사용하여 테스트하면 아마도 눈이 휘둥그레질것이다 왜냐~ 전에 배치작업을 사용하지 않고 작업할때 밖에서 나가서 커피한잔 마시고, 화장실 갔다오고 해도 안되던 것이 "엔터키를 한방치고 자리에서 슬슬 일어나려고 할때 이미 트랜잭션작업들이 끝나기때문이다." ▶오라클모델에서 사용하는 statement 일반적으로 반복되는 쿼리에 대하여 값만 바꾸는 형태의 statement는 무엇인가? 그렇다. PreparedStatement이다. 안타깝게도 오라클에서는PreparedStatement밖에 사용하질 못한다. 또한 배치에 관련된 작업은 UPDATE, INSERT, DELETE로 한정할 수 있으며 SELECT에 대한 것은 이미 이전의 article인 select speedup에서 처리하는 것을 보여주었다. note. 위에서 PreparedStaement만 사용한다고 했는데 CallableStatement도 배치를 지원해주냐고 물으면 " 그런거 구현하려고 한적없다" 라는 대답만 듣게 될 것이다. ▶오라클배치모델의 특징 - 배치에 대한 어떠한 세팅도 이루어져 있지 않다면 connection은 자동으로 1건데이터에 대한 각각의 배치처리를 시도하려고 한다.
- 오라클에서 이야기하는 최상의 퍼포먼스값은 batch작업의 크기가 5에서 30사이일때라고 이야기한다.
- PreparedStatement에 아무리 열나게 배치를 올리고 있다고 하더라도 COMMIT만 만나면 driver는 아무런 생각없이 바로 sendBatch()라는 request를 데이터베이스로 날려서 현재 올려진 데이터를 update시켜버린다.
당신~ 질문하나 하겠다. JDBC에서 database commit이 언제 날아가는 지 아나? 바로 statement가 닫힐때나 connection이 close될때 자동으로 날아간다. 알고 있었는가? 알고 있었으면 말구~~ ^^ ▶본격적인 배치작업 자, 이제 본격적인 오라클모델 배치를 시작하도록 하자. 1. Connection에 batch value setting하기 처음에 우선 오라클데이터베이스와 연결이 되어진다면 connection객체를 얻게 될 것이다. 여기서부터 배치에 대한 세팅이 들어가져야 하는데 코딩은 아래와 같다. ((OracleConnection)conn).setDefaultExecuteBatch(20); 오호. 무엇하는 코드인고? 당연히 오라클 모델의 connection을 불러오는 것이다. 위의 코드는 간단하면서 객체지향의 polymorphism이 그대로 묻어난 예술적인 코드이다. ^^ 즉 우리가 얻어낸 connection이란 놈은 해당 jdbc driver의 super class인 java.sql.Connecdion인터페이스 이다. 그것을 conn.getClass().getName()하게 되면 oracle.jdbc.driver.OracleConnection이란 예쁜 객체를 돌려주게 되는 데 겉껍데기로 싸여있으니 제 구실을 못할 것 같아서 casting을 이용하여 꺼꾸로 끄집어 낸것이다. 거기에는 default배치값을 세팅할 수 있는데 위에서 이야기했던 최상의 퍼포먼스를 낼수 있는 5~30사이의 값을 세팅하여 넣었다. 자, 보통의 코딩에서 connection을 추출해냈다면 당신의 습관적인 다음 코딩은 무엇인가? 바로 connection에서 statement를 뽑아낼 것인데, 위에서도 말했듯이 오라클은 PreparedStaement만을 지원한다고 했으므로 PreparedStatement를 이용하여 값을 세팅하도록 한다. 2. Statement에 batch value setting하기 - 아래의 코딩은 일반적으로 당신도 많이 하는 것일테고 그냥 보자. PreparedStatement ps = conn.prepareStatement ("INSERT INTO dept VALUES (?,?,?)"); ps.setInt (1,12); ps.setString (2,"Carouser"); ps.setString (3,"Korea");
- 여기서부터가 중요한데 저렇게 해서 PreparedStaement를 세팅한 후에 다시 오라클배치를 할수 있도록 속성을 끌어내도록 해야 한다. 예제는 OraclePreparedStatement객체를 뽑은 후 setExecuteBatch()메소드안의 기본배치 사이즈를 2개로 잡은 것이다. 아래처럼 잡으면 위의 connection이 잡은 배치사이즈와는 다른 형태인 것임을 알아두어야 한다. ((OraclePreparedStatement)ps).setExecuteBatch(2); 혹여, 현재 PreparedStaement에 배치사이즈를 알고 싶다면 아래처럼 한번 찍어보면 될것이다 System.out.println (" 배치크기 " + ((OraclePreparedStatement)ps).getExecuteBatch());
- 자, 이제 어떤 시점에 데이터베이스에 update시켜달라고 요청해야 하는데, 아래처럼 하면 바로 업데이트 될것 같은가? 안된다. 왜냐하면 batchSize를 2로 했기 때문에 그냥 버퍼링한다고 보면 된다. System.out.println ("Number of rows updated so far: " + ps.executeUpdate ());
- 자, 아래의 값을 다시 한번 세팅시킨후 다시 executeUpdate()를 수행하면 어찌 될것인가? 위의 PS에서 세팅한 batch value인 2와 같은 수의 데이터가 세팅되어져 있으므로 드디어 Oracle로의 배치작업이 자동으로 시작되게 된다. 와~ 인공지능수준인가? ㅎㅎ, 당신도 가볍게 짤수 있는 코드이긴 하다. ps.setInt (1, 11); ps.setString (2, "ienvyou"); ps.setString (3, "Korea"); int rows = ps.executeUpdate (); System.out.println ("Number of rows updated now: " + rows); ps.close ();
자 간단한 샘플코드 하나 보자.
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;
class BatchTest{
public static void main(String[] args) throws Exception{
Connection conn = null;
Connection conn2 = null;
PreparedStatement pstmt= null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
String url = "jdbc:oracle:thin:@192.168.0.137:1521:abn";
conn = DriverManager.getConnection(url, "abs", "abs");
((OracleConnection)conn).setDefaultExecuteBatch(30);
conn.setAutoCommit(false);
pstmt = conn.prepareStatement(" update ABT230 SET update_ymd = ? where customer_no = ?");
((OraclePreparedStatement)pstmt).setExecuteBatch(20); // 요거는 오라클배치
conn2 = DriverManager.getConnection(url, "abs", "abs");
Statement stmt = conn2.createStatement();
ResultSet rset = stmt.executeQuery("select customer_no from abt230");
int count = 0;
while(rset.next()) {
pstmt.setString(1, "20039999");
pstmt.setInt(2, rset.getInt(1));
// pstmt.addBatch(); // 요거는 standard배치
pstmt.executeUpdate();
count++;
if( (count % 10000 ) == 0) {
System.out.println(new java.util.Date() + "] " + count );
}
}
pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
} finally {
pstmt.close();
conn.close();
conn2.close();
}
}
}
| 위에서 connection과 statement에 batch를 먹였는데 그건 코딩하는 사람 맘일테고, 아참. 그리고 commit mode설정하는거 잊지 마셔야 합니다. 안그러면 1건당 시스템이 소위 뻑났을때 데이터 무결성이 깨질수도 있다는 것만 명심하시면 될것 같슴다. 간략하게나마 하는 방법을 적어드렸는데 맘에 드시는지 모르겠슴다. 자자.. 이제 배치의 세계는 이만으로 하고 다음 아티클은 WAF가 있겠군요.. |