캐시된 쿼리 지침
Rails는 SQL 쿼리 캐시를 제공합니다. 이는 요청 기간 동안 데이터베이스 쿼리의 결과를 캐시하는 데 사용됩니다. Rails가 동일한 쿼리를 동일한 요청 내에서 다시 만나면 데이터베이스에 다시 쿼리를 실행하는 대신 캐시된 결과 세트를 사용합니다.
쿼리 결과는 단일 요청 기간 동안에만 캐시되며, 여러 요청 간에는 지속되지 않습니다.
캐시된 쿼리가 왜 나쁜 것으로 여겨지는가
캐시된 쿼리는 데이터베이스의 부하를 줄이지만 여전히 다음과 같이 합니다:
- 메모리를 사용함.
- Rails가 각
ActiveRecord
객체를 다시 인스턴스화 해야 함. - Rails가 객체의 각 관계를 다시 인스턴스화 해야 함.
- 캐시된 쿼리 목록을 조회하는 데 추가 CPU 사이클을 사용함.
캐시된 쿼리는 데이터베이스 관점에서는 저렴하지만, 메모리 관점에서는 더 비싼 경우가 있습니다. 이는 종종 N+1 쿼리 문제를 감출 수 있으므로 일반적인 N+1 쿼리와 동일하게 처리해야 합니다.
캐시된 쿼리에 의해 가려진 N+1 쿼리 경우, 동일한 쿼리가 N번 실행됩니다. 데이터베이스에 N번 접근하는 것이 아니라 캐시된 결과를 N번 반환합니다. 이것은 여전히 비용이 많이든다는 것입니다. 왜냐하면 CPU 및 메모리 리소스에 더 큰 비용을 지불하여 각 시간마다 객체를 다시 초기화해야 하기 때문입니다. 대신 가능한 경우에 같은 메모리 객체를 사용해야 합니다.
새로운 기능을 도입할 때는 다음을 해야 합니다:
- N+1 쿼리를 피함.
- 쿼리 횟수를 최소화함.
- 특히 캐시된 쿼리가 N+1 문제를 가리지 않도록 주의를 기울임.
캐시된 쿼리를 탐지하는 방법
키바나를 사용하여 잠재적인 가해자 탐지
GitLab.com은 pubsub-redis-inf-gprd*
색인에 실행된 캐시된 쿼리 수와 함께 logs 항목을 기록합니다.
db_cached_count
와 같이, 수행된 캐시된 쿼리 수로 필터링할 수 있습니다. 예를 들어, db_cached_count
가 100보다 큰 엔드포인트는 주로 캐시된 쿼리에 의해 가려진 N+1 문제를 나타낼 수 있습니다. 이 엔드포인트를 자세히 조사하여 중복된 캐시된 쿼리를 실행하고 있는지 확인해야 합니다.
캐시된 쿼리와 관련된 더 많은 키바나 시각화 정보는 이슈 #259007, ‘잠재적인 N+1 CACHED SQL 호출을 탐지하는 데 도움이 되는 메트릭 제공’을 읽어보세요.
성능 바를 사용하여 의심스러운 엔드포인트 조사
기능을 구축할 때, 성능 바를 사용하여 캐시된 쿼리를 포함한 데이터베이스 쿼리 목록을 볼 수 있습니다. 성능 바는 실행된 전체 쿼리 및 캐시된 쿼리 수가 100보다 큰 경우에 경고를 표시합니다.
사용 가능한 통계에 대한 자세한 정보는 성능 바를 참조하세요.
탐지 대상
Kibana를 사용하여 실행된 캐시된 쿼리 수를 확인할 수 있습니다. 많은 수의 db_cached_count
를 가진 엔드포인트는 일반적으로 가려진 N+1 문제를 나타낼 수 있습니다.
특정 엔드포인트를 조사할 때 성능 바를 사용하여 유사한 캐시된 쿼리를 식별할 수 있으며, 이는 N+1 쿼리 문제나 유사한 종류의 쿼리 일괄 처리 문제를 나타낼 수 있습니다.
예시
예를 들어, “그룹 멤버” 페이지를 디버깅해 봅시다. 성능 바의 왼쪽 모서리에 있는 데이터베이스 쿼리는 총 데이터베이스 쿼리 수와 실행된 캐시된 쿼리 수를 보여줍니다:
이 페이지에는 55개의 캐시된 쿼리가 포함되어 있었습니다. 해당 숫자를 선택하면 쿼리에 대한 자세한 정보가 포함된 모달 창이 표시됩니다. 캐시된 쿼리는 쿼리 아래의 cached
레이블로 표시됩니다. 이 모달 창에서 여러 번 중복된 캐시된 쿼리를 볼 수 있습니다.
실제 스택 트레이스를 확장하려면 ellipsis ()를 선택하세요:
[
"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'"
]
스택 트레이스는 코드가 반복적으로 각 그룹 멤버에 대해 group.has_owner?(current_user)
를 실행하고 있으므로 N+1 문제를 보여줍니다. 이 문제를 해결하려면 반복된 코드 라인을 루프 외부로 이동하여 결과를 렌더링된 각 멤버에 전달하세요:
- 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 }
캐시된 쿼리 수정 후, 성능 바는 이제 캐시된 쿼리가 6개로 나타납니다:
변경 사항의 영향 측정 방법
코드를 프로파일링하려면 메모리 프로파일러를 사용하세요.
이 예시의 경우, Groups::GroupMembersController#index
액션 주변에 프로파일러를 사용하세요. 수정 전 애플리케이션의 통계는 다음과 같았습니다:
- 총 할당량: 7133601 바이트 (84858 객체)
- 총 유지된 객체: 757595 바이트 (6070 객체)
-
db_count
: 144 -
db_cached_count
: 55 -
db_duration
: 303 ms
수정으로 할당된 메모리와 캐시된 쿼리 수가 줄었습니다. 이러한 요소들은 전체 실행 시간을 개선하는 데 도움이 됩니다:
- 총 할당량: 5313899 바이트 (65290 객체), 1810 KB(25%) 감소
- 총 유지된 객체: 685593 바이트 (5278 객체), 72 KB(9%) 감소
-
db_count
: 95 (34% 감소) -
db_cached_count
: 6(89% 감소) -
db_duration
: 162 ms (87% 빨라짐)
자세한 정보는 다음을 참조하세요
- 잠재적인 N+1 캐시 SQL 호출을 감지하는 데 도움이 되는 메트릭
- 캐시된 쿼리를 위한 병합 요청 성능 지침(merge_request_concepts/performance.md#cached-queries)
- 가장 큰 가해자에 대한 개선사항