라라벨 연관 프로퍼티에서 캐시 사용 습관을 들입시다

라라벨 모델의 연관 프로퍼티 기능은 아주 강력합니다. 라라벨을 사용하는 핵심적인 이유라고 할 만도 합니다.

대규모 어플리케이션에서는 연관 프로퍼티를 사용할 때 성능 이슈에 주의를 기울여야 합니다. 어떻게 사용하느냐에 따라 거의 100배 성능 차이가 나기 때문입니다. 아래 벤치마크를 보면 확인할 수 있습니다.

어차피 밀리초 단위 차이라고 생각할 수 있는데요. 그런데 2밀리초짜리 일이 100번 실행되면 200밀리초가 됩니다. 200밀리초는 웹페이지 로딩에서 무시하지 못할 성능차입니다. 만약 100배 빠르게 실행할 수 있다면 같은 작업을 2밀리초로 만들 수 있게 되므로 이득이 큽니다.

테스트 코드

예컨대 $article->authors라는 연관 프로퍼티가 있다고 가정해 봅시다. 정의는 아래와 같습니다. authorables라는 연관 테이블을 이용해서 필자와 콘텐츠를 연관짓는 코드입니다. 이 때 순서는 order 필드를 이용해서 정렬하고, 역할과 설명과 순서를 함께 가져오라고 합니다. 좀 복잡한 편이죠.

public function authors()
{
$relation = $this->morphToMany(Author::class, 'authorable');
$relation->withPivot(['designation', 'description', 'order']);
$relation->orderBy('order');
return $relation;
}

캐시 사용을 하지 않는 경우

필자가 있으면 보여 줘야 할 내용이 있다고 가정합시다. 이 때 성능 이슈가 발생할 수 있습니다. 아래 코드를 봅시다.

@if ($article->authors()->count())
  필자가 있으면 보여 줄 내용
@endif

강조한 authors()가 성능 이슈에서 핵심이 되는 코드인데요, 이러면 author를 불러 오기 위해 매번 디비에 쿼리를 날리게 됩니다. 캐시를 사용하지 않게 되는 것입니다.

캐시를 사용하는 경우

아래와 같이 authors에 괄호를 붙이지 않으면 캐시를 사용하게 됩니다. 아직 한 번도 $article->authors를 사용하지 않은 경우에는 $article->authors를 불러오느라 시간이 좀 걸리게 되지만 한 번 불러온 이후부터는 속도가 거의 100배 빨라집니다.

@if ($article->authors->count())
  필자가 있으면 보여 줄 내용
@endif

벤치마크

아래는 벤치마크 코드입니다.

// 첫 실행
$article->authors()->count(); // 1.4ms
$article->authors->count(); // 1.9ms

// 두 번째 실행. 캐시 여부 확인 가능
$article->authors()->count(); // 1.25ms
$article->authors->count(); // 0.017ms

아래는 각 실행별로 걸린 시간입니다.

  1. 첫 번째 $article->authors()->count()1.4밀리초
    디비에서 count 쿼리를 실행하므로 느립니다.
  2. 첫 번째 $article->authors->count()1.9밀리초
    처음 $article->authors를 디비에서 불러오므로 역시 느립니다. 그러나 이제 캐시가 됐습니다.
  3. 두 번째 $article->authors()->count()1.25밀리초
    두 번째 실행임에도 여전히 디비에서 count 쿼리를 실행하므로 비슷한 시간이 걸립니다.
  4. $article->authors->count()0.017밀리초
    $article->authors가 캐시돼 있어 매우 빠릅니다. 약 82배 빠릅니다.

따라서 $article->authors라고 괄호 없는 연관 프로퍼티를 사용하는 습관을 들여야 합니다.

존재 유무 확인에는 isNotEmpty()를 쓰자

그런데 한 가지 개선할 게 더 있습니다. 바로 count()가 아니라 isNotEmpty()를 사용하는 것입니다. isNotEmpty()는 라라벨 콜렉션의 메서드입니다.

@if ($article->authors->isNotEmpty())
  필자가 있으면 보여 줄 내용
@endif

isNotEmpty()count()보다 빠릅니다. 제 벤치마크에서는 0.017ms가 0.014ms로 개선됐습니다.

(참고로 벤치마크에는 Laravel Debugbarstart_measure(), stop_measure() 함수를 사용했습니다.)

카테고리 글 목록 👉

대표글

댓글 남기기