N+뭐라고요?(또는: n+1 조회 및 이를 피하는 방법에 대해 간단히 소개)

10058 단어 railssqlrubybeginners
알림: 이것은 아주 간단한 소개로 완벽한 초보자를 위해 쓴 것입니다.
이 화제에 대한 더 많은 정보는 현지의 제단에 구글에 제품을 남기고 검색창에 당신의 질문에 낮은 소리로 대답하는 것을 권장합니다.하지만 조용히 해!이웃에게 들리고 싶지 않다.
추가 알림: 이 강좌는 Ruby on Rails와Active Record를 사용하여 이 주제를 설명하지만 전체적인 사고방식은 더욱 광범위한 환경에 적용된다.
마지막으로 일깨워 드리자면, 이 글에는 초보자에 대한 주석이 나올 수 있습니다.

다음으로 이동:
  • N + What?
  • How To Avoid Them
  • In Closing

  • N+뭐라고요?
    간단하게 말하면 n+1 조회는 ORM의 한 동작이 (1) 찾으려는 대상에 대한 조회를 생성한 다음에 (n) 대상에 대한 관련 조회를 생성할 때이다.
    상당히 건조하죠?시범을 보여 드리겠습니다.
    몬스터 조련기 프로그램을 구축하고 있다고 가정하십시오.(애저드림을 기억하는 사람이 또 있나요?)
    당신은 조련사의 모형, 모든 괴물의 모형, 그리고 연결표로서의 괴물 조련사의 모형, 괴물 조련사의 모형을 가지고 있는 조련사가 어떤 괴물을 가지고 있는지 추적하는 관계가 많은 모형을 가지고 있습니다.
    기억해 주십시오: 이곳의 중점은 모델이 아니라 관계에 대한 조회입니다
    나는 나의 모든 조련사와 괴물의 데이터베이스를 바꾸고 싶다.Ruby on Rails 애플리케이션의 컨트롤러 동작은 다음과 같습니다.
    def index
      render json: Tamer.all, include: [ :monsters ]
    end
    
    이 표현식으로 생성된 실제 SQL 질의는 다음과 같습니다.
    호기심 때문에: 나는 루비 온 레일스rails server를 실행한 후 생성된 로그에서 이 조회를 추출했다. 이 예에서 브라우저의javascript 컨트롤러에서fetch()를 속이고 수동으로 호출해서 간단한 GET 요청을 실행했다.
    Started GET "/tamers" for ::1 at 2020-09-28 21:42:35 -0700
       (0.6ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
    Processing by TamersController#index as */*
      Tamer Load (0.7ms)  SELECT "tamers".* FROM "tamers"
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.8ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 11]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.6ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 12]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.6ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 13]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (1.8ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 14]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.6ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 15]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.7ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 16]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.8ms)  SELECT "monsters".* FROM "monsters" INNER JOIN "monster_tamers" ON "monsters"."id" = "monster_tamers"."monster_id" WHERE "monster_tamers"."tamer_id" = $1  [["tamer_id", 17]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
    Completed 200 OK in 80ms (Views: 60.0ms | ActiveRecord: 15.6ms | Allocations: 23215)
    
    와, 와, 와.이 문제들을 봐라!우리 조련사는 지금 몇 명의 괴물만 있을지도 모르지만, 150명의 괴물이 있을 때 무슨 일이 일어날까요?893 ? 이것도 소수의 조련사밖에 없어!
    이것은 n+1 검색의 예입니다. 그 중에서 우리는 (1) 개의 초기 모델에 대한 검색과 (n) 개의 관련 검색이 있습니다.

    어떻게 그것들을 피할 것인가
    Active Record와 같은 층이 백엔드에서 하는 일들을 알고 있습니다. 다른 언어로 작성된 함수 호출을 바탕으로 데이터베이스에 대한 조회를 생성하고 실행하는 것입니다. 이 예에서는 루비입니다.
    내가 그에게 요구한 이 동작을 좀 더 자세히 살펴보자.
    def index
      render json: Tamer.all, include: [ :monsters ]
    end
    
    이 글에서 나는 모든 조련사를 요구하고 include: [ :monsters ]을 표현할 때 모든 조련사의 괴물을 요구한다.
    문제는 내 응용 프로그램은 내가 한 사람으로서 가지고 있는 놀라운 예측 능력이 없다는 것이다.내가 그림을 그려 달라고 하면 Tamer.all 너무 간단한 말이다. 내가 며칠 후에 조련사들과 관련된 괴물을 그려 달라고 요구할 줄은 전혀 모른다.
    다른 것을 시도해 봅시다.
    def index
      render json: Tamer.includes(:monsters).all, include: [ :monsters ]
    end
    
    봤어.includes(:monsters)?이것은 쿼리 생성기에 알려 줍니다. 이 예는Active Record입니다. 저는 Tamer를 그리고 싶습니다.모두, 하지만 관련된 모든 괴물을 포함한다.
    다음은 3개의 조련기를 사용하여 생성된 질의입니다.
    Started GET "/tamers" for ::1 at 2020-09-28 21:54:02 -0700
       (5.0ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
    Processing by TamersController#index as */*
      Tamer Load (0.6ms)  SELECT "tamers".* FROM "tamers"
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      MonsterTamer Load (1.0ms)  SELECT "monster_tamers".* FROM "monster_tamers" WHERE "monster_tamers"."tamer_id" IN ($1, $2, $3)  [["tamer_id", 18], ["tamer_id", 19], ["tamer_id", 20]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (0.5ms)  SELECT "monsters".* FROM "monsters" WHERE "monsters"."id" IN ($1, $2, $3)  [["id", 24], ["id", 25], ["id", 26]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
    Completed 200 OK in 92ms (Views: 68.5ms | ActiveRecord: 13.6ms | Allocations: 18700)
    
    허허.이것은 보기에 매우 가벼워 보인다. 단지 세 가지 문제뿐이다.
    길들인 사람의 수를 7로 배로 늘려 무슨 일이 일어날지 봅시다.
    Started GET "/tamers" for ::1 at 2020-09-28 22:45:38 -0700
       (0.6ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
    Processing by TamersController#index as */*
      Tamer Load (0.7ms)  SELECT "tamers".* FROM "tamers"
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      MonsterTamer Load (1.0ms)  SELECT "monster_tamers".* FROM "monster_tamers" WHERE "monster_tamers"."tamer_id" IN ($1, $2, $3, $4, $5, $6, $7)  [["tamer_id", 24], ["tamer_id", 25], ["tamer_id", 26], ["tamer_id", 27], ["tamer_id", 28], ["tamer_id", 29], ["tamer_id", 30]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
      Monster Load (1.2ms)  SELECT "monsters".* FROM "monsters" WHERE "monsters"."id" IN ($1, $2, $3)  [["id", 30], ["id", 31], ["id", 32]]
      ↳ app/controllers/tamers_controller.rb:3:in `index'
    Completed 200 OK in 72ms (Views: 50.6ms | ActiveRecord: 15.0ms | Allocations: 20559)
    
    잠깐만요.이것은 기록이 생성한 조회 수량의 절반에 해당한다!
    이것은 정확하다.현재, 우리는 이미 응용 프로그램에서 이 모델의 n+1 조회를 해결했다. 이것은 기록이 아무리 많아도 조회의 수량은 영원히 증가하지 않는다는 것을 의미한다.
    소량의 기록과 소량의 병렬 사용자만 있는 소형 응용 프로그램은 성능상의 실제 차이를 볼 수 없지만 수학적으로 이 조작의 시간 복잡도(buzzword alert)가 현저히 낮아졌다는 것을 의미한다.

    최후
    웃음 포인트 없음;다음에 데이터베이스에서 자원을 추출하는 것을 발견하면 이 도구를 사용할 수 있습니다. 내일 바리스타에게 n+1 조회를 배웠습니다.(실제로는 마지막에 하나도 안 할 수도 있다)
    만약 내가 무엇을 빠뜨렸다고 생각하거나 땅콩잼보다 누텔라가 왜 더 좋은지 알려주고 싶으면 메시지를 남겨주세요.

    좋은 웹페이지 즐겨찾기