InfoGrab DocsInfoGrab Docs

캐시된 쿼리 가이드라인

요약

Rails는 요청 기간 동안 데이터베이스 쿼리 결과를 캐시하는 데 사용되는 SQL 쿼리 캐시를 제공합니다. 쿼리 결과는 해당 단일 요청 기간 동안만 캐시되며, 여러 요청에 걸쳐 유지되지 않습니다. 캐시된 쿼리는 데이터베이스 부하를 줄여주지만, 다음과 같은 비용이 여전히 발생합니다:

Rails는 요청 기간 동안 데이터베이스 쿼리 결과를 캐시하는 데 사용되는 SQL 쿼리 캐시를 제공합니다. Rails는 동일한 요청 내에서 동일한 쿼리를 다시 만나면, 데이터베이스에 쿼리를 재실행하는 대신 캐시된 결과 집합을 사용합니다.

쿼리 결과는 해당 단일 요청 기간 동안만 캐시되며, 여러 요청에 걸쳐 유지되지 않습니다.

캐시된 쿼리가 문제가 되는 이유#

캐시된 쿼리는 데이터베이스 부하를 줄여주지만, 다음과 같은 비용이 여전히 발생합니다:

  • 메모리를 소비합니다.

  • Rails가 각 ActiveRecord 객체를 재인스턴스화해야 합니다.

  • Rails가 객체의 각 관계(relation)를 재인스턴스화해야 합니다.

  • 캐시된 쿼리 목록을 조회하는 추가 CPU 사이클이 필요합니다.

캐시된 쿼리는 데이터베이스 관점에서는 비용이 적지만, 메모리 관점에서는 더 비쌀 수 있습니다. 캐시된 쿼리는 N+1 쿼리 문제를 마스킹할 수 있으므로, 일반 N+1 쿼리와 동일하게 취급해야 합니다.

캐시된 쿼리로 마스킹된 N+1 쿼리의 경우, 동일한 쿼리가 N번 실행됩니다. 데이터베이스를 N번 히트하지는 않지만 캐시된 결과를 N번 반환합니다. 매번 객체를 재초기화해야 하므로 CPU와 메모리 리소스에 더 큰 비용이 들어 여전히 비쌉니다. 대신 가능한 한 동일한 인메모리 객체를 사용해야 합니다.

새로운 기능을 도입할 때는 다음 사항을 따라야 합니다:

  • N+1 쿼리를 피해야 합니다.

  • 쿼리 수를 최소화해야 합니다.

  • 캐시된 쿼리가 N+1 문제를 마스킹하지 않도록 특별히 주의해야 합니다.

캐시된 쿼리를 감지하는 방법#

Kibana를 사용한 잠재적 문제 감지#

GitLab.com은 실행된 캐시 쿼리 수를 pubsub-redis-inf-gprd* 인덱스의 db_cached_count로 로그에 기록합니다. 실행된 캐시 쿼리 수가 많은 엔드포인트를 필터링할 수 있습니다. 예를 들어, db_cached_count가 100을 초과하는 엔드포인트는 캐시된 쿼리로 마스킹된 N+1 문제를 나타낼 수 있습니다. 이 엔드포인트가 실제로 중복된 캐시 쿼리를 실행하고 있는지 추가로 조사해야 합니다.

캐시된 쿼리와 관련된 더 많은 Kibana 시각화를 위해 이슈 #259007, '잠재적 N+1 캐시 SQL 호출을 감지하는 데 도움이 되는 메트릭 제공'을 읽어보세요.

Performance Bar를 사용한 의심스러운 엔드포인트 검사#

기능을 구축할 때 performance bar를 사용하여 캐시된 쿼리를 포함한 데이터베이스 쿼리 목록을 확인하세요. performance bar는 실행된 총 쿼리 수와 캐시된 쿼리 수의 합이 100을 초과하면 경고를 표시합니다.

사용 가능한 통계에 대한 자세한 내용은 Performance bar를 참조하세요.

확인해야 할 사항#

Kibana를 사용하여 실행된 캐시 쿼리의 수가 많은 엔드포인트를 확인할 수 있습니다. db_cached_count가 큰 엔드포인트는 중복된 캐시 쿼리가 많음을 의미할 수 있으며, 이는 종종 마스킹된 N+1 문제를 나타냅니다.

특정 엔드포인트를 조사할 때는 performance bar를 사용하여 유사하거나 캐시된 쿼리를 식별하세요. 이는 N+1 쿼리 문제(또는 유사한 종류의 쿼리 배치 문제)를 나타낼 수 있습니다.

예시#

예를 들어, "Group Members" 페이지를 디버그해 보겠습니다. performance bar의 왼쪽 모서리에서 Database queries는 데이터베이스 쿼리의 총 수와 실행된 캐시 쿼리 수를 보여줍니다:

[

](/19.1/development/img/performance_bar_members_page_v13_6.png)

해당 페이지에는 55개의 캐시된 쿼리가 포함되어 있었습니다. 숫자를 선택하면 쿼리에 대한 자세한 정보가 담긴 대화 상자가 표시됩니다. 캐시된 쿼리는 쿼리 아래에 cached 라벨로 표시됩니다. 이 대화 상자에서 여러 중복된 캐시 쿼리를 확인할 수 있습니다:

[

](/19.1/development/img/performance_bar_cached_queries_v13_6.png)

줄임표( ellipsis_h )를 선택하여 실제 스택 트레이스를 펼칩니다:

[
  "app/models/group.rb:305:in `has_owner?'",
  "ee/app/views/shared/members/ee/_license_badge.html.haml:1",
  "app/helpers/application_helper.rb:19:in `render_if_exists'",
  "app/views/shared/members/_member.html.haml:31",
  "app/views/groups/group_members/index.html.haml:75",
  "app/controllers/application_controller.rb:134:in `render'",
  "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
  "ee/app/controllers/ee/application_controller.rb:44:in `set_current_ip_address'",
  "app/controllers/application_controller.rb:493:in `set_current_admin'",
  "lib/gitlab/session.rb:11:in `with_session'",
  "app/controllers/application_controller.rb:484:in `set_session_storage'",
  "app/controllers/application_controller.rb:478:in `set_locale'",
  "lib/gitlab/error_tracking.rb:52:in `with_context'",
  "app/controllers/application_controller.rb:543:in `sentry_context'",
  "app/controllers/application_controller.rb:471:in `block in set_current_context'",
  "lib/gitlab/application_context.rb:54:in `block in use'",
  "lib/gitlab/application_context.rb:54:in `use'",
  "lib/gitlab/application_context.rb:21:in `with_context'",
  "app/controllers/application_controller.rb:463:in `set_current_context'",
  "lib/gitlab/jira/middleware.rb:19:in `call'"
]

스택 트레이스는 N+1 문제를 보여줍니다. 코드가 각 그룹 멤버에 대해 group.has_owner?(current_user)를 반복적으로 실행하기 때문입니다. 이 문제를 해결하려면, 반복되는 코드 줄을 루프 밖으로 이동하고 결과를 렌더링되는 각 멤버에 전달하세요:

- current_user_is_group_owner = @group && @group.has_owner?(current_user)

= render  partial: 'shared/members/member',
          collection: @members, as: :member,
          locals: { membership_source: @group,
                    group: @group,
                    current_user_is_group_owner: current_user_is_group_owner }

캐시된 쿼리를 수정한 후, performance bar에는 이제 6개의 캐시된 쿼리만 표시됩니다:

[

](/19.1/development/img/performance_bar_fixed_cached_queries_v13_6.png)

변경의 영향을 측정하는 방법#

메모리 프로파일러를 사용하여 코드를 프로파일링하세요. 이 예시의 경우, Groups::GroupMembersController#index 액션 주위에 프로파일러를 감쌉니다. 수정 전, 애플리케이션은 다음과 같은 통계를 보였습니다:

  • Total allocated: 7133601 bytes (84858 objects)

  • Total retained: 757595 bytes (6070 objects)

  • db_count: 144

  • db_cached_count: 55

  • db_duration: 303 ms

수정을 통해 할당된 메모리와 캐시된 쿼리 수가 줄었습니다. 이러한 요소들이 전체 실행 시간을 개선하는 데 도움이 됩니다:

  • Total allocated: 5313899 bytes (65290 objects), 1810 KB (25%) 감소

  • Total retained: 685593 bytes (5278 objects), 72 KB (9%) 감소

  • db_count: 95 (34% 감소)

  • db_cached_count: 6 (89% 감소)

  • db_duration: 162 ms (87% 빠름)

추가 정보#

캐시된 쿼리 가이드라인

GitLab v19.1
원문 보기
요약

Rails는 요청 기간 동안 데이터베이스 쿼리 결과를 캐시하는 데 사용되는 SQL 쿼리 캐시를 제공합니다. 쿼리 결과는 해당 단일 요청 기간 동안만 캐시되며, 여러 요청에 걸쳐 유지되지 않습니다. 캐시된 쿼리는 데이터베이스 부하를 줄여주지만, 다음과 같은 비용이 여전히 발생합니다:

Rails는 요청 기간 동안 데이터베이스 쿼리 결과를 캐시하는 데 사용되는 SQL 쿼리 캐시를 제공합니다. Rails는 동일한 요청 내에서 동일한 쿼리를 다시 만나면, 데이터베이스에 쿼리를 재실행하는 대신 캐시된 결과 집합을 사용합니다.

쿼리 결과는 해당 단일 요청 기간 동안만 캐시되며, 여러 요청에 걸쳐 유지되지 않습니다.

캐시된 쿼리가 문제가 되는 이유#

캐시된 쿼리는 데이터베이스 부하를 줄여주지만, 다음과 같은 비용이 여전히 발생합니다:

  • 메모리를 소비합니다.

  • Rails가 각 ActiveRecord 객체를 재인스턴스화해야 합니다.

  • Rails가 객체의 각 관계(relation)를 재인스턴스화해야 합니다.

  • 캐시된 쿼리 목록을 조회하는 추가 CPU 사이클이 필요합니다.

캐시된 쿼리는 데이터베이스 관점에서는 비용이 적지만, 메모리 관점에서는 더 비쌀 수 있습니다. 캐시된 쿼리는 N+1 쿼리 문제를 마스킹할 수 있으므로, 일반 N+1 쿼리와 동일하게 취급해야 합니다.

캐시된 쿼리로 마스킹된 N+1 쿼리의 경우, 동일한 쿼리가 N번 실행됩니다. 데이터베이스를 N번 히트하지는 않지만 캐시된 결과를 N번 반환합니다. 매번 객체를 재초기화해야 하므로 CPU와 메모리 리소스에 더 큰 비용이 들어 여전히 비쌉니다. 대신 가능한 한 동일한 인메모리 객체를 사용해야 합니다.

새로운 기능을 도입할 때는 다음 사항을 따라야 합니다:

  • N+1 쿼리를 피해야 합니다.

  • 쿼리 수를 최소화해야 합니다.

  • 캐시된 쿼리가 N+1 문제를 마스킹하지 않도록 특별히 주의해야 합니다.

캐시된 쿼리를 감지하는 방법#

Kibana를 사용한 잠재적 문제 감지#

GitLab.com은 실행된 캐시 쿼리 수를 pubsub-redis-inf-gprd* 인덱스의 db_cached_count로 로그에 기록합니다. 실행된 캐시 쿼리 수가 많은 엔드포인트를 필터링할 수 있습니다. 예를 들어, db_cached_count가 100을 초과하는 엔드포인트는 캐시된 쿼리로 마스킹된 N+1 문제를 나타낼 수 있습니다. 이 엔드포인트가 실제로 중복된 캐시 쿼리를 실행하고 있는지 추가로 조사해야 합니다.

캐시된 쿼리와 관련된 더 많은 Kibana 시각화를 위해 이슈 #259007, '잠재적 N+1 캐시 SQL 호출을 감지하는 데 도움이 되는 메트릭 제공'을 읽어보세요.

Performance Bar를 사용한 의심스러운 엔드포인트 검사#

기능을 구축할 때 performance bar를 사용하여 캐시된 쿼리를 포함한 데이터베이스 쿼리 목록을 확인하세요. performance bar는 실행된 총 쿼리 수와 캐시된 쿼리 수의 합이 100을 초과하면 경고를 표시합니다.

사용 가능한 통계에 대한 자세한 내용은 Performance bar를 참조하세요.

확인해야 할 사항#

Kibana를 사용하여 실행된 캐시 쿼리의 수가 많은 엔드포인트를 확인할 수 있습니다. db_cached_count가 큰 엔드포인트는 중복된 캐시 쿼리가 많음을 의미할 수 있으며, 이는 종종 마스킹된 N+1 문제를 나타냅니다.

특정 엔드포인트를 조사할 때는 performance bar를 사용하여 유사하거나 캐시된 쿼리를 식별하세요. 이는 N+1 쿼리 문제(또는 유사한 종류의 쿼리 배치 문제)를 나타낼 수 있습니다.

예시#

예를 들어, "Group Members" 페이지를 디버그해 보겠습니다. performance bar의 왼쪽 모서리에서 Database queries는 데이터베이스 쿼리의 총 수와 실행된 캐시 쿼리 수를 보여줍니다:

[

](/19.1/development/img/performance_bar_members_page_v13_6.png)

해당 페이지에는 55개의 캐시된 쿼리가 포함되어 있었습니다. 숫자를 선택하면 쿼리에 대한 자세한 정보가 담긴 대화 상자가 표시됩니다. 캐시된 쿼리는 쿼리 아래에 cached 라벨로 표시됩니다. 이 대화 상자에서 여러 중복된 캐시 쿼리를 확인할 수 있습니다:

[

](/19.1/development/img/performance_bar_cached_queries_v13_6.png)

줄임표( ellipsis_h )를 선택하여 실제 스택 트레이스를 펼칩니다:

[
  "app/models/group.rb:305:in `has_owner?'",
  "ee/app/views/shared/members/ee/_license_badge.html.haml:1",
  "app/helpers/application_helper.rb:19:in `render_if_exists'",
  "app/views/shared/members/_member.html.haml:31",
  "app/views/groups/group_members/index.html.haml:75",
  "app/controllers/application_controller.rb:134:in `render'",
  "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
  "ee/app/controllers/ee/application_controller.rb:44:in `set_current_ip_address'",
  "app/controllers/application_controller.rb:493:in `set_current_admin'",
  "lib/gitlab/session.rb:11:in `with_session'",
  "app/controllers/application_controller.rb:484:in `set_session_storage'",
  "app/controllers/application_controller.rb:478:in `set_locale'",
  "lib/gitlab/error_tracking.rb:52:in `with_context'",
  "app/controllers/application_controller.rb:543:in `sentry_context'",
  "app/controllers/application_controller.rb:471:in `block in set_current_context'",
  "lib/gitlab/application_context.rb:54:in `block in use'",
  "lib/gitlab/application_context.rb:54:in `use'",
  "lib/gitlab/application_context.rb:21:in `with_context'",
  "app/controllers/application_controller.rb:463:in `set_current_context'",
  "lib/gitlab/jira/middleware.rb:19:in `call'"
]

스택 트레이스는 N+1 문제를 보여줍니다. 코드가 각 그룹 멤버에 대해 group.has_owner?(current_user)를 반복적으로 실행하기 때문입니다. 이 문제를 해결하려면, 반복되는 코드 줄을 루프 밖으로 이동하고 결과를 렌더링되는 각 멤버에 전달하세요:

- current_user_is_group_owner = @group && @group.has_owner?(current_user)

= render  partial: 'shared/members/member',
          collection: @members, as: :member,
          locals: { membership_source: @group,
                    group: @group,
                    current_user_is_group_owner: current_user_is_group_owner }

캐시된 쿼리를 수정한 후, performance bar에는 이제 6개의 캐시된 쿼리만 표시됩니다:

[

](/19.1/development/img/performance_bar_fixed_cached_queries_v13_6.png)

변경의 영향을 측정하는 방법#

메모리 프로파일러를 사용하여 코드를 프로파일링하세요. 이 예시의 경우, Groups::GroupMembersController#index 액션 주위에 프로파일러를 감쌉니다. 수정 전, 애플리케이션은 다음과 같은 통계를 보였습니다:

  • Total allocated: 7133601 bytes (84858 objects)

  • Total retained: 757595 bytes (6070 objects)

  • db_count: 144

  • db_cached_count: 55

  • db_duration: 303 ms

수정을 통해 할당된 메모리와 캐시된 쿼리 수가 줄었습니다. 이러한 요소들이 전체 실행 시간을 개선하는 데 도움이 됩니다:

  • Total allocated: 5313899 bytes (65290 objects), 1810 KB (25%) 감소

  • Total retained: 685593 bytes (5278 objects), 72 KB (9%) 감소

  • db_count: 95 (34% 감소)

  • db_cached_count: 6 (89% 감소)

  • db_duration: 162 ms (87% 빠름)

추가 정보#