하이버네이트 VS iBatis 성능 비교: 이번에는 select

결과는 비슷하다. 차이 없다.

어제는 insert 성능 비교했었는데, 단일 insert 문이야 얼마나 차이가 나겠냐. 고작 3~4 밀리세컨 가지고 성능을 논하기는 뭐할 것 같아서, 1000건~5000건을 한꺼번에 insert 시키는 시간을 측정해 봤더니, 이건 뭐 하이버네이트가 한 5~6배는 빠르게 나왔었다. 코드까지 열어봐도 잘 모르겠지만, 배치 업데이트를 하는것처럼 보인다. 그러지 않고서는 저렇게 성능차이가 날 수 없다. 아니면 내가 뭔가 잘못 테스트 했거나….

코드는 github에 올려두었으니, 해보고 싶으신 분들은 직접 해보시면 됩니다.

https://github.com/keesun/his

어제 저녁에는 select 성능을 측정하다가 퇴근시간이 다되서 나갔는데, 오늘 오전에 마저 해봤더니, 성능이 비슷하게 나왔습니다. 예상했던 결과입니다. 하이버네이트나, iBatis나 결국은 DataSource 빈 통해서 가져오는거고, 그 위에서 맵핑좀 하고, 미리 만들어둔 쿼리 가져다가 쓰는것 뿐이니까요.

소스 코드에서는 MemberServiceIntegTest 클래스를 실행해보시면 됩니다.

하이버네이트와 아이바티스 DAO를 사용하는 서비스 구현체를 가지고 테스트했습니다. 처음엔 DAO를 직접 가져다가 테스트 했는데, 그때는 테스트 클래스 위에 붙인 @Transacional 때문에, 트랜잭션 단위가  테스트 메서드로 묶이는데, 그게 성능에 영향을 준건지, Hibernate가 iBatis보다 select 성능이 2배 정도 높게 측정됐었습니다. 조금 차이는건 이해하겠지만, 2배 정도 차이나는건 이해할 수가 없고, 원인도 모르겠더군요. 그러던 중 직장 동료분께서 트랜잭션 범위에 대해서 조언을 주셨고, 결국 서비스를 구현하여, 서비스 구현체에 @Transional을 붙인 상태로 테스트 해보게 됐습니다.

테스트 코드 찾기 귀찮으신 분들을 위해서…

[java]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
public class MemberServiceIntegTest {
   
    @Autowired MemberServiceHibernateImpl memberServiceHibernate;
    @Autowired MemberServiceIbatisImpl memberServiceIbatis;

    @Before
    public void setUp(){
        memberServiceHibernate.flushAndClear();
        memberServiceIbatis.deleteAll();
    }

    @Test
    @Repeat(10)
    public void hibernate(){
        //GIVEN
        List<Member> member5000 = MemberTestUtils.makeMember5000();
        for(Member newMember : member5000) {
            memberServiceHibernate.add(newMember);
        }
        memberServiceHibernate.flushAndClear();
        System.out.println("Hibernate: insert 5000 complete!");

        //WHEN
        StopWatch watch = new StopWatch();
        watch.start();
        for(int i = 0 ; i < 100 ; i++) {
            List<Member> members = memberServiceHibernate.list();
        }
        memberServiceHibernate.flushAndClear();
        watch.stop();

        //THEN
        System.out.println(watch.prettyPrint());
    }

    @Test
    @Repeat(10)
    public void iBatis(){
        //GIVEN
        List<Member> member5000 = MemberTestUtils.makeMember5000();
        for(Member newMember : member5000) {
            memberServiceIbatis.add(newMember);
        }
        System.out.println("iBatis: insert 5000 complete!");

        //WHEN
        StopWatch watch = new StopWatch();
        watch.start();
        for(int i = 0 ; i < 100 ; i++) {
            List<Member> members = memberServiceIbatis.list();
        }
        watch.stop();
        //THEN
        System.out.println(watch.prettyPrint());
    }

}
[/java]

5천 건을 넣고, selecrt를 100번한 시간을 측정하는 겁니다. 그렇게 하이버 10번, iBatis 10번. 해본 결과는 다음과 같습니다.

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 6428
—————————————–
ms     %     Task name
—————————————–
06428  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 5399
—————————————–
ms     %     Task name
—————————————–
05399  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3758
—————————————–
ms     %     Task name
—————————————–
03758  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3773
—————————————–
ms     %     Task name
—————————————–
03773  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3741
—————————————–
ms     %     Task name
—————————————–
03741  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3735
—————————————–
ms     %     Task name
—————————————–
03735  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3719
—————————————–
ms     %     Task name
—————————————–
03719  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 4156
—————————————–
ms     %     Task name
—————————————–
04156  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3829
—————————————–
ms     %     Task name
—————————————–
03829  100% 

Hibernate: insert 5000 complete!
StopWatch ”: running time (millis) = 3785
—————————————–
ms     %     Task name
—————————————–
03785  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3753
—————————————–
ms     %     Task name
—————————————–
03753  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3568
—————————————–
ms     %     Task name
—————————————–
03568  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3521
—————————————–
ms     %     Task name
—————————————–
03521  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3558
—————————————–
ms     %     Task name
—————————————–
03558  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3471
—————————————–
ms     %     Task name
—————————————–
03471  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3497
—————————————–
ms     %     Task name
—————————————–
03497  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3667
—————————————–
ms     %     Task name
—————————————–
03667  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3460
—————————————–
ms     %     Task name
—————————————–
03460  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3524
—————————————–
ms     %     Task name
—————————————–
03524  100% 

iBatis: insert 5000 complete!
StopWatch ”: running time (millis) = 3467
—————————————–
ms     %     Task name
—————————————–
03467  100%

OSIV 패턴 사용시 SQL 누수 현상에 대한 대처 방안

OSIV 패턴을 안티 패턴으로 보는 시각도 있다. 이해한다. OSIV 필터나 인터셉터를 사용하면 개발할 때 편하지만, 뷰 랜더링 시점에 예측하지 못한 쿼리가 발생해서 시스템 성능에 영향을 줄 수 있다. 그래서 확인해봐야 하는데.. 문제는 테스트할 때 OSIV 필터 적용으로 발생하는 추가적인 SQL까지 잡아낼 수 있느냐가 관건이다.

결론부터 말하자면, 그렇게 새어나가는 SQL을 모두 잡을 수 있다.

쉽다. 서블릿 컨테이너에 무조건 올려서 해당 URL을 모두 브라우저 주소창에 입력하고, 매번 콘솔로 가서 찍힌 쿼리 목록을 수집하면 된다. 쉬운데 피곤하다. 그래서 자동화된 테스트가 필요하다.

토비의 스프링 3을 잘 읽은 사람이라면 손쉽게 이 불편함을 해결하면서 새어나가는 SQL을 잡아낼 수 있다.

지금은 업무 중이라 길게 못 쓰겠다. @_@;; Adios~

 

ps: 참 애매하다. 내가 업무 중에 알아낸 기술적인 내용을 블로깅 해도 되는 것일까 안되는 것일까. 잘 모르겠다. 업무 시간에 내가 알아낸 사실들은 전부 회사의 자산으로 봐야하기 때문에.. 내가 업무중에 알아낸 내용을 공개하면 처벌을 받을 수도 있겠다. 그런데.. 내가 업무중에 알아낸 내용은 내가 이 회사에 다니기 전부터 공부해 왔던 지식들이나 업무중이 아닐 때 머리속으로 생각한 내용 때문이기도 하다. 그리고.. 내가 업무 중일 때 알아낸 게 사실은 업무 중이 아닐 때 알아낸 것일 수도 있다. 스터디에 나가서 상담을 했다거나, 다른 회사에 다니는 사람과 전화 통화를 하다가 알아 냈다거나.. 그런거는 블로깅을 해도 되는 것일까?

모르겠다. 한가지 분명한건 회사가 알려준게 아니라 내가 알아낸 거다.

그러니까.. 내가 업무 중이 아닐 때 알아냈다고 우기면 그만 아닌가? 내 머릿속에 DRM이나 감시용 프로세스를 달아 놓은게 아니라면 어떻게 알겠어..

[하이버네이트 성능 튜닝] 패칭 전략

참조: http://www.javadev.org/files/Hibernate%20Performance%20Tuning.pdf

하이버네이트에서 패칭 전략은 애플리케이션에서 어떤 객체와 연관된 객체를 가져오는 방법이다. 즉 A->B 또는 A->Collection 이런 관계가 있을 때 A와 연관되어 있는 B 또는 Collection를 가져오는 방법이다. 쉽게 생각해보면 A를 가져갈때 B나 C도 같이 가져가는 방법이 있고, A를 가져갈땐 A만 가져가고 B는 나중에 a.b라고 접근하는 순간에 가져갈 수 있겠다. 그런데 이렇게 단순하지 많은 않다.

  • Join fetching: 연관된 인스턴스나 콜렉션을 동일한 SELECT 절에서 OUTER JOIN으로 가져온다.
  • Select fetching: 연관된 인스턴스나 콜렉션을 부가적인 SELECT 절을 사용해서 가져오기. lazy=”false”라고 명시하지 않는한 이 부가적인 쿼리는 실제 해당 컬렉션에 접근할 때 발생한다.
  • Subselect fetching: 이전 쿼리나 패치에서 가져온 모든 엔티티에 연관된 인스턴스나 콜렉션을 부가적인 SELECT 절을 사용해서 가져오기. lazy=”false”라고 명시하지 않는한 이 부가적인 쿼리는 실제 해당 컬렉션에 접근할 때 발생한다.
  • Batch fetching: select fetching의 최적화 전략. 하이버네이트는 인스턴스나 컬렉션을 주키나 외래키 목록을 사용해 하나의 SELECT 절로 묶어서 가져온다.

패칭 전략을 이렇게 나누기도 한다.

  • Immediate fetching: owner 쪽을 로딩할 때 컬렉션이나 속성을 그 즉시 가져온다.
  • Lazy collection fetching: 해당 컬렉션에 접근할 때 가져온다.(이게 컬렉션의 기본 전략)
  • “Extra-lazy” collection fetching: 컬렉션의 개별 요소에 접근할 때 데이터베이스에 접근한다. 컬렉션을 전부다 메모리로 가져오지 않는 방법인데 컬렉션이 매우 클때 적합하다.
  • Proxy fetching: 단일 값 관계에 있는 인스턴스는 id가 아닌 다른 속성에 접근할 때 가져온다.
  • “No-proxy” fetching: 인스턴스 변수에 접근하면 바로 가져온다. Proxy fetching에 비해 덜 lazy한 방식이지만 프록시를 사용하지 않으니 더 깔끔하다. 이 방법을 사용하려면 빌드시 바이트코드 조작을 해야 하다. 이 방법은 거의 사용하지 않는다.
  • Lazy attribute fetching: 어떤 속성이나 단일 값 관계에 접근할 때 인스턴스 변수를 접근한다. 이 방법도 빌드시 바이트코드 조작을 해야 하며 거의 사용하지 않는다.