Clickhouse 연구 노트(10) - 쿼리 최적화

단일 테이블 쿼리

Where 대신 Prewhere

데이터를 필터링할 때 prewhere가 지정된 열 데이터를 먼저 읽어 데이터 필터링을 결정하고 대기하는 위치와 비교 데이터 필터링 후 select로 선언된 열 필드를 읽어나머지 속성을 완성합니다

간단히 말하면 필터링을 먼저 하고 쿼리를 하는 것이고, where 필터링은 해당 컬럼 필드를 먼저 쿼리하고 필터링 조건에 따라 데이터를 필터링하는 것이므로 prewhere 필터링을 사용하여 처리하는 데이터의 양을 비교하면 더 작고 효율성이 더 높습니다.

그러나 prewhere는 mergetree 엔진에만 적용될 수 있다는 점에 유의해야 합니다.

기본적으로 where 조건은 매개변수에 의해 제어되는 사전 위치로 자동 최적화됩니다. optimize_move_to_prewhere;

매개변수 값 1은 prewhere를 사용함을 의미합니다.


where와 prewhere 사용 비교:

①사용 장소:

사전 자동 최적화를 끈 후 다음 문을 실행합니다.

select WatchID, 
 JavaEnable, 
 Title, 
 GoodEvent, 
 EventTime, 
 EventDate, 
 CounterID, 
 ClientIP, 
 ClientIP6, 
 RegionID, 
 UserID, 
 CounterClass, 
 OS, 
 UserAgent, 
 URL, 
 Referer, 
 URLDomain, 
 RefererDomain, 
 Refresh, 
 IsRobot, 
 RefererCategories, 
 URLCategories, 
 URLRegions, 
 RefererRegions, 
 ResolutionWidth, 
 ResolutionHeight, 
 ResolutionDepth, 
 FlashMajor, 
 FlashMinor, 
 FlashMinor2
from datasets.hits_v1 where UserID='3198390223272470366';

결과는 다음과 같습니다. 2.467s가 걸렸습니다.

②다음 위치에서 사용:

Optimization을 켠 후 같은 문장을 실행하면 다음과 같은 결과가 나오며, 0.172초 밖에 걸리지 않아 효율성이 크게 향상되는 것을 확인할 수 있습니다.


일부 시나리오에서는 다음과 같이 자동 최적화가 적용되지 않습니다.

  • 상수 표현식 사용
  • 별칭 유형의 기본값이 있는 필드 사용
  • arrayJOIN, globalIn, globalNotIn 또는 indexHint를 포함하는 쿼리
  • 선택 쿼리의 열 필드는 다음의 조건자와 동일합니다.
  • 사용된 기본 키 필드

예를 들어, 선택 쿼리의 열 필드와 where의 조건자는 동일합니다.

explain syntax select UserID from hits_v1 where UserID='3198390223272470366';

결과는 다음과 같습니다.

최적화가 없음을 알 수 있습니다.

따라서 prewhere를 사용할 때 자동 최적화에 의존하는 대신 SQL에서 직접 prewhere를 사용하는 것이 가장 좋습니다.

예를 들어:

explain syntax select UserID from hits_v1 prewhere UserID='3198390223272470366';

결과는 다음과 같으며 유효합니다.

데이터 샘플링 샘플

예:

SELECT Title,count(*) AS PageViews 
FROM hits_v1
SAMPLE 0.1
WHERE CounterID =57
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000

샘플링 구문:SAMPLE 样本比例(1代表100%)

샘플 조항 | 클릭하우스 문서

참고:샘플링 수정자는 MergeTree 엔진 테이블에서만 유효하며 테이블을 생성할 때 샘플링 전략을 지정해야 합니다.

여기서 샘플링 전략은 테이블을 생성할 때 지정됩니다.

UserID 필드의 해시 값을 기준으로 샘플링을 나타냅니다.

열 가지치기 및 파티션 가지치기

열 정리: 데이터 양이 너무 많은 경우 select * 작업을 사용하지 마세요.

파티션 정리: 필요한 파티션만 읽고 필터 조건에 지정합니다.

예를 들어:

select WatchID, 
 JavaEnable, 
 Title, 
 GoodEvent, 
 EventTime, 
 EventDate, 
 CounterID, 
 ClientIP, 
 ClientIP6, 
 RegionID, 
 UserID
from datasets.hits_v1
where EventDate='2014-03-23';

여기EventDate는 파티션 필드입니다

where 및 Limit와 결합된 orderby

천만 개가 넘는 데이터 세트에 대해 쿼리별 순서를 수행하는 경우 where 조건과 제한 문을 함께 사용해야 합니다.

비교:

①다음 순서로만 사용하세요:

SELECT UserID,Age
FROM hits_v1 
ORDER BY Age DESC;

②사용 장소 및 제한:

SELECT UserID,Age
FROM hits_v1 
WHERE CounterID=57
ORDER BY Age DESC LIMIT 1000;

처리되는 데이터의 양이 크게 감소하고 효율성 향상이 매우 분명하다는 것을 알 수 있습니다.

③만 사용 제한:

SELECT UserID,Age
FROM hits_v1 
ORDER BY Age DESC LIMIT 1000;

처리되는 데이터의 양은 줄어들지 않았지만 효율성은 크게 향상됐다.

가상 기둥 구축 방지

가상 컬럼이란 : 선택 쿼리의 필드 중 테이블을 생성하지 않을 때 지정하는 필드가 가상 컬럼입니다.

예를 들어:

SELECT Income,Age,Income/Age as IncRate FROM datasets.hits_v1;

여기Income/Age는 가상 컬럼입니다. 문의 각 쿼리에는 계산이 필요하므로 성능에 큰 영향을 미칩니다

가상 열 사용:

가상 열 없음:

구별 대신 uniqCombined

uniqCombined 하단 레이어는 HyperLogLog와 유사한 알고리즘을 사용하여 구현되며 약 2%의 데이터 오류를 수용할 수 있습니다. 이 중복 제거 방법을 직접 사용하여 쿼리 성능을 향상시킬 수 있습니다

그리고 Count(distinct)는 uniqExact정확한 중복 제거를 사용합니다.


예:

explain syntax select count(distinct UserID) from hits_v1;


사용자 ID 중복 제거:

사용uniqCombined

SELECT uniqCombined(UserID) from datasets.hits_v1;

사용uniqExact

select count(distinct UserID) from hits_v1;

다른

회로 차단기 쿼리

개별 느린 쿼리로 인해 발생하는 서비스 폭증 문제를 방지하기 위해단일 쿼리에 대한 시간 제한 설정 외에도 다음을 수행할 수 있습니다. 쿼리 주기 내에서 주기적인 회로 차단기를 구성합니다. 사용자가 느린 쿼리 작업을 자주 수행하고 지정된 임계값을 초과하면 사용자는 쿼리 작업을 계속할 수 없습니다.

가상 메모리 끄기

실제 메모리와 가상 메모리 간의 데이터 교환으로 인해 쿼리 속도가 느려지므로 리소스가 허용되면 가상 메모리를 끄세요.

배치 join_use_nulls

계정별 Join_use_nulls 설정을 추가하고, 왼쪽 테이블의 레코드가 오른쪽 테이블에 존재하지 않는 경우 오른쪽 테이블의 해당 필드는 표준 SQL의 Null 값 대신 해당 필드의 해당 데이터 유형의 기본값을 반환합니다. .

일괄 작성 시 먼저 정렬

일괄적으로 데이터를 쓰는 경우 각 데이터 일괄 처리에 포함된 파티션 수를 제어해야 하며, 쓰기 전에 가져올 데이터를 정렬하는 것이 가장 좋습니다. 순서가 지정되지 않은 데이터 또는 관련된 파티션이 너무 많으면 ClickHouse가 새로 가져온 데이터를 적시에 병합할 수 없어 쿼리 성능에 영향을 미칠 수 있습니다.

CPU에 집중

일반적으로 CPU가 50% 정도에 도달하면 쿼리 변동이 발생하며, 70%에 도달하면 쿼리 타임아웃이 광범위하게 발생합니다. CPU는 가장 중요한 지표이므로 주의 깊게 살펴보아야 합니다.

CPU 사용량을 보려면 top 명령을 사용하십시오.

매개변수 의미는 다음과 같습니다.

%us: 사용자 공간 프로그램의 CPU 사용량을 나타냅니다(nice를 통해 예약되지 않음).

%sy: 시스템 공간, 주로 커널 프로그램의 CPU 사용량을 나타냅니다.

%ni: 사용자 공간에서 nice를 통해 예약된 프로그램의 CPU 사용량을 나타냅니다.

%id: 유휴 CPU

%wa: 실행 시 CPU가 io를 기다리는 시간

%hi: CPU가 처리한 하드 인터럽트 수

%si: CPU가 처리하는 소프트 인터럽트 수

%st: 가상 머신이 CPU를 훔쳤습니다.

참고: 99.0 id는 유휴 CPU, 즉 CPU가 사용되지 않음을 의미하고, 100%-99.0%=1%, 즉 시스템의 CPU 사용량이 1%입니다.

다중 테이블 연관

데이터 준비

Clickhouse의 조인 작업은 매우 비효율적이므로 공식 데이터 세트를 기반으로 두 개의 작은 테이블을 준비합니다.

#创建小表

CREATE TABLE visits_v2 
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID)
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192
as select * from visits_v1 limit 10000;

#创建 join 结果表:避免控制台疯狂打印数据
CREATE TABLE hits_v2 
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192
as select * from hits_v1 where 1=0;

여기visits_v2는 조인 테스트를 위한 작은 테이블입니다

hits_v2Join 후 데이터를 저장하는데 사용되는 결과 테이블입니다.

1=0인 조건은 Hits_v1 테이블의 구조를 유지하지만 그 안에 데이터가 없다는 점에 유의하십시오.

JOIN 대신 IN을 사용하세요.

①사용 IN:

insert into hits_v2
select a.* from hits_v1 a where a. CounterID in (select CounterID from visits_v1);

②JOIN 사용:

insert into table hits_v2
select a.* from hits_v1 a left join visits_v1 b on a. CounterID=b. CounterID;

비교 결과 IN 사용 효율성이 크게 향상되었음을 알 수 있습니다.

동시에 조인을 실행할 때 CPU 사용량을 관찰하세요.

CPU 사용량이 80%에 가까운 것으로 나타났습니다.

올바른 원리에 따른 작은 테이블

여러 테이블을 조인할 때에는 오른쪽 작은 테이블의 원칙을 만족해야 하며, 오른쪽 테이블이 연관되면 메모리에 로드되어 왼쪽 테이블과 비교됩니다.클릭하우스에서는 Left 조인인지, Right 조인인지, Inner Join은 항상 오른쪽 테이블의 각 테이블을 가져오고, 왼쪽 테이블에서 레코드를 검색하여 레코드가 있는지 확인하므로 오른쪽 테이블은 작은 테이블이어야 합니다.


다음을 비교해보세요:

①오른쪽에 작은 테이블이 있다

insert into table hits_v2
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID;

②큰 시계는 오른쪽에 있습니다

insert into table hits_v2
select a.* from visits_v2 b left join hits_v1 a on a. CounterID=b. CounterID;

실행 후 몇 초 내에 메모리 제한이 초과됩니다.

조건자 푸시다운(조인 쿼리를 자동으로 구현할 수 없음)

ClickHouse는 조인 쿼리 중에 조건자 푸시다운 작업을 적극적으로 시작하지 않으며 각하위 쿼리가 필터링 작업을 미리 완료하도록 요구합니다

조건자 푸시다운이 수행되는지 여부는 성능에 큰 영향을 미친다는 점에 유의해야 합니다.

하위 쿼리는 미리 필터링되지 않습니다.

insert into hits_v2
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID
where a.EventDate = '2014-03-17';

사전 하위 쿼리 필터링:

insert into hits_v2
select a.* from (
 select * from 
 hits_v1 
 where EventDate = '2014-03-17'
) a left join visits_v2 b on a. CounterID=b. CounterID;

효율성에 있어서는 어느 정도 차이가 있음을 알 수 있다


소위 술어 푸시다운은 조인 전 조건에 따라 필터링하여 데이터의 일부를 필터링하고 조인에 필요한 데이터의 양을 줄여 조인의 효율성을 높이는 것입니다.

위의 두 select 문에 대한 최적화 계획은 다음과 같습니다.

서브쿼리를 통한 필터링을 미리 수동으로 완료하지 않으면 조건자 푸시다운이 자동으로 수행되지 않는 것을 알 수 있다.

분산 테이블은 GLOBAL 키워드를 사용합니다.

분산된 두 테이블의 IN과 JOIN 앞에 GLOBAL 키워드를 추가해야 합니다. 이 경우쿼리 요청을 받은 노드에서는 오른쪽 테이블이 한 번만 쿼리됩니다. a> 다른 노드에 배포

GLOBAL 키워드가 추가되지 않으면 각 노드는 오른쪽 테이블에 대해 별도의 쿼리를 시작하며 오른쪽 테이블은 분산 테이블이므로 오른쪽 테이블이 총 N² 번 쿼리됩니다(N은 분포 공식 테이블의 샤드 수), 이는 쿼리 증폭이며, 이는 많은 오버헤드를 가져옵니다


공식 웹사이트 관련 설명:

자세한 사용법은 다음에서 확인할 수 있습니다.IN Operators | ClickHouse Docs

사전

조인 작업을 위해 상관 분석이 필요한 일부 비즈니스를 사전 테이블에 생성합니다. 사전 테이블은 메모리에 상주하므로 사전 테이블이 너무 크지 않아야 한다는 것이 전제입니다.

参考:사전 | 클릭하우스 문서

추천

출처blog.csdn.net/qq_51235856/article/details/134359537