본문 바로가기
백엔드/JPA

상속관계매핑 전략별 속도 비교

by ARlegro 2025. 2. 14.

목차

  1. 간단히 : JPA에서의 상속관계 매핑을 위한 전략들
  2. 시작 전 : INSERT 쿼리 비교
  3. 전략별 1000번 저장 및 수정 성능 비교 
  4. 2번째 조회부터 빠른 이유 : PostgreSQL의 여러 캐싱 
  5. 전략별 10000번 저장 및 수정 성능 비교 
  6. 결론

 

 

 

관계형 DB는 상속 관계가 없기 때문에 JPA에서는 다른 전략씀

  1. 조인 전략 → 각각 테이블로 변환
  2. 단일 테이블 전략 → 통합 테이블로 변환
  3. 구현 클래스마다 테이블 전략 → 서브타입 테이블로 변환

1,2 번 전략의 차이를 비교해볼 것이다 

 

시작 전 : INSERT 쿼리 비교 

실험 코드

for (int i = 1; i < 2; i++) {
    Movie movie = new Movie("Movie" + i, i * 100, "인셉션");
    itemRepository.save(movie);

조인 전략

부모-자식 테이블에 각각 INSERT가 발생함 → 성능 저하 예상 가능

  • 한 번의 저장에 대해 최소 2개의 INSERT 쿼리 발생
  • 테이블이 많아질수록 INSERT 성능 저하 가능성

 

싱글 테이블 전략

하나의 테이블(Item)만 존재하므로 INSERT가 단순함 → 성능 유리

  • 모든 데이터를 하나의 Item 테이블에 저장
  • dtype 컬럼을 사용하여 데이터 유형 구분
  • INSERT가 한 번만 발생함

 

 

전략별 1000번 성능 비교

1000번의 저장 및 조회 실험을 통해 조인 전략과 싱글 테이블 전략의 성능을 비교

저장 성능 비교

  • movie, books 데이터를 각각 1000개씩 저장 (총 2000개)
public void save(){
    long startTime = System.currentTimeMillis();
    log.info("========================시작========================");
    for (int i = 1; i < 1000; i++) {
        Movie movie = new Movie("Movie" + i, i * 100, "인셉션");
        Books book = new Books("Book" + i, i*50, "봉준호");
        itemRepository.save(movie);
        itemRepository.save(book);
    }
    long endTime = System.currentTimeMillis();
    log.info("걸린 시간: {}ms", (endTime - startTime));
    log.info("========================끝========================");
}

결과

전략    1st 실행(ms)  2nd 실행(ms) 3rd 실행(ms) 평균(ms)
조인 전략 1615ms 1438ms 1371ms 1475ms
싱글 테이블 전략 967ms 864ms 930ms 920ms

 

분석

  • 조인 전략은 평균 1475ms, 싱글 테이블 전략은 평균 920ms
  • → 싱글 테이블 전략이 약 37% 더 빠름
  • 조인 전략이 느린 이유
    • 각 자식 엔티티(Movie, Books)를 저장할 때마다 부모 테이블(Item)에도 INSERT 필요

조회 성능 비교

    public void findAll(){
        long startTime = System.currentTimeMillis();
        itemRepository.findAll();
        long endTime = System.currentTimeMillis();
        log.info("조회 걸린 시간: {}ms", (endTime - startTime));
    }
  • findAll() 메서드를 실행하여 1000개의 데이터를 조회
  • 쿼리 실행 시간 비교
**##조인 전략 조회 쿼리**
SELECT
    i1_0.id,
    CASE
        WHEN i1_1.id IS NOT NULL THEN 1
        WHEN i1_2.id IS NOT NULL THEN 2
        WHEN i1_0.id IS NOT NULL THEN 0
    END,
    i1_0.name,
    i1_0.price,
    i1_1.author,
    i1_2.title
FROM item i1_0
LEFT JOIN books i1_1 ON i1_0.id = i1_1.id
LEFT JOIN movie i1_2 ON i1_0.id = i1_2.id;
********************************************************************
**##싱글 테이블 전략 조회 쿼리** 
SELECT
    i1_0.id,
    i1_0.dtype,
    i1_0.name,
    i1_0.price,
    i1_0.author,
    i1_0.title
FROM item i1_0;

전략 1st 실행 (ms) 2nd 실행 (ms) 3rd 실행 (ms)

전략  1st 실행 (ms) 2nd 실행 (ms) 3rd 실행 (ms)
조인 전략 186ms 20ms 11ms
싱글 테이블 전략 193ms 21ms 13ms

 

두 전략 간 조회 속도 차이는 크지 않음

싱글 테이블과 조인 전략 간 속도 차이가 없는 이유

  • 조인이 발생하더라도 데이터 크기가 작고, 1:1 관계라서 조인 비용이 크지 않다
  • 만약 상속관계와 필드가 복잡해진담 차이가 날 수 있다.

2번 쨰 조회부터 빠른 이유 분석

어떻게 2번 조회부터 빠르지??(캐싱 설정도 따로 안했는데??)

일단 JPA의 1차 캐시는 전혀 관련이 없을 것이다. 왜냐하면 트랜잭션이 끝나면 초기화되기 때문. 따라서 다른 이유가 있을 것

현재 이용중인 postgreSQL에서 그런 기능을 제공해주나 의심이 들었고 공부를 해보았다.

PostgreSQL에서는 속도 최적화를 위한 여러 기능들이 있는데 현재 내 실험에서 적용된 기능은 2가지 기능이 예상된다.

1. PostgreSQL의 실행 계획 캐싱 효과 및 준비된 쿼리

PostgreSQL은 기본적으로 쿼리를 실행할 때 SQL을 파싱하고 실행 계획(Execution Plan)을 생성한다.

이후에 똑같은 쿼리가 들어오면 동일한 실행 계획을 재사용하여 준비된 쿼리를 사용함으로써 2nd 시도부터는 시간이 줄어든다.

(근데 준비된 쿼리는 따로 설정하던가 아니면 jdbc에서 자동으로 해줄 수도 있다는데 이 부분은 잘 모르겠다)

 

-- 준비된 쿼리 생성
PREPARE my_query (int) AS
SELECT * FROM users;

-- 실행 (매번 같은 실행 계획 사용)
EXECUTE my_query(3);  

  • 준비된 쿼리를 이로 인해 2번 실행부터 쿼리 실행 시간이 단축된다.
  • 유지 기간 : 세션 종료 시 사라진다
  • Planning Time: PostgreSQL이 이 쿼리를 실행하기 전에 최적의 실행 계획을 찾는 데 걸린 시간.
  • Execution Time: 순수한 데이터 조회 속도만 계산했을 때, 매우 빠름.

정리

  • 첫 번째 실행은 실행 계획 생성 때문에 느림
  • 두 번째 실행부터는 캐싱된 실행 계획 덕분에 빠름
  • 즉, 캐싱된 실행 계획 덕분에 이후 쿼리가 훨씬 빠르게 실행됨

 

실제 3번 조회 실험

  • EXPLAIN ANALYZE 실행 결과를 보면 첫 번째 실행 시 Planning Time이 길고 이후 실행에서는 감소
EXPLAIN ANALYZE SELECT * FROM item;

실행 계획(Planning Time) 감소

  • 처음에는 실행 계획을 세우는 데 1.9ms가 걸렸지만, 이후 실행에서는 0.075ms로 줄어들었다.
  • 이는 PostgreSQL의 실행 계획 캐싱 효과가 반영된 결과.

하지만 실행 계획만 저장한다고 해서 데이터 조회 속도가 231ms → 13ms로 급격히 줄어들 정도의 효과는 아니다. 아마, 다른 캐싱이 원인일 것으로 생각했다.

 

2. Shared Buffer - PostgreSQL의 메모리 캐싱

 

PostgreSQL은 데이터를 조회할 때 Shared Buffer라는 메모리를 활용하여 자주 조회되는 데이터를 캐싱한다

실행 횟수 데이터 로딩 방식 걸린 시간(ms)
1st 실행 디스크에서 직접 읽음 231ms
2nd 실행 Shared Buffer에서 읽음 13ms
3rd 실행 Shared Buffer에서 읽음 11ms
  • 1st 실행 : 디스크에서 직접 데이터를 읽어오므로 속도가 상대적으로 느림 (231ms).
  • 한 번 읽어온 데이터는 PostgreSQL 내부의 Shared Buffer 메모리에 저장됨.
  • 다음 실행부터는 디스크를 거치지 않고 메모리에서 데이터를 바로 가져오기 때문에 속도가 급격히 향상됨

 

전략별 10000번 성능 비교

  • 목적: 데이터 크기를 1000개 → 10000개로 증가시켜 저장, 조회 성능 비교
  • 대상:
    • 조인 전략 (Joined Strategy)
    • 싱글 테이블 전략 (Single Table Strategy)

저장 속도 (INSERT)

전략 저장 시간(ms)

조인 전략 12,791ms
싱글 테이블 전략 7,804ms
  • 조인 전략이 약 60% 더 느림
  • 조인 전략은 여러 테이블(item, books, movie)에 INSERT해야 하므로 성능이 낮음
  • 싱글 테이블 전략은 한 테이블에만 INSERT하므로 빠름

조회 속도 (SELECT)

전략 1st 조회 (ms) 2nd 조회 (ms) 3rd 조회 (ms)
조인 전략 268ms 113ms 115ms
싱글 테이블 전략 278ms 154ms 97ms
  • 10000번이라도 조회 성능은 큰 차이 없음
  • 데이터가 늘어난 상태에서는 전략별 차이가 심할 것으로 예상했지만, 조회 성능 차이는 미미함

결론

조회 성능은 큰 차이 없음, 하지만 저장 속도에서 싱글 테이블이 훨씬 유리함

1. 조인 전략이 저장 속도가 60% 더 걸리는 이유

  1. 조인 전략: 여러 테이블에 INSERT 필요
    • item, books, movie 등 여러 개의 테이블에 INSERT 작업이 발생
    • 즉, 1개 데이터 저장 시 INSERT가 여러 번 실행되므로 속도가 느려짐
  2. 싱글 테이블: 한 테이블에만 INSERT
    • 모든 데이터를 item 테이블에 저장 → INSERT 연산이 1회 발생
    • 테이블 개수가 적어 트랜잭션 커밋 비용도 낮아짐
    • 한 번의 INSERT로 저장이 끝나므로 조인 전략보다 훨씬 빠름

2. 조회 속도 차이가 크지 않은 이유

전략 1st 조회 (ms) 2nd 조회 (ms) 3rd 조회 (ms)
조인 전략 268ms 113ms 115ms
싱글 테이블 전략 278ms 154ms 97ms

LEFT JOIN을 사용하는 조인 전략이 훨씬 느릴것으로 예상했다. but 크지 않음

이유 분석 ⇒ 1:1 관계이기 때문에 테이블이 3개라도 실제 조인 비용이 크지 않음

만약 데이터가 커진다면 조인 성능이 악화될 수 있다고 생각한다

"조인 전략이 언제 성능이 나빠지는가?"

  1. 상속 관계가 깊어 테이블 개수가 많아질 때
    • 조인할 테이블이 많아지고 실행계획 최적화가 어려워진다.
    • 캐시 및 조회 성능이 저하될 가능성 높아진다
      SELECT 
          i.id, i.name, i.price, 
          b.author, m.title, 
          c.director, d.publisher, e.genre
      FROM item i
      LEFT JOIN books b ON i.id = b.id
      LEFT JOIN movie m ON i.id = m.id
      LEFT JOIN cd c ON i.id = c.id
      LEFT JOIN dvd d ON i.id = d.id
      LEFT JOIN ebook e ON i.id = e.id;
      ​
    •  
  2. 자식 테이블의 필드(컬럼) 개수가 많을 때
    • 조인 시 가져와야 할 데이터 양이 많아지고, 캐싱 효율이 떨어짐
      SELECT 
          i.id, i.name, i.price, 
          b.author, b.page_count, b.publisher, b.language, 
          m.title, m.director, m.release_year, m.genre,
          c.artist, c.track_count, c.label, c.format
      FROM item i
      LEFT JOIN books b ON i.id = b.id
      LEFT JOIN movie m ON i.id = m.id
      LEFT JOIN cd c ON i.id = c.id;
      ​
  3. 데이터 크기가 100만 개 이상일 때
    • 조인 시 디스크 I/O가 증가하고, 인덱스 최적화가 어렵고 조인 비용이 증가할 가능성이 큼

 

조인 전략은 데이터 정규화와 효율적인 데이터 관리가 중요한 경우에 사용

1) 테이블 수가 적고 2) 필드 수가 적을 때 유리하다 But 구조가 복잡해질수록 비효율적이 됨.

특정 카테고리의 상품만 조회할 경우, 조인 전략이 훨씬 유리

'백엔드 > JPA' 카테고리의 다른 글

@ManyToOne의 optional, cascade 속성  (0) 2025.04.21
@NotNull과 @Column(nullabe=false)  (0) 2025.04.20
JPA 소개  (0) 2025.04.18
개선된 hibernate 6.0 @EntityGraph 에 미친 영향  (0) 2025.03.12