๐ N+1 Problem ์ด๋
์ฐ๊ด๊ด๊ณ๊ฐ ์ค์ ๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋, ์กฐํ๋ ๋ฐ์ดํฐ ๊ฐ์(N) ๋งํผ ์ฐ๊ด๊ด๊ณ์ ์กฐํ ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋ก ๋ฐ์ํ๋ ํ์
์๋ฅผ ๋ค์ด, ๋ธ๋ก๊ทธ ๊ฒ์๊ธ๊ณผ ๋๊ธ์ด ์๋ ๊ฒฝ์ฐ, ๊ฒ์๊ธ์ ์กฐํํ ํ ๊ฐ ๊ฒ์๊ธ๋ง๋ค ๋๊ธ์ ์กฐํํ๊ธฐ ์ํ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์ด๋ฅผ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
๐ธ ๋ฐ์ ์์ธ
- JPA์ ๊ฐ์ ORM์์๋ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ Proxy ๊ฐ์ฒด๋ก Lazy Loading(์ง์ฐ๋ก๋ฉ)์ ํ๋ค.
- Proxy ๊ฐ์ฒด : ์ค์ ๊ฐ์ฒด๋ฅผ ๋์ ํ์ฌ ์ฌ์ฉ์์ ์์ฒญ์ด ์์ ๋ ์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐํํ๋ ๊ฐ์ฒด
- Lazy Loading : ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ๋ ์์ ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ์กฐํํ๋ ๋ฐฉ์
- Lazy Loading์ ํ๋ ์ด์
- ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ์ง ์๊ธฐ ์ํจ์ด๋ค.
- 1:N ์ ์ฐ๊ด๊ด๊ณ์ ๊ฒฝ์ฐ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ์กฐํํ๊ฒ ๋๋ฉด DB ์ฑ๋ฅ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
๐งพ N+1 ์์ ์ฝ๋
๋ค์๊ณผ ๊ฐ์ Entity ๊ฐ ์๋ค๊ณ ํ ๋,
@Entity(name = "post")
data class Post (
@field:Id
@field:GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val title: String,
val content: String,
val author: String,
@field:OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
val comments: List<Comment> = listOf()
)
- Post ๋ ์ฌ๋ฌ ๊ฐ์ Commnet๋ฅผ ๊ฐ์ง๊ณ ์๋ค. (1:N ๊ด๊ณ)
- ๊ทธ๋ฆฌ๊ณ Lazy Loading ๋ฐฉ์์ผ๋ก ์ค์
→ Post๋ง ๋จผ์ ๊ฐ์ ธ์ค๊ณ , comments ๋ฅผ ๊บผ๋ผ ๋ ๊ทธ์ ์์ผ DB์ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆผ
์ด๋ฌ๋ฉด ์ด๋ค ๋ฌธ์ ๊ฐ ์๊ธฐ๋ํ๋ฉด -
ex1)
/ api / posts / {id} API๋ฅผ ํธ์ถํ๋ฉด PostResponse๋ฅผ 1๊ฐ ๋ฐํํ๊ณ ์ด API๋ฅผ ์ฌ์ฉํ ๋ ๋ฐ์ํ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
์ ๋ด์ฉ์ ๋ณด๋ฉด Post๋ฅผ ์กฐํํ๊ธฐ ์ํ ์ฟผ๋ฆฌ๊ฐ 1๋ฒ Comment๋ฅผ ์กฐํํ๊ธฐ ์ํ ์ฟผ๋ฆฌ๊ฐ 1๋ฒ ๋ฐ์ํ ์ ์๋ค. (์ด 2๋ฒ)
์ด๋ฅผ N+1 ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
ex2)
val posts = postRepository.findAll() // Post ์ํฐํฐ 20๊ฐ ๊ฐ์ ธ์ด
for (post in posts) {
println(post.comments) // Post๋ง๋ค comment ์กฐํ
}
์ ์ฝ๋์ฒ๋ผ findAll( )๋ก 20๊ฐ์ Post๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๊ฐ์ ธ์ค๋ฉด
SELECT * FROM post;
- Post ์กฐํ ์ฟผ๋ฆฌ 1๋ฒ
- Comment ์กฐํ ์ฟผ๋ฆฌ 20๋ฒ (Post ํ๋๋น 1๋ฒ์ฉ)
- ์ด 21๋ฒ์ ์ฟผ๋ฆฌ → ๋นํจ์จ์
๐จ N+1์ด ์ ๋ฌธ์ ๊ฐ ๋๋๊ฐ?
๐งฉ ๋ฌธ์ ์ํฉ ์ค๋ช (N+1 ๋ฌธ์ )
์๋ฅผ ๋ค์ด Post๊ฐ 20๊ฐ ์ ๋ ์๊ณ , Comment๊ฐ 2000๊ฐ ์ ๋ ์๋ค๊ณ ๊ฐ์ ํ ๋,
Post๋ฅผ ์กฐํํ๋ API๋ฅผ ํธ์ถํ๋ฉด 20๊ฐ์ Post๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ณ ,
๊ฐ Post๋ง๋ค Comment๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ 20๋ฒ ๋ฐ์ํ๊ฒ ๋๋ค.
์ด๋ ๊ฒ ๋๋ฉด ์ด 20+1๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ฒ ๋์ด DB์ ๋ถํ๊ฐ ๊ฑธ๋ฆฌ๊ฒ ๋๋ค.
- Post 20๊ฐ ์กด์ฌ
- Post๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ : 1๋ฒ
- ๊ฐ Post์ ๋ํ Comment๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ : 20๋ฒ
- ์ด ์ฟผ๋ฆฌ ์ : 1๋ฒ (Post ์ ์ฒด) + 20(๊ฐ Post์ Comment) = 21๋ฒ
๐งจ ๋ฌธ์ ์
- DB์ ๋ง์ ๋ถํ ์ ๋ฐ : Post ์๊ฐ ๋ง์์ง์๋ก ์ฟผ๋ฆฌ ์๋ ๋น๋ก์ ์ผ๋ก ์ฆ๊ฐ
- ์ฑ๋ฅ ์ ํ : ํนํ ํธ๋ํฝ์ด ๋ง์ ์๋น์ค์์ ์ฑ๋ฅ ๋ณ๋ชฉ์ ์์ธ์ด ๋๋ค.
- ๋นํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ ๊ทผ : ๋์ผํ ์ ํ์ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ณด๋ด๋ ๋นํจ์จ ๋ฐ์
์ด๋ฐ ๊ฒฝ์ฐ, DB์ ์ฑ๋ฅ์ด ์ถฉ๋ถํ๋ค๋ฉด ๋จ ํ๋ฒ์ ์กฐ์ธ์ผ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๊ฒ์ด ํจ์จ์ ์ด๋ค.
โ์ค์ํ ํฌ์ธํธ๋ ์ฟผ๋ฆฌ ํ์
๋ฐ์ดํฐ๊ฐ ๋ง๋ ์ ๋ , Lazy Loading์์๋ Post ์๋งํผ ์ฟผ๋ฆฌ๊ฐ ๋ ๋๊ฐ๋ค๋ ์ ์ด N+1 ๋ฌธ์ ์ ํต์ฌ์ด๋ค.
ํ์ํ ๋๋ง ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ค๋ค ๋ณด๋, ์์๋ณด๋ค ๋ง์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์
โ ํด๊ฒฐ ๋ฐฉ๋ฒ
โ fetchType.EAGER์ ์ฌ์ฉํ๋ฉด ์๋๋ค!
- ๋จ์ํ EAGER๋ก ๋ฐ๊พธ๋ฉด ์กฐํ ์์ ์ comment ๊น์ง ๋ค ๊ฐ์ ธ์ค์ง๋ง,
- ์์์น ๋ชปํ ๋๋ ์กฐํ๋ก ์คํ๋ ค ์ฑ๋ฅ ์ ํ๋ ์ํ ์ฐธ์กฐ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ค.
fun findAll(): List<PostResponse> {
return postRepository.findAll().map{
logger.info("Comment ์กฐํ ์ ")
val result = PostConverter.toResponseUseRepository(it)
logger.info("Comment ์กฐํ ํ")
result
}
}
ํ์ฌ Comment๊ฐ ๊ฐ์ฒด ํ์๋๋ ์ ์ฝ๋์์ toReponseUseRepository ๋ถ๋ถ์ด๋ค.
๊ทธ๋์ ํด๋น ๋ผ์ธ ์ ๋ค๋ก log๋ฅผ ์ฐ์ด๋ณด๋ฉด Lazy Loading์์๋ ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
๋ฐ๋ฉด EAGER ๋ก ์ค์ ํ๋ฉด ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
์ด๋ฌํ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ ์ด๋ด๋ Post N๊ฐ๋ฅผ ์กฐํํ๋ ์ํฉ์์๋ Lazy์ Eager์ ์ฐจ์ด๋ Comment๊ฐ ์กฐํ๋๋ ์์ ์ Comment๋ฅผ ์กฐํํ๋์ง(Lazy), Post๋ฅผ ์กฐํํ๋ ์์ ์ Comment๋ฅผ ์กฐํํ๋์ง(Eager)์ ์ฐจ์ด์ด๊ธฐ ๋๋ฌธ์ด๋ค.
+) Post 1๊ฐ๋ฅผ ์กฐํํ๋ ์ํฉ์์๋ FetchType.EAGER ์ ์ฌ์ฉํ๋ฉด N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๊ณ Left Join์ด ๊ฑธ๋ฆฐ๋ค. (1:1)
01 FetchJoin ์ฌ์ฉ (JPA ๊ถ์ฅ ๋ฐฉ์)
- fetch join์ ์ฐ๊ด ๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฆ์ ๋ก๋ฉํ๋ ๊ตฌ๋ฌธ
- @Query๋ก ์ง์ ์กฐ์ธํด์ ํ ๋ฒ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ
select distinct u
from User u
left join fetch u.posts
@Query Annotation์ ์ฌ์ฉํด์ Fetch Join์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
@Query("SELECT p FROM post p JOIN FETCH p.comments")
fun findAllPostFetchJoinComments(): List<Post>
์ค์ ๋ฐ์ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค
Hibernate: select p1_0.id,p1_0.author,c1_0.post_id,c1_0.id,c1_0.author,c1_0.content,p1_0.content,p1_0.title from example.post p1_0 join example.comment c1_0 on p1_0.id=c1_0.post_id
์ฆ, Post + Command๋ฅผ ํ์ํ ์์ ์ ํ์ํ ๋ฐ์ดํฐ๋ง ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ N+1 ๋ฌธ์ ํด๊ฒฐ
02 Batch Size (์ง์ฐ ๋ก๋ฉ ๊ฐ์ )
- Lazy Loading ์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์กฐํํ ๋ where in ์ ๋ก ๋ฌถ์ด์ ํ ๋ฒ์ ์กฐํํ ์ ์๊ฒ ํด์ฃผ๋ ์ต์
- ์ง์ฐ ๋ก๋ฉ์ ์ ์งํ๋ฉด์, ์ฟผ๋ฆฌ๋ฅผ ๋ฌถ์ด์ ํ ๋ฒ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ์
@OneToMany(mappedBy = "post")
@BatchSize(size = 100)
val comments: List<Comment>
- yml ์ ์ ์ญ ์ต์ ์ผ๋ก ์ ์ฉํ ์ ์๊ณ @BatchSize ๋ฅผ ํตํด ์ฐ๊ด๊ด๊ณ BatchSize๋ฅผ ๋ค๋ฅด๊ฒ ์ ์ฉ ๊ฐ๋ฅ
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
- BatchSize 100 ~ 1000 ์ ๋๋ก ์ ์ฉํ๊ณ DBMS์ ๋ฐ๋ผ์ where in ์ ์ 1000๊น์ง ์ ํํ๋ ๊ฒฝ์ฐ๊ฐ ์์ผ๋ฏ๋ก 1000 ์ด์์ ์ค์ ์ ์ ํ์ง ์๋๋ค.
03 EntityGraph ์ฌ์ฉ
@EntityGraph(attributePaths = {"posts"}, type = EntityGraphType.FETCH)
List<User> findAll();
- JPA์์ ์ ๊ณตํ๋ ์ด๋ ธํ ์ด์
- ํน์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป fetch join ํ๋๋ก ์ค์
์ฆ, - @EntityGraph Annotation์ ์ฌ์ฉํ๋ฉด EntityGraph์ ๋ก๋ฉ์ ๊ธฐ๋ณธ Lazy์์ Eager๋ก ๋ณ๊ฒฝํ ์ ์๋ค.
- → ํน์ ์ฟผ๋ฆฌ์ ๋ํด์๋ง EAGER ์ฒ๋ผ ์๋ํ๊ฒ ๋ง๋ค ์ ์๋ค.
- EntityGraph๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Left Outer Join ์ผ๋ก Post + Comment ๊ฐ์ด ์กฐํํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค.
04 ๊ทธ ์ธ ๋ฐฉ๋ฒ๋ค
- MyBatis๋ QueryDSL ๊ฐ์ QueryBuilder๋ฅผ ์ฌ์ฉํด์๋ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
- ์ง์ SQL์ ๊ฐ๊น์ด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํด์ N+1 ๋ฌธ์ ๋ฅผ ์ ์ด ๊ฐ๋ฅ
๐ ์ ๋ฆฌ
๋ฐฉ๋ฒ | ํน์ง | ์ฅ์ | ๋จ์ |
Lazy Loading | ํ์ํ ๋๋ง ์กฐํ | ๋ถํ์ํ ์กฐํ ๋ฐฉ์ง | N+1 ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ |
EAGER | ์ฒ์๋ถํฐ ๋ค ์กฐํ | ํธ๋ฆฌํ์ง๋ง ์์ธก ์ด๋ ค์ | ๊ณผ๋ํ ์กฐํ, ์ํ ์ฐธ์กฐ ์ํ |
Fetch Join | ํ ๋ฒ์ ์กฐ์ธํด์ ์กฐํ | ์ฑ๋ฅ ๋งค์ฐ ์ข์ | ์กฐ์ธ์ผ๋ก ์ธํ ์ค๋ณต ๊ฐ๋ฅ์ฑ |
Batch Size | Lazy + ์ผ๊ด์กฐํ | ์ ์ฐํ๊ฒ ์ฟผ๋ฆฌ ์ ๊ฐ์ | IN์ ์ ํ ์ฃผ์ ํ์ |
EntityGraph | ์ฟผ๋ฆฌ๋ง๋ค EAGER์ฒ๋ผ ์๋ | ์ ์ฐํ๊ณ ๊ฐํธ | ์ํฉ์ ๋ฐ๋ผ ๋ณต์กํด์ง ์ ์์ |
reference
https://github.com/jmxx219/CS-Study/blob/main/spring/N%2B1%20Problem.md
CS-Study/spring/N+1 Problem.md at main · jmxx219/CS-Study
Computer Science && Tech Interview . Contribute to jmxx219/CS-Study development by creating an account on GitHub.
github.com
https://www.maeil-mail.kr/question/49
๋งค์ผ๋ฉ์ผ - ๊ธฐ์ ๋ฉด์ ์ง๋ฌธ ๊ตฌ๋ ์๋น์ค
๊ธฐ์ ๋ฉด์ ์ง๋ฌธ์ ๋งค์ผ๋งค์ผ ๋ฉ์ผ๋ก ๋ณด๋ด๋๋ฆด๊ฒ์!
www.maeil-mail.kr
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] ๋น ์๋ช ์ฃผ๊ธฐ ์ฝ๋ฐฑ (1) | 2025.04.28 |
---|---|
[Spring] ์์กด๊ด๊ณ ์๋ ์ฃผ์ (0) | 2025.04.23 |
[Spring] ์ปดํฌ๋ํธ ์ค์บ (0) | 2025.04.11 |
[Spring] ์ฑ๊ธํค ์ปจํ ์ด๋ (0) | 2025.04.05 |
[Spring] Dispatcher Servlet ๋์ ๊ณผ์ (0) | 2025.04.01 |