라라벨 모델의 연관 프로퍼티 기능은 아주 강력합니다. 라라벨을 사용하는 핵심적인 이유라고 할 만도 합니다.
대규모 어플리케이션에서는 연관 프로퍼티를 사용할 때 성능 이슈에 주의를 기울여야 합니다. 어떻게 사용하느냐에 따라 거의 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
아래는 각 실행별로 걸린 시간입니다.
- 첫 번째
$article->authors()->count()
– 1.4밀리초
디비에서 count 쿼리를 실행하므로 느립니다. - 첫 번째
$article->authors->count()
– 1.9밀리초
처음$article->authors
를 디비에서 불러오므로 역시 느립니다. 그러나 이제 캐시가 됐습니다. - 두 번째
$article->authors()->count()
– 1.25밀리초
두 번째 실행임에도 여전히 디비에서count
쿼리를 실행하므로 비슷한 시간이 걸립니다. $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 Debugbar의 start_measure()
, stop_measure()
함수를 사용했습니다.)
댓글 남기기