상황
800억 건 규모의 Splunk PC 기능 통계를 기존 ClickHouse 통계에 추가하고 있습니다. 전체 적재에 앞서 일부 데이터로 두 가지를 확인했습니다.
- 통계 정확성: Splunk와 ClickHouse에서 같은 범위의 기능별 건수가 일치하는지 확인했습니다.
- 보관 비용: 반복되는 공통 정보를 얼마나 줄여 저장할 수 있는지 비교했습니다.
적재를 진행하면서 매분 건수 확인에 드는 CPU 비용도 문제로 드러났습니다. 10월 27일 시작한 적재 과정과, 28일까지 이어진 개선을 함께 정리했습니다.
Splunk에서 받은 보고에는 기기 ID와 기능별 실행 횟수 배열이 담깁니다. 보고 하나에 평균 6개의 기능이 있어, 기능 배열을 코드별 행으로 변환(flatten)해 저장하면 공통 정보도 행마다 반복됩니다.
기능별로 건수를 대조하고 집계할 수 있도록 ClickHouse에서 보고를 행으로 변환했습니다. 이 과정에서 반복되는 공통 정보가 보관 비용에 미치는 영향도 확인해야 했습니다.
집계 누락과 중복을 찾기 위한 Splunk와 ClickHouse 건수 비교하기
Splunk에서 하던 필드 추출을 적재 단계로 옮겨도 같은 통계가 나오는지 확인해야 했습니다.
- 집계 단위: flatten 후에는 행 수가 늘어나므로 보고 건수와 기능별 행 수를 구분해야 합니다.
- 지연 도착: 사용 시각과 수집 시각이 다릅니다. 지금 들어오는 보고가 과거 사용 기록일 수 있어, 사용 날짜만으로 적재 범위를 나누면 늦게 도착한 보고를 빠뜨릴 수 있습니다.
- 반복 적재: Incremental Materialized View (MV)는 새로 추가된 행을 반영하므로, 같은 데이터를 다시 넣으면 합계가 중복될 수 있습니다. 상세 데이터를 지워도 집계가 함께 되돌아가지는 않습니다. (Incremental MV 동작)
집계 날짜와 적재 범위는 다음과 같이 나누어 관리했습니다.
- 집계 기준: 사용 날짜에 해당하는 집계에 더합니다.
- 수집 기준: 인덱싱 시각으로 범위를 나누고, 늦게 검색되는 보고는 이미 반영한 데이터와 구분해 빠진 것만 추가합니다.
매분 동기화에서는 같은 인덱싱 시각 범위에 대해 적재 전후를 확인했습니다.
- 적재 전: 해당 범위에 이미 들어온 데이터가 있는지 확인했습니다.
- 적재 후: Splunk 인덱스별 건수가 맞는지 확인했습니다.
건수가 맞지 않는 데이터가 집계에 누적되지 않도록, 상세 테이블에 반영하기 전에 임시 테이블에서 검증했습니다. 수집 범위와 기능 단위를 맞춰 대조한 일부 구간에서는 건수가 일치했습니다.
반복되는 필드의 저장 용량을 줄이기 위한 정렬 키 순서 바꾸기
flatten하면 기기 ID(uuid), 보고 ID(report_id), 인덱싱 시각(indextime)이 각 행에 반복됩니다. 이 값들이 가까이 모이도록 정렬을 바꾸고 저장 공간을 비교했습니다.
상세 테이블을 기능 코드 중심으로 정렬하지 않으므로 특정 기능의 상세 조회는 더 많은 행을 읽을 수 있습니다. 기능별 통계는 앱과 기능 코드 중심으로 정렬한 일별, 월별 집계에서 조회하므로, 상세 테이블은 보관 효율에 맞췄습니다. (정렬 키와 조회 범위)
같은 수집 인덱스와 사용 날짜 안에서 기기와 보고 순으로 행을 모으면, 기기 ID 열에서도 같은 값이 이어집니다. 그림의 ‘원본 값 + 참조’는 ZSTD가 반복되는 바이트의 저장을 줄이는 효과를 단순화한 표현입니다. (정렬과 압축, ZSTD 형식)
같은 1,391만 행에서 정렬 변경과 ZSTD 적용의 효과를 나눠 비교했습니다. 기기 ID는 정렬 변경만으로 약 90.4% 줄어, 압축 방식보다 정렬 순서가 더 큰 영향을 줬습니다.
| 열 | 기존 정렬 | 정렬 변경 | 정렬 변경 + ZSTD |
|---|---|---|---|
| 기기 ID | 208MiB | 20MiB | 16.5MiB |
| 보고 ID | 106MiB | 26MiB | 21MiB |
| 인덱싱 시각 | 49MiB | 7.7MiB | 6.2MiB |
매분 건수 확인의 CPU 비용을 줄이기 위한 인덱스 추가
4코어 운영 서버에서 1분 주기 동기화와 백필을 실행하자 CPU 사용률이 최대 100%까지 증가했습니다. 집계 비용을 줄인 뒤에도 약 55%가 남았습니다.
query_log를 확인하니 적재 전후의 건수 확인 SELECT가 매분 수십억 행을 읽고 있었습니다. 확인 범위는 indextime의 1분 구간이었지만, 정렬 키와 파티션으로는 이 조건에 맞춰 읽기 범위를 줄이지 못했습니다. 백필로 데이터가 쌓일수록 확인 비용도 증가했습니다.
확인 주기나 조건을 바꾸면 다음 문제가 있었습니다.
- 동기화 주기 늘리기: 통계 반영이 늦어집니다.
- 사용 날짜 조건 추가: PC 시계가 잘못된 보고를 빠뜨릴 수 있습니다.
28일에는 기존 주기와 확인 조건을 유지하면서 indextime에 minmax 인덱스를 추가하고 기존 데이터에도 적용했습니다. 블록별 최솟값과 최댓값을 확인해 조회 범위와 겹치지 않는 블록을 제외하는 방식입니다. (건너뛰기 인덱스 동작)
확인 조건을 유지한 채 읽을 블록을 줄여 매분 검증 비용을 낮췄습니다. 운영 쿼리 1회 기준의 실행 시간입니다.
| 쿼리 | 적용 전 | 적용 후 |
|---|---|---|
| 적재 전 확인 | 14초 | 160ms |
| 적재 후 인덱스별 확인 | 6.6초 | 100ms |
- 서버 사용자 CPU: 약 1.8코어 → 0.13코어
- 추가 저장 공간: 인덱스 8.48MiB
적재한 데이터를 어떤 공통 집계로 남기고 대시보드에서 조회할지는 집계 비용과 조회 병목을 다룬 글에 정리했습니다.