260924. Study 2 - 대규모 서비스의 조회 최적화 방향
대규모 서비스의 조회 최적화 방향
Ⅰ . 발표 목차
1. 대규모 서비스의 조회 최적화 방향
1) 조회 최적화 방향
2. JPA Native Query vs MyBatis
1) 비교
2) 왜 Native Query를 사용하는가?
3) Native Query는 MyBatis 처럼 hint 사용이 가능한가?
3. 쿠케 강의 Review
1) 핵심 내용 요약
2) 부족한 점
4. 현업 실무 쿼리 튜닝 사용 빈도
1) SQLP 목차 요약/ 현업 사용 빈도
2) 현업 실무 팁
5. 쿼리 튜닝 기법
1) 작업 순서
① 현재 쿼리 조회
=> 쿼리 속도 측정
② query plan 실행
=> 병목 지점 파악
* paln 보는 법?
③ 인덱스 추가
④ 쿼리 개선
- hint 적용 검토
2) 사례 설명
3) 쿼리 튜닝시 고려점
Ⅱ. 발표
1. 대규모 서비스의 조회 최적화 방향
1) 조회 최적화 방향
| SQL 자체가 비효율적 ↓ Query Tuning │ │ 그래도 DB 요청량이 많음 ↓ Cache │ │ 조회/쓰기 모델의 책임이 복잡하게 얽힘 ↓ CQRS │ │ 트래픽/처리량이 계속 증가 ↓ Scale-out / 분산 구조 |
2. JPA Native Query vs MyBatis
1) 비교
| JPA Native Query | MyBatis | |
| SQL 직접 작성 | O | O |
| SQL 튜닝 | O | O |
| JPA 영속성 관리 | O | X |
| Entity/연관관계 | JPA가 관리 | 직접 처리 |
| 복잡한 SQL | 가능 | 강점 |
| DB 특화 SQL | 가능 | 강점 |
Native Query라고 해서 쿼리 튜닝을 못 하는 게 아니다. DB에 전달되는 SQL이므로 동일한 SQL 튜닝 원리를 적용할 수 있다.
2) 왜 Native Query를 사용하는가?
| JPA ├─ JPQL ├─ QueryDSL └─ Native Query ↓ SQL 직접 제어 |
3) Native Query는 MyBatis 처럼 hint 사용이 가능한가?
Native Query는 JPA의 영속성 관리 기능을 사용하면서도 SQL을 직접 작성할 수 있기 때문에, DB가 제공하는 Hint를 포함한 SQL 튜닝 기법을 적용할 수 있다.
단, Hint 문법은 DBMS마다 다름.
예를 들어 Oracle 계열이라면:
SQL
SELECT /*+ INDEX(p IDX_POST_BOARD_DATE) */
p.*
FROM post p
WHERE p.board_id = :boardId
ORDER BY p.created_at DESC
Spring Data JPA에서는 그대로:
Java
@Query(value = """
SELECT /*+ INDEX(p IDX_POST_BOARD_DATE) */
p.*
FROM post p
WHERE p.board_id = :boardId
ORDER BY p.created_at DESC
""", nativeQuery = true)
List<Post> findPosts(Long boardId);
MyBatis와 비교하면?
사실 Hint를 사용하는 것 자체에는 차이가 거의 없다.
MyBatis:
XML
<select id="findPosts">
SELECT /*+ INDEX(p IDX_POST_BOARD_DATE) */
p.*
FROM post p
WHERE p.board_id = #{boardId}
</select>
JPA Native Query:
Java
@Query(value = """
SELECT /*+ INDEX(p IDX_POST_BOARD_DATE) */
p.*
FROM post p
WHERE p.board_id = :boardId
""", nativeQuery = true)
둘 다 DB에는 결국 비슷한 SQL이 전달됨.
* 참고 링크 : https://checkit3625.tistory.com/entry/%EC%84%B9%EC%85%982-%EA%B2%8C%EC%8B%9C%EA%B8%80
15. 게시글 목록 API - 페이지 번호 - N번째 페이지 M개 게시글 - 설계
===> ppt. 161
16. 게시글 목록 API - 페이지 번호 - 게시글 개수 - 설계
===> ppt. 244
18. 게시글 목록 API - 무한 스크롤 설계
===> ppt. 257
20. Primary Key 생성 전략
===> ppt. 284
1) 핵심 내용 요약
- 데이터 규모
게시글 CRUD API 및 테스트 데이터 삽입
• 게시글 생성(C), 조회(R), 수정(U), 삭제(D) API
• 1,200만 건의 테스트 데이터 삽입
• 1번 게시판에 1,200만 건의 게시글
• 대규모라기엔 부족할 수 있지만, 테스트에 유의미한 개수 생성
- 적용해야 할 쿼리 튜닝 기법은? (고민..,)
• 데이터베이스에서 특정 페이지의 데이터만 바로 추출하는 방법 필요
• 페이징 쿼리
• 이러한 페이징 방식은 클라이언트 또는 서비스 특성에 따라서 크게 두 가지로 나뉜다.
• 페이지 번호
• 무한 스크롤
② Query Plan 실행 - 1. 페이지 번호 방식
| 쿼리 / 결과 | 실행시간 | ||
| Test 1. | select * from article // 게시글 테이블 where board_id= {board_id} // 게시판별 order by created_atdesc // 최신순 limit {limit} offset {offset}; // N번 페이지에서 M개 |
select * from article where board_id= 1 order by created_at desc limit 30 offset 90; |
4.16 sec |
| Plan | explain {query} explain select * from article where board_id= 1 order by created_atdesc limit 30 offset 90; |
Type Extra ALL Using where: Using filesort type = ALL 테이블 전체를 읽는다.(풀 스캔) Extras = Using where; Using filesort where절로 조건에 대해 필터링. 데이터가 많기 때문에 메모리에서 정렬을 수행할 수 없어서, 파일(디스크)에서 데이터를 정렬하는 filesort 수행. |
③ 쿼리 튜닝
① 접근 경로 최적화
- Index 설계
- Index Scan
→ create index idx_board_id_article_id on article(board_id asc, article_id desc);
• board_id 오름차순 정렬, article_id 내림차순 정렬
| • 데이터베이스에서 4번 페이지의 30개의 게시글을 다시 조회해보자. | 쿼리 / 결과 | 실행시간 |
|
| Plan | explain {query} explain select * from article where board_id= 1 order by article_id desc limit 30 offset 90; |
key idx_board_id_article_id 생성한 인덱스가 쿼리에 사용됐다는 것을 확인할 수 있다. |
0초 대 |
② SQL 재작성
- Subquery ↔ JOIN
→ Subquery
| • 이번에는 50,000페이지를 조회해보자 | 쿼리 / 결과 | 실행시간 | |
| Test 1. | select * from article // 게시글 테이블 where board_id= {board_id} // 게시판별 order by created_atdesc // 최신순 limit {limit} offset {offset}; // N번 페이지에서 M개 |
select * from article where board_id= 1 order by article_iddesc limit 30 offset 1499970; |
4.16 sec |
| Plan | explain {query} explain select * from article where board_id= 1 order by created_atdesc limit 30 offset 90; |
Type Extra ALL Using where: Using filesort type = ALL 테이블 전체를 읽는다.(풀 스캔) Extras = Using where; Using filesort where절로 조건에 대해 필터링. 데이터가 많기 때문에 메모리에서 정렬을 수행할 수 없어서, 파일(디스크)에서 데이터를 정렬하는 filesort 수행. |
③ 데이터 처리량 감소
- 필요한 컬럼만 조회
④ 정렬/집계 최소화
- ORDER BY
2) 부족한 점
4. 현업 실무 쿼리 튜닝 사용 빈도
1) SQLP 목차 요약/ 현업 사용 빈도
| 기법 | 게시판 조회와 관련성 |
| 실행계획 분석 | ⭐⭐⭐⭐⭐ |
| Index 설계/튜닝 | ⭐⭐⭐⭐⭐ |
| JOIN 튜닝 | ⭐⭐⭐⭐⭐ |
| Pagination | ⭐⭐⭐⭐⭐ |
| SORT 최소화 | ⭐⭐⭐⭐ |
| 불필요한 컬럼/데이터 제거 | ⭐⭐⭐⭐ |
| Partition | ⭐⭐ |
| Parallel Query | ⭐ |
| ROWID | ⭐ |
| RAC | ⭐ |
2) 현업 실무 팁
| 기법 | 핵심 |
| 서브쿼리 → JOIN 전환 | 불필요한 반복 접근을 줄일 수 있는지 검토 |
| IN → EXISTS 전환 | 세미 조인 형태로 바꿔 효율적인 실행계획을 유도할 수 있음 |
| NOT IN → NOT EXISTS | NULL 처리 차이까지 고려해서 변경 |
| OR 조건 → UNION ALL | 서로 다른 조건을 분리해 인덱스를 활용할 수 있는 경우 |
| 불필요한 DISTINCT 제거 | 중복 제거를 위한 SORT/HASH 비용 제거 |
| 불필요한 JOIN 제거 | 실제 조회에 필요 없는 테이블 접근 제거 |
| SELECT * → 필요한 컬럼만 | 불필요한 데이터 읽기/전송 감소 |
| 함수 적용 조건 개선 | WHERE DATE(col)=... 같은 형태를 범위 조건으로 변경 |
| LIKE 조건 개선 | LIKE '%keyword%'는 인덱스 활용이 어려운 경우가 많음 |
| OR 조건 개선 | 조건에 따라 UNION ALL 등이 더 유리할 수 있음 |
| 페이징 방식 변경 | OFFSET 방식 → Keyset/Cursor 방식 |
| 집계 방식 개선 | 불필요한 GROUP BY/COUNT 등을 줄임 |
여기서 중요한 포인트는
❌ "IN보다 EXISTS가 무조건 빠르다"
예를 들어
WHERE user_id IN (
SELECT user_id
FROM user_role
WHERE role = 'ADMIN'
)
을
WHERE EXISTS (
SELECT 1
FROM user_role r
WHERE r.user_id = u.user_id
AND r.role = 'ADMIN'
)
로 바꾸는 게 특정 데이터 분포나 DB 옵티마이저에서는 유리할 수 있지만, 현대 DB는 두 SQL을 같은 실행계획으로 최적화할 수도 있다.
그래서 SQL 튜닝의 핵심은
"이 문법이 더 빠르다"가 아니라 "실행계획을 확인하고 더 효율적인 접근 경로를 만들 수 있는가"
5. 쿼리 튜닝 기법
1) 작업 순서
| JPA ↓ Native Query ↓ 실행계획 (Query Plan) ↓ 문제 발견 (병목지점 확인) ↓ Index / JOIN / SORT / Pagination 개선 ↓ 성능 비교 |
2) 사례
1. 문제 상황
7. 성능 비교
|
3) 쿼리 튜닝시 고려점
① 접근 경로 최적화
② SQL 재작성
③ 데이터 처리량 감소
④ 정렬/집계 최소화
|
'이직준비 3 > Study' 카테고리의 다른 글
| 260917. Study 1 - 카프카 스터디 전 참고자료 (0) | 2026.09.17 |
|---|---|
| 260917. Study 1 - 카프카 (v4.0) (1) | 2026.09.16 |


