좋은 쿼리를 위해서는 적절한 쿼리문 생성, 인덱스 사용 등과 같이 쿼리 속도 향상을 위한 방법이 필요하다.

내가 작성한 PR 에서 "이렇게 하면 쿼리 속도가 느리지 않을까요?" 혹은 "쿼리가 이상해요!" 라는 코멘트를 받지 않으려면 N+1 Problem 이라는 개념을 꼭 알고 있어야 한다.

N+1 Problem 이란?

한번의 쿼리로 가져올 수 있는 데이터에서 관련된 데이터를 가져오기 위해 N 번의 쿼리를 다시 질의해야 하는 문제이다.

예를 들어 아래 쿼리를 보자.

$building = query_rows("SELECT * FROM building");
$room = [];
foreach( $building as $building ) {
    $room['room_person'] = query_one("SELECT person FROM rooms WHERE room_no=?", $building['room_no']); 
}

빌딩의 세입자를 가져오기 위해서 빌딩을 쿼리 질의 하고, 이어서 빌딩에 있는 모든 호수를 반복해서 질의한다.

DB는 쿼리 질의가 비용과 연결되기 때문에, 매우매우 비효율적인 방법이라고 할 수 있다.

N+1 Problem 을 해결하기 위해서는?

join 을 사용 하면 쉽다!

select a.*, b.person from buildings a
left join rooms b on a.building_id = b.building_id

하지만 우리는 어플리케이션 단에서 해결해보자고 한다. (라라벨 기준)

Lazy loading 이 아닌 Eager loading 을 사용하면 N+1 Problem 을 해결 할 수 있다.

기본적으로 라라벨에서는 쿼리 질의 시 Lazy loading 을 사용한다.

아래와 같은 모델이 선언되어 있다고 가정한다.

// 빌딩은 여러개의 방을 가지고 있다.
class Buildings extends Model
{
    public function rooms(): HasMany
    {
        return $this->hasMany(Rooms::class);
    }
}
// 방은 빌딩에 속해있다.
class Rooms extends Model
{
    public function buildings(): BelongsTo
    {
        return $this->belongsTo(Buildings::class);
    }
}

Lazy loading 이란?

관련된 데이터에 접근이 요청되기 이전에는 관련 데이터는 로딩되지 않는다.

$buildings = Buildings::query()
        ->get();

foreach ($buildings as $building) {
    echo $building->rooms->no;
}
// 결과
select * from buildings;
select * from rooms where no = 1;
select * from rooms where no = 2;
.
.
.
select * from rooms where no = n;

이렇게 room 의 번호를 조회 하기 위해 n 번의 쿼리가 질의 되어 진다.

Eager loading 이란 ?

해결하기 위해 라라벨에서 제공하는 eager loading 을 위한 with() 를 사용해 보자.

$buildings = Buildings::query()
		->with('rooms')
		->get();

foreach ($buildings as $building) {
    echo $building->rooms->no;
}
// 결과
select * from buildings;
select * from rooms where no in (1,2,3...,n);

Eager loading 의 경우 관련된 key 로 한번에 질의 하기 때문에 N+1 Problem 을 방지 할 수 있다.

어떠한 방법이 더 좋을까요?

어떠한 방법이 더 좋고 나쁨은 없는 것 같다.

연관된 모델에 대한 접근이 빈번 하지 않는 경우는 Lazy loading 이 효과적이겠지만, 빈번한 경우는 Eager loading 이 효과적일 수 있 듯이, 내가 나아가고자 하는 목표와 어떠한 방법과 가장 잘 어울리고 효율적인지 생각하여 사용하는게 항상 최선의 결과를 도출 할 수 있다고 생각한다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN