AggregatingMergeTree 테이블 엔진
AggregatingMergeTree 테이블 엔진
이 엔진은 MergeTree에서 상속하며 데이터 파트 병합 로직을 변경해요. ClickHouse는 같은 기본 키(더 정확히는 같은 정렬 키)를 가진 모든 행을, 집계 함수 상태의 조합을 저장하는 단일 행(단일 데이터 파트 내에서)으로 대체해요.
AggregatingMergeTree 테이블은 증분 데이터 집계에 사용할 수 있어요. 집계 materialized view에도 사용할 수 있어요. AggregatingMergeTree과 Aggregate 함수를 사용하는 예시는 아래 비디오에서 볼 수 있어요.
엔진은 다음 타입의 모든 컬럼을 처리해요.
AggregatingMergeTree는 행 수를 자릿수 단위로 줄인다면 사용하기 적절해요.
출처: 문서
본문
테이블 생성하기
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = AggregatingMergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[TTL expr]
[SETTINGS name=value, ...]
요청 매개변수에 대한 설명은 request description을 참고해요.
쿼리 절(Query clauses)
AggregatingMergeTree 테이블을 만들 때 MergeTree 테이블을 만들 때와 같은 절들이 필요해요.
SELECT와 INSERT
데이터를 삽입하려면 집계 -State 함수와 함께 INSERT SELECT 쿼리를 사용해요. AggregatingMergeTree 테이블에서 데이터를 선택할 때는 GROUP BY 절과 데이터를 삽입할 때와 같은 집계 함수를 사용하되 -Merge 접미사를 붙여요.
SELECT 쿼리 결과에서 AggregateFunction 타입의 값은 모든 ClickHouse 출력 형식에 대해 구현별 이진 표현을 가져요. 예를 들어 SELECT 쿼리로 데이터를 TabSeparated 형식으로 덤프하면 그 덤프를 INSERT 쿼리로 다시 로드할 수 있어요.
집계 materialized view 예시
다음 예시는 test라는 데이터베이스가 있다고 가정해요. 없으면 아래 명령으로 만들어요.
CREATE DATABASE test;
이제 원시 데이터를 담는 test.visits 테이블을 만들어요.
CREATE TABLE test.visits
(
StartDate DateTime64 NOT NULL,
CounterID UInt64,
Sign Nullable(Int32),
UserID Nullable(Int32)
) ENGINE = MergeTree ORDER BY (StartDate, CounterID);
다음으로 총 방문 수와 고유 사용자 수를 추적하는 AggregationFunction들을 저장할 AggregatingMergeTree 테이블이 필요해요. test.visits 테이블을 감시하고 AggregateFunction 타입을 사용하는 AggregatingMergeTree materialized view를 만들어요.
CREATE TABLE test.agg_visits (
StartDate DateTime64 NOT NULL,
CounterID UInt64,
Visits AggregateFunction(sum, Nullable(Int32)),
Users AggregateFunction(uniq, Nullable(Int32))
)
ENGINE = AggregatingMergeTree() ORDER BY (StartDate, CounterID);
test.visits에서 test.agg_visits를 채우는 materialized view를 만들어요.
CREATE MATERIALIZED VIEW test.visits_mv TO test.agg_visits
AS SELECT
StartDate,
CounterID,
sumState(Sign) AS Visits,
uniqState(UserID) AS Users
FROM test.visits
GROUP BY StartDate, CounterID;
test.visits 테이블에 데이터를 삽입해요.
INSERT INTO test.visits (StartDate, CounterID, Sign, UserID)
VALUES (1667446031000, 1, 3, 4), (1667446031000, 1, 6, 3);
데이터는 test.visits와 test.agg_visits 둘 다에 삽입돼요. 집계된 데이터를 얻으려면 materialized view test.visits_mv에서 SELECT ... GROUP BY ... 같은 쿼리를 실행해요.
SELECT
StartDate,
sumMerge(Visits) AS Visits,
uniqMerge(Users) AS Users
FROM test.visits_mv
GROUP BY StartDate
ORDER BY StartDate;
┌───────────────StartDate─┬─Visits─┬─Users─┐
│ 2022-11-03 03:27:11.000 │ 9 │ 2 │
└─────────────────────────┴────────┴───────┘
test.visits에 몇 개의 레코드를 더 추가해요. 이번에는 레코드 중 하나에 다른 타임스탬프를 사용해요.
INSERT INTO test.visits (StartDate, CounterID, Sign, UserID)
VALUES (1669446031000, 2, 5, 10), (1667446031000, 3, 7, 5);
SELECT 쿼리를 다시 실행하면 다음 출력이 반환돼요.
┌───────────────StartDate─┬─Visits─┬─Users─┐
│ 2022-11-03 03:27:11.000 │ 16 │ 3 │
│ 2022-11-26 07:00:31.000 │ 5 │ 1 │
└─────────────────────────┴────────┴───────┘
어떤 경우에는 집계 비용을 삽입 시간에서 병합 시간으로 옮기기 위해 삽입 시 행을 사전 집계하지 않으려 할 수 있어요. 보통은 오류를 피하기 위해 materialized view 정의의 GROUP BY 절에 집계에 포함되지 않는 컬럼을 포함해야 해요. 그러나 optimize_on_insert = 0(기본적으로 켜져 있음) 설정과 함께 initializeAggregation 함수를 사용하면 이를 달성할 수 있어요. 이 경우 GROUP BY는 더 이상 필요하지 않아요.
CREATE MATERIALIZED VIEW test.visits_mv TO test.agg_visits
AS SELECT
StartDate,
CounterID,
initializeAggregation('sumState', Sign) AS Visits,
initializeAggregation('uniqState', UserID) AS Users
FROM test.visits;
initializeAggregation을 사용하면 그룹화 없이 각 개별 행에 대해 집계 상태가 만들어져요. 각 소스 행은 materialized view에서 하나의 행을 만들고, 실제 집계는 나중에 AggregatingMergeTree가 파트를 병합할 때 발생해요. 이는 optimize_on_insert = 0일 때만 사실이에요.
Tuple 요소 집계
allow_tuple_element_aggregation 설정이 활성화되면 Tuple 컬럼이 재귀적으로 평면화되어 각 리프 요소가 독립적으로 집계에 참여해요. 이는 Tuple 내부의 AggregateFunction 또는 SimpleAggregateFunction 하위 컬럼이 마치 최상위 컬럼인 것처럼 각자의 함수에 따라 집계된다는 뜻이에요.
정렬 키의 Tuple에 속하는 하위 컬럼은 집계에서 제외돼요. 비집계 하위 컬럼은 일반 컬럼으로 취급돼요 (첫 값이 유지돼요).
이 설정은 불변(immutable)이며 테이블 생성 시 지정해야 해요.
CREATE TABLE agg_tuples
(
key UInt32,
metrics Tuple(
total_visits SimpleAggregateFunction(sum, UInt64),
unique_users SimpleAggregateFunction(max, UInt64)
)
) ENGINE = AggregatingMergeTree()
ORDER BY key
SETTINGS allow_tuple_element_aggregation = 1;
INSERT INTO agg_tuples VALUES (1, (100, 5));
INSERT INTO agg_tuples VALUES (1, (200, 8));
INSERT INTO agg_tuples VALUES (2, (50, 3));
OPTIMIZE TABLE agg_tuples FINAL;
SELECT key, metrics.total_visits, metrics.unique_users FROM agg_tuples ORDER BY key;
┌─key─┬─metrics.total_visits─┬─metrics.unique_users─┐
│ 1 │ 300 │ 8 │
│ 2 │ 50 │ 3 │
└─────┴──────────────────────┴──────────────────────┘
total_visits는 sum으로 집계되고 (100 + 200 = 300), unique_users는 max로 집계돼요 (max(5, 8) = 8).