N+1 쿼리 문제
1. N+1 쿼리 문제란 무엇인가?¶
ORM을 사용해 개발해본 경험이 있다면 N+1쿼리에 대해서 한번쯤은 들어봤을 것입니다.
도대체 N+1쿼리가 뭘까요?
N+1 쿼리는 이름에서 짐작할 수 있듯 N+1번 실행하는 쿼리를 의미합니다.
물론 쿼리를 N+1번 실행하는 것 자체가 오류는 아닙니다.
다만 여러번 실행하는 만큼 리소스를 필요로 하기때문에 성능 이슈를 초래하는 경우가 많습니다.
예를 들면 2번의 실행만으로 결과를 얻을 수 있는데 N+1번 실행하면 당연히 낭비가 생기겠죠?
그렇기 때문에 의도한 동작이 아니라면 수정을 해주는것이 좋습니다.
많이 실행해서 안 좋은건 알겠는데 N과 1은 어디서 나타난걸까요?
N+1쿼리 문제는 무엇이며 왜 이러한 이름이 붙었는지 알아보겠습니다!
2. 왜 발생하는가?¶
N+1 쿼리가 발생하는 조건으로는 2가지가 있습니다.
첫번째
는 1:N 관계
(one-to-many association)입니다.
테이블 설계를 하다보면 1대 다 관계는 너무나 자연스러운 부분입니다.
딱히 문제라고 생각되는 부분이 없습니다.
두번째
는 ORM의 지연 로딩(Lazy loading)
입니다.
지연 로딩은 리소스가 필요한 순간 직전까지 쿼리 실행과 변수의 초기화를 미뤄두는 것을 의미합니다.
실제로 엔티티가 사용되기 전까지 데이터베이스에 쿼리를 보내지 않는것이죠.
덕분에 필요한 순간에만 로딩하고 메모리를 사용하므로 컴퓨팅 자원을 아낄 수 있습니다.
그러나 이런 특성이 1:N 관계와 만나면 조금 웃긴 상황이 발생합니다.
1대 다 관계의 모든 레코드를 조회하는 상황을 예로 들어보겠습니다.
먼저 부모 엔티티가 필요해서 이에 해당하는 레코드를 조회합니다.
당연히 부모 엔티티의 레코드를 조회하는 쿼리는 1번 실행 되겠죠?
그런데 모든 부모 엔티티를 루프를 돌면서 자식 엔티티를 전부 사용해야 하는 상황이 생겼습니다.
그러면 루프를 돌 때 마다 자식이 필요해지니 계속 쿼리를 실행하게 됩니다.
그렇기 때문에 부모 레코드 조회 쿼리 실행(1회)과 자식 레코드 조회 쿼리 실행(N회 - 부모 레코드가 N개인 경우)을 더해서 N+1번 호출하게 됩니다.
그래서 N+1 쿼리라는 이름이 붙게 된 것이고 1:N 관계에서 불필요하게 쿼리를 실행하게 되는 상황을 의미하게 되었습니다.
3. N+1 문제를 해결하는 방법¶
답은 간단합니다. 이미 예상하신 분들도 있으시겠죠?
지연 로딩으로 인해 발생하는 부분을 즉시 로딩하도록 바꿔주면 됩니다.
즉시 로딩(Eager loading)은 지연 로딩의 반대되는 개념입니다.
리소스가 필요해질 때 까지 로딩을 미루는 지연 로딩과 달리 즉시 로딩은 필요한 엔티티는 미리 전부 조회합니다.
물론 현재 로직에서 필요 없는 부분까지 전부 로딩하면 당연히 낭비가 발생하기 때문에 부분적으로만 사용할 필요가 있습니다. 그래서 ORM마다 부분적으로 즉시 로딩을 할 수 있는 수단을 제공합니다.
Rails를 예로 들면 include
라는 메서드를 제공합니다.
마무리¶
오늘은 ORM을 다루는 개발자라면 꼭 알고있어야 할 N+1 쿼리 문제에 대해서 알아보았습니다.
참고¶
- https://medium.com/doctolib/understanding-and-fixing-n-1-query-30623109fe89
- https://charliereese.ca/rails-performance-3-tips-for-removing-n-1-queries/
- https://stackoverflow.com/questions/10084355/eager-loading-and-lazy-loading-in-rails
작성자: 성승익
작성일: 2022-01-16