ClickHouse Bitmap을 활용한 특정 기간 내 중복 제거된 사용자基数 연산

1. Bitmap이란?

Bitmap은 이진 숫자의 배열을 활용하여 데이터 집합을 표현하는 효율적인 자료구조이다. 각 비트는 특정 요소의 존재 여부를 나타내며, 집합 연산(AND, OR, NOT 등)을高速으로 수행할 수 있다.

2. ClickHouse의 RoaringBitmap

ClickHouse는 RoaringBitmap 알고리즘을 지원한다. RoaringBitmap은稀疏한 Bitmap을 효율적으로 압축하여 저장하며, 다양한 크기의 데이터셋에서 빠른 연산 성능을 제공한다. ClickHouse에서 제공하는 AggregateFunction(groupBitmap, UInt32)을 사용하면 Bitmap을 상태로聚合할 수 있다.

3. Bitmap을 활용한 비즈니스 요구사항 구현

요구사항 시나리오

특정 기간 동안 특정 브랜드를 팔로우한 사용자의 중복 제거된 수(基數)를 구해야 한다. 전통적인 데이터 웨어하우스 방식(일별 파티션)으로 구현할 경우 비용이 상당히 높으며, 온라인 쿼리로 결과를 즉시 반환하기 어렵다.

구체적인 예시:

2021-01-01에 특정 브랜드를 팔로우한 사용자: [사용자A, 사용자C, 사용자D]

2021-01-02에 특정 브랜드를 팔로우한 사용자: [사용자C, 사용자D, 사용자E]

2021-01-01부터 2021-01-02까지 해당 브랜드를 팔로우한 총 사용자 수:

[사용자A, 사용자C, 사용자D] ∪ [사용자C, 사용자D, 사용자E]
중복 제거 후: [사용자A, 사용자C, 사용자D, 사용자E]

2021-01-01부터 2021-01-02까지의 리텐션(잔존) 사용자 수:

[사용자A, 사용자C, 사용자D] ∩ [사용자C, 사용자D, 사용자E]
교집합: [사용자C, 사용자D]

2021-01-02의 신규 사용자 수(2021-01-01 대비 증가분):

[사용자C, 사용자D, 사용자E] - [사용자A, 사용자C, 사용자D]
2021-01-02에만 등장한 사용자: [사용자E]

ClickHouse로 이러한 연산을 구현하기 위해 다음 단계를 거친다:

  1. 요구사항을 충족하는 와이드 테이블(Wide Table) 생성
  2. 와이드 테이블을 기반으로 Bitmap 테이블 구현
  3. 요구사항에 맞는 쿼리 SQL 작성

3.1 와이드 테이블 구현

데이터 처리 파이프라인: Hive에서 데이터 가공 후 Datax를 통해 ClickHouse로 동기화

테이블 구조:

CREATE TABLE business_wide_table (
    dt String,
    brand String,
    phone_model String,
    device_id String
) ENGINE = MergeTree()
ORDER BY (dt, brand, phone_model);

참고: 이 단계는 Bitmap 특성과 ClickHouse 테이블 엔진에 따라 추가 최적화 가능

3.2 Bitmap 테이블 구현

ClickHouse Bitmap 함수 공식 문서 참조

요청 사항: 브랜드, 모델, 일자별 사용자 基數 조회

Bitmap 테이블 구조:

CREATE TABLE brand_model_bitmap (
    event_date LowCardinality(String),
    dimension_type LowCardinality(String),
    dimension_value LowCardinality(String),
    user_bitmap AggregateFunction(groupBitmap, UInt32)
) ENGINE = MergeTree()
ORDER BY (event_date, dimension_type, dimension_value);

ClickHouse로 Bitmap 테이블에 데이터 삽입 (JDBC 또는 SparkSQL 사용):

1) 데이터 삽입 전 파티션 삭제:

ALTER TABLE brand_model_bitmap ON CLUSTER default_cluster
DROP PARTITION event_date;

2) 각 차원별 Bitmap 삽입:

-- 브랜드 차원 데이터 삽입
INSERT INTO brand_model_bitmap
SELECT 
    event_date,
    'brand' AS dimension_type,
    brand_name,
    groupBitmapState(toUInt32(user_id)) AS user_bitmap
FROM business_wide_table
WHERE event_date = '2021-07-01'
GROUP BY brand_name;

-- 모델 차원 데이터 삽입
INSERT INTO brand_model_bitmap
SELECT 
    event_date,
    'model' AS dimension_type,
    model_name,
    groupBitmapState(toUInt32(user_id)) AS user_bitmap
FROM business_wide_table
WHERE event_date = '2021-07-01'
GROUP BY model_name;

3) 리텐션 및 신규 사용자 조회:

-- 특정 브랜드의当期 기간(2021-07-01~2021-09-29) 대 上期 기간(2021-05-01~2021-06-30) 리텐션 사용자 수 조회
SELECT bitmapCardinality(
    bitmapAnd(
        (
            SELECT groupBitmapOrState(user_bitmap)
            FROM brand_model_bitmap
            WHERE event_date >= '2021-07-01' 
              AND event_date <= '2021-09-29'
              AND dimension_type = 'brand'
              AND dimension_value = 'iphone12'
        ),
        (
            SELECT groupBitmapOrState(user_bitmap)
            FROM brand_model_bitmap
            WHERE event_date >= '2021-05-01'
              AND event_date <= '2021-06-30'
              AND dimension_type = 'brand'
              AND dimension_value = 'iphone12'
        )
    )
) AS retention_count;

성능 테스트 결과: 약 1억 명 규모의 월간 사용자 데이터에 대해 Bitmap 기반 리텐션 조회 시 2초 이내 응답

4. ClickHouse Bitmap 최적화

4.1 테이블 엔진 선택

Bitmap 연산에서 최고 성능을 보이는 엔진 두 가지:

  1. MergeTree
  2. ReplicatedMergeTree

MergeTree 엔진이 더 나은 성능을 제공하며, 분산 환경이 필요하면 ReplicatedMergeTree 사용

4.2 Bitmap 크기 최적화

일반적으로 디바이스 ID는 64비트 값이며, 해시 처리 시 Bitmap 크기가 상당히 커진다. 따라서 모든 디바이스 ID에 대해 순차적 숫자 매핑(일종의 row_number)을 부여하여 Bitmap 크기를 크게 축소한다. 이를 통해 쿼리 속도가 향상되고 CPU 및 메모리 사용량이 절감된다.

태그: ClickHouse Bitmap RoaringBitmap cardinality deduplication

6월 20일 06:46에 게시됨