Compilation used too much memory와 max_sierra 전역 변수

Nathan Parrish -

경우에 따라 크거나 복잡한 쿼리에 대한 응답으로 "Query compilation used too much memory" 오류가 발생하거나 clustrix.log에 같은 텍스트로 경고가 표시될 수 있습니다. 이 유형의 오류에 대한 해결책은 global max_sierra_opt_mem_MB과 max_sierra_tot_mem_MB 매개변수를 늘리는 것입니다. 이러면 일반적으로 로그의 경고가 지속될 수 있지만(초기 컴파일에 몇 초가 걸릴 수 있습니다) 쿼리가 컴파일 될 수 있습니다.

이 아티클에서는 "compilation used too much memory" 메시지가 무엇을 의미하는지, max_sierra_* 전역 변수가 실제 무슨 일을 하는지와 커다란 복잡한 쿼리를 컴파일하는 문제를 처리하는 방법에 대해 설명합니다.  

컴파일 단계와 메모리 제한

수많은 복잡함을 넘어서 메모리 할용 목적에 대해 다음과 같은 세 단계에 관심을 갖고 있습니다.

  1. 쿼리 파싱 
  2. 쿼리 플랜 최적화
  3. 실행 가능 명령어 생성(쿼리 프래그먼트)

파싱

쿼리를 파싱하는데 사용할 수 있는 메모리 양은 max_sierra_parse_mem_MiB(기본 256MB)에 의해 결정됩니다. 이 보호 장치는 비정상적으로 큰 쿼리에 직면하여 데이터베이스크가 크래시 되는 것을 방지하는데 적합합니다. 쿼리가 이 한계를 만나면 다음과 같은 에러를 리턴합니다.

ERROR 1 (HY000) at line 1: [80897] Sierra used too much working memory: Query parsing

이 제한은 최대 512MB까지 늘릴 수 있지만 문제가 발생하면 지원 담당자에게 문의하십시오. 그러면 파서가 너무 많은 메모리를 소비하면서 부적절한 작업을 수행하고 있는지 확인할 수 있습니다.  

최적화

일반적으로 메모리를 가장 많이 사용하는 단계는 두 번째 쿼리 최적화입니다. 플래너가 잠재적으로 가능한 많은 접근 방법 중 쿼리에 응답하는 가장 좋은 접근 방법을 찾으려고 하는 단계입니다. 여기에는 조인 순서 선택, 사용할 인덱스 및 가능하면 LEFT JOIN을 INNER JOIN으로 변환하거나 수많은 OR '컬럼'='값'을 상응하는 IN SET으로 변화하는 등의 다양한 최적화를 적용합니다. 최적화 프로세스는 가능한 모든 쿼리 계획의 여지를 검색하고 예상 실행 계획을 비교하여 가장 빠르게 실행될 가능성이 높은 것을 결정하는 것과 관련이 있습니다(Clustrix 설명서의 Query Optimizer 세션에서 훨씬 자세히 다룹니다). 특히, JOIN의 수의 증가--JOIN 순서의 수는 n!입니다(n 팩토리얼. 즉, 1*2*3*...*n)--와 같이 쿼리가 복잡해지면서 모든 가능한 해결 방안도 더 증가합니다. 사용 가능한 인덱스의 수도 검색 공간의 크기에 영향을 줍니다. 우리가 이런 모든 플랜을 검토할 때 지금까지 발견한 모든 것을 놓치지 않아야 하는데 이것는 상당한 양의 메모리를 사용할 수 있습니다. 

최적화 단계에서 허용되는 메모리 양은 max_sierra_opt_mem_MiB(기본값 80MB)에 의해 제어됩니다. 컴파일러가 이 제한을 초과하면 더 최적의 플랜을 위한 검색 공간을 탐색하는 것을 중지하고 지금까지 발견된 최상의 플랜을 가지고 컴파일 다음 단계를 진행합니다. 이 플랜은 완벽하게 훌륭하거나 매우 좋지 않을 수 있습니다. 좋지 않은 경우 기대 보다 실행 비용이 증가하고 클러스터에 지연 증가와 더 많은 부하가 가해집니다. 중요하게도 이 경우 쿼리가 실행되어 문제가 표시되지 않고 결과가 사용자에게 반환됩니다. 메모리 제약 때문에 최적화를 완료하지 않았다는 표시는 clustrix.log에서 다음과 같은 경고로 표시됩니다.

WARNING rigr/sierra/plan_parse.c:282 sierra_plan_impl(): Compilation used too much memory; took 4.230127sec: SELECT `t`.`id` AS `t0_c0`, `t`.`user_id` AS `t0_c1`, `t`.`item_id` AS `t0_c2`, ...
INFO rigr/sierra/plan_parse.c:287 sierra_plan_impl(): Cost=239.49296827979, rows=2.653641
INFO rigr/sierra/plan_parse.c:289 sierra_plan_impl(): LEXPRS PEXPRS DUPEXP GROUP ADGRP MEMORY
INFO rigr/sierra/plan_parse.c:290 sierra_plan_impl(): 11732 37457 10331 2996 2996 117714kB TOTAL
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 5 10986 1076 5 5 3828kB nljoin_rule
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 10986 1081 0 0 1459kB nljoin_rule_par
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 10962 0 6060 2451 2451 81795kB join_commute_rule
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 8567 792 0 0 6526kB join_to_msjoin
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 2782 0 0 0 568kB enforce_combine
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 1826 0 0 0 2036kB enforce_merge
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 1826 0 0 0 4171kB enforce_sort
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 424 0 1037 268 268 6999kB table_scan_sarge
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 0 178 0 0 0 70kB phys_filter
INFO rigr/sierra/plan_parse.c:295 sierra_plan_impl(): 55 111 278 55 55 2275kB pklookup_to_nljoin

WARNING 메시지는 문제가 있는 쿼리를 표시하고 다음 행은 다양한 최적화 규칙에 의해 얼마나 메모리가 사용되고 있는지 표시합니다. 이 예에서 우리는 join_commute_rule이 80MB가 넘는 메모리 소모의 주범이라는 것을 알 수 있습니다. 이것은 JOIN이 매우 많을 때 일반적입니다.  

sierra_max_opt_mem_MiB 전역 변수(최대 1024MB)를 늘릴 수 있지만 메모리 제한은 또한 시간 제한이기 때문에 사용 가능한 메모리를 늘리면 초기 컴파일이 더 오래 걸릴 수 있다는 것을 명심하십시오(쿼리 플랜 캐시(QPC)은 이 비용을 상쇄할 것입니다). 또한 아래에 설명된 것 처럼 sierra_max_opt_mem_MiB가 max_sierra_tot_mem_MiB 보다 훨씬 낮아야 합니다.

컴파일

일단 구문 분석을 하고 최적의 플랜을 위해 사용 가능한 플랜을 검색한 후에는 병렬 실행을 위해 노드에 분산되도록 쿼리를 쿼리 프래그먼트로 컴파일해야 합니다. 이 단계에서도 메모리가 필요하며 메모리 소비는 max_sierra_tot_mem_MiB(기본값 128)에 의해 제한됩니다. max_sierra_tot_mem_MiB를 초과하면 쿼리는 다음과 같이 오류가 발생합니다.

ERROR 1 (HY000): [80896] Sierra used too much memory: Compilation

max_sierra_tot_mem_MiB에는 최적화 중에 사용된 메모리가 포함된다는 것에 주의하십시오(동일한 힙을 사용함). 즉, 최적화 동안 80MB를 소모하고 max_sierra_tot_mem_MiB가 기본값 128이라고 가정하면 나머지 컴파일에는 48MB 미만을 사용할 수 있습니다. 따라서, max_sierra_tot_mem_MiB와 sierra_max_opt_mem_MiB 사이에 약간의 간격을 유지하는 것이 중요합니다. ERROR "Sierra used too much memory: Compilation" 오류가 발생하는 경우 sierra_max_opt_mem_MiB가max_sierra_tot_mem_MiB 보다 훨씬 낮아야 합니다.  

작업 집합 메모리

컴파일 동안에 사용된 일부 추가 메모리가 있으며, 이를 작업 집합 메모리라고 합니다. 이는 컴파일러가 스크래치 영역의 일종으로 사용하므로 컴파일 프로세스 내내 증가하고 줄어듭니다(일반적으로 전체 컴파일을 통해 증가하는 위에서 사용된 메모리와 반대). v5.0에서는 전역 변수 max_sierra_working_mem_MiB에 제어되고 기본값은 512입니다. 이 제한을 초과하는 쿼리에는 다음과 같은 오류가 표시됩니다.

ERROR 1 (HY000): [80897] Sierra used too much working memory: Compilation

이 전역 변수는 1024로 늘릴 수 있지만 작업 집합에서 이렇게 많의 양의 메모리를 사용하면 문제가 있음을 나타낼 수 있으므로 이 오류가 발생하면 지원 담당자에게 문의하는 것이 좋습니다. 또한 이 작업 메모리는 한계는 max_sierra_tot_mem_MiB 제한과 별개이며 작업 집합 메모리는 max_sierra_tot_mem_MiB 체크에서 계산되지 않습니다. 

쿼리 힌트를 사용하여 컴파일 메모리 요구 사항 줄이기

컴파일 시간이 오래 걸리는 것을 피하기 위해 컴파일러가 탐색해야하는 일부 플랜 변경, 즉, 조인 순서 및 인덱스 선택을 제거하여 컴파일 중에 컴파일러의 작업량을 줄일 수 있습니다.  

STRAIGHT_JOIN을 사용하여 최적화 공간 줄이기

가장 효과적인 전략은 JOIN 대신 STRAIGHT_JOIN을 사용하여 조인 순서를 제공하는 것입니다. 이것은 컴파일러에게 이 특정 조인에 대한 쿼리에서 제공된 순서를 사용하도록 알립니다. 이는 두 테이블 즉, `foo` STRAIGHT_JOIN `bar` ON (foo.id = bar.pid) 간의 각 조인을 위해 제공됨에 주목하십시오. Clustrix는 현재 MySQL의 SELECT STRAIGHT_JOIN 구문을 지원하지 않습니다. Clustrix는 LEFT STRAIGHT_JOIN을 지원합니다(MySQL과 달리).

모든 조인 테이블이 아닌 일부에 조인 순서를 제공하면 플래너 메모리 소비를 상당히 줄일 수 있습니다 -- 조인을 탐색하는 비용이 계승(factorial)으로 증가하므로 하나만 줄이는 것도 아주 큰 영향이 있다는 것을 기억하십시오. STRAIGHT_JOIN을 추가할 때는 시간이 지남에 따라 데이터가 변경되므로 강제 조인 순서가 최적이 되지 못한다는 것을 고려하십시오.  

다음튼 QA 담당자의 테스트 사례 중 하나에서 제기된 쿼리의 예이며 여기서"compilation used too much memory warning" 에러가 발생합니다.

SELECT id,
       action                                                 AS op,
       reason,
       DATABASES.name                                         AS `database`,
       relations.name                                         AS relation,
       representations.name                                   AS representation,
       NULL                                                   AS bytes,
       NULL                                                   AS started,
       NULL                                                   AS finished,
       NULL                                                   AS error,
       queued,
       NULL                                                   AS running,
       IF(queued < From_unixtime(1350755986), 'leftover', '') AS leftover
FROM   system.rebalancer_queued_activity
       JOIN system.rebalancer_copies USING (id)
       JOIN system.slices USING (slice)
       JOIN system.representations USING (representation)
       JOIN system.relations
         ON ( relations.table = representations.relation )
       JOIN system.DATABASES USING (db)
WHERE  id NOT IN (SELECT id
                  FROM   system.rebalancer_activity_log);

보기에는 엄청난 규모의 조인은 아니지만, rebalancer_copies는 모두 합해서 14개의 조인을 포함하는 많은 조인과 sub-select(추가 뷰의 일부)로 작성된 뷰라는 것이 문제입니다. 이로 인해 3-4초의 컴파일 시간이 소요되고 컴파일 작업에서 너무 맣은 메모리 경고가 사용되었습니다. 몇 가지 적절한 STRAIGHT_JOIN을 추가하여 컴파일을 단축하고 이러한 경고를 제거할 수 있을까요?  

이 경우, FROM절의 두번째 행을 다음과 같이 변경하여 rebalancer_queued_activity와 rebalancer_copies 사이의 조인 순서를 강제했습니다.

       STRAIGHT_JOIN system.rebalancer_copies ON (rebalancer_copies.id = rebalancer_queued_activity.id)

(STRAIGHT_JOIN은 현재 USING 구문을 지원하지 않으므로 표준 ON으로 변경했습니다.)

이 쿼리는 이제 0.2초 안에 컴파일됩니다. 뷰가 쿼리의 복잡성을 없애는 경우 뷰의 조인 순서를 강제하는 것은 매우 큰 영향을 미칩니다.  그러나, 실제로 거의 모든 경우에 STRAIGHT_JOIN을 강제하면 컴파일 시간이 크게 단축되고 로그 경고가 제거됩니다. 이는 조인 변경이 이전에 언급한 것 처럼 계승으로 증가하기 때문에 하나의 조인을 제거(또는 순서를 강제)하면 큰 영향을 미칩니다. 이 경우rebalancer_queued_activity가 조인 순서에서 첫번째가 되도록 허용하는 한(일반적으로 매우 작기 때문에) 합리적으로 좋은 계획을 막지는 않을 것입니다. 

USE INDEX로 최적화 공간 줄이기

테이블에 사용할 수 있는 인덱스가 많은 경우에는 DBA에게 명백한 경우 사용할 인덱스를 가지고 플래너에 힌트를 사용하는 것도 도움이 될 수 있습니다. 이것은 MySQL과 비슷하게 USE INDEX(인덱스명) 구문을 사용하여 수행할 수 있습니다.  

컴파일러 메모리 사용량을 크게 증가시킬 수 있는 요소

  1. 조인 수
    • 여기에는 더 많은 조인을 포함하는 뷰가가 포함됩니다
    • Sub-selects 또한 조인으로 처리됩니다
    • 15 개의 테이블은 문제가 될 수 있습니다
    • 다음과 같은 rule에서 높은 메모리 비용이 표시됨: nljoin_rule, nljoin_rule_par, join_commute_rule, filter_inner_join_rule
    • STRAIGHT_JOIN울 사용하여 조인 순서를 지정하여 완화하십시오
  2. WHERE 절에서 여러 IN 집합 사용
    • IN 집합은 내부적으로 mtables(메모리 테이블)로 변환되므로 추가적인 조인 복잡성이 생성됩니다
    • inner_join_mtable_find에 추가로 위의 rule에서 높은 비용이 표시됨
    • IN을 여러 개의 OR 절로 변환하여 완화하십시오(예. a.thing IN (1,2,3) => (a.thing = 1 OR a.thing = 2 OR a.thing = 3)). 쿼리에 몇 개의 IN 집합이 있을 때 특히 유용함
  3. VALUES 튜플에서 반복적으로 사용되는 여러 매개 변수가 있는 함수를 가진 다중 행 삽입
    •  예. INSERT INTO foo VALUES (1, concat('abc','def')), (2, concat('ghi','jkl')), ... (10000, concat('zyx','wvu'));
    • 매개 변수가 없거나 동일한 매개 변수가 반복적으로 사용되는 경우에는 문제가 되지 않습니다. 예. now() 또는 N이 모든 행에 동일한 from_unixtime(N).
    • 다음과 같은 이유로 파싱에 실패합니다: Sierra used too much working memory: Parsing plan

Clustrix 지원 센터에 문의하십시오!

마지막으로 이러한 대안을 적용하는데 어려움이 있거나 원하는 결과를 얻지 못하면 지원 센터에 문의하십시오. 지원 계약을 맺은 고객이 티켓을 오픈할 수 있습니다.  

 

또 다른 질문이 있으십니까? 문의 등록

0 댓글

이 문서에는 댓글을 달 수 없습니다.
Zendesk 제공