Query DSL을 사용해 Cursor 기반으로 가게 메뉴 목록 조회 기능을 구현하던 중, Sub Query를 사용한 방법과 Join을 사용한 방법의 차이가 궁금해 2가지 방법을 모두 테스트 해보았다.
- Shop의 UUID를 통해 Shop의 ID를 조회하고 조회한 ID 값과 일치하는 Product 목록을 조회하는 방법
- Join을 사용해 Shop과 Product를 연결하여, Shop의 UUID에 해당하는 shop_id를 가진 Product 목록을 조회하는 방법
2개의 쿼리의 차이를 비교하기 위해 실행 계획으로 살펴보았다.
방법 1. Sub Query
EXPLAIN ANALYZE
SELECT *
FROM p_product AS p
where p.shop_entity_shop_id = (
select s.shop_id
FROM p_shop s
where s.uuid = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'
)
AND p.product_id > 10
ORDER BY p.product_id ASC, p.created_at ASC
LIMIT 10;
결과는 다음과 같다.
Limit (cost=18.31..18.32 rows=1 width=4169) (actual time=0.109..0.112 rows=10 loops=1)
InitPlan 1 (returns $0)
-> Index Scan using uk7dljnp9cmxbuvgkkpr5b6i3f1 on p_shop s (cost=0.14..8.15 rows=1 width=8) (actual time=0.012..0.013 rows=1 loops=1)
Index Cond: ((uuid)::text = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'::text)
-> Sort (cost=10.16..10.17 rows=1 width=4169) (actual time=0.107..0.109 rows=10 loops=1)
" Sort Key: p.product_id, p.created_at"
Sort Method: quicksort Memory: 30kB
-> Seq Scan on p_product p (cost=0.00..10.15 rows=1 width=4169) (actual time=0.046..0.057 rows=20 loops=1)
Filter: ((product_id > 10) AND (shop_entity_shop_id = $0))
Rows Removed by Filter: 10
Planning Time: 1.017 ms
Execution Time: 0.164 ms
방법 2. Join
EXPLAIN ANALYZE
SELECT *
FROM p_product AS p
JOIN p_shop AS s ON p.shop_entity_shop_id = s.shop_id
WHERE s.uuid = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'
AND p.product_id > 10
ORDER BY p.product_id ASC, p.created_at ASC
LIMIT 10;
결과는 다음과 같다.
Limit (cost=18.33..18.33 rows=1 width=7822) (actual time=0.080..0.081 rows=10 loops=1)
-> Sort (cost=18.33..18.33 rows=1 width=7822) (actual time=0.080..0.080 rows=10 loops=1)
" Sort Key: p.product_id, p.created_at"
Sort Method: quicksort Memory: 35kB
-> Nested Loop (cost=0.14..18.32 rows=1 width=7822) (actual time=0.043..0.055 rows=20 loops=1)
Join Filter: (p.shop_entity_shop_id = s.shop_id)
-> Index Scan using uk7dljnp9cmxbuvgkkpr5b6i3f1 on p_shop s (cost=0.14..8.15 rows=1 width=3653) (actual time=0.033..0.033 rows=1 loops=1)
Index Cond: ((uuid)::text = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'::text)
-> Seq Scan on p_product p (cost=0.00..10.12 rows=3 width=4169) (actual time=0.009..0.016 rows=20 loops=1)
Filter: (product_id > 10)
Rows Removed by Filter: 10
Planning Time: 0.183 ms
Execution Time: 0.116 ms
2개의 결과를 비교해보면, 메모리 사용량은 첫 번째 쿼리가 작고, 실행 시간은 두 번째 쿼리가 빠르다.
- 지금은 DB에 data가 30개 정도 밖에 없어서 차이가 크지 않은 것 같다.
대규모 데이터셋에서는 Sub Query 방식이 성능 저하를 일으켜 Join 방식을 사용한다고 한다.
- Join 방식으로 대체할 수 없는 경우는 Sub Query를 사용해야겠지만, Join으로 대체할 수 있는 경우는 Join을 사용하는 것이 좋다고 한다. (꼭 필요한 경우가 아니라면 Sub Query를 남용하지 않는 것을 권장한다고 한다.)
나중에 대규모 데이터셋을 다루게 될 때 그때 실행 계획으로 다시 살펴봐야겠다.
'💻 개발 > 주문 플랫폼' 카테고리의 다른 글
11/15 - TIL : userDetails에 null이 들어오는 문제 (0) | 2024.11.15 |
---|---|
11/14 - TIL : POSTMAN 로그인 설정하기 (0) | 2024.11.14 |
11/12 - TIL : git merge와 rebase, 반정규화 (0) | 2024.11.12 |
11/11 - TIL : 팀프로젝트 장점! (0) | 2024.11.11 |
페이지네이션 방식 - offset 기반과 cursor 기반 (0) | 2024.11.10 |