๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Spring

[Spring] N+1 Problem

๐Ÿ“Œ  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