LogoSkills

clickhouse-backend-rules

ClickHouse 백엔드 타입 안전성 및 쿼리 규칙(scalar casting 등). ClickHouse 쿼리를 작성하거나 scalar 타입 캐스팅 및 쿼리 오류를 수정할 때 사용합니다.

ClickHouse Backend Rules#

한마디로#

이 문서는 대용량 데이터 분석 창고인 ClickHouse를 다룰 때 자주 생기는 실수를 막아 주는 안전 수칙 모음입니다. 마치 큰 물류 창고에서 물건을 넣을 때와 꺼낼 때 규칙을 정해 두어 물건이 사라지거나 엉뚱한 칸에 들어가는 일을 막는 것과 같습니다. 개발자(또는 AI)가 분석 기능을 만들 때 이 수칙을 따르면 숫자가 어긋나거나 데이터가 안 보이는 흔한 버그를 예방할 수 있습니다.

무엇을·언제#

  • 무엇을 해 주나요: 숫자 데이터를 잘못 읽어 생기는 오류, 저장한 곳과 찾는 곳이 달라 데이터가 안 보이는 문제, 빈 값(NULL)이나 시간대(한국 시간) 처리 실수를 막아 줍니다.
  • 언제 작동하나요: 통계나 분석 같은 새 기능을 ClickHouse 데이터로 만들 때 적용합니다. 예를 들어 "평균 읽은 시간", "사용자 이벤트 수" 같은 집계 화면을 만들 때 쓰입니다.
  • 결과적으로: 분석 화면의 숫자가 정확해지고, 데이터가 빠지거나 시간이 어긋나는 일이 줄어듭니다.

핵심 용어#

용어쉬운 설명
ClickHouse아주 많은 데이터를 빠르게 모아 분석하기 위한 데이터 저장 창고(분석 전용 데이터베이스).
CDC "변경 사항 따라 옮기기". 한쪽 데이터베이스(PostgreSQL)에 저장하면 그 변화를 자동으로 분석 창고(ClickHouse)로 복사해 주는 방식.
PostgreSQL거래 기록처럼 정확함이 중요한 데이터를 안전하게 보관하는 일반 데이터베이스.
NULL"값이 비어 있음"을 뜻하는 상태. 그냥 두면 0처럼 잘못 계산될 수 있어 따로 처리해 줘야 함.
타입(type) 캐스팅데이터의 형태(정수/소수 등)를 다른 형태로 바꾸는 일. 잘못 바꾸면 오류가 남.
트랜잭션여러 작업을 "전부 성공 아니면 전부 취소"로 묶어 데이터가 어중간하게 남지 않게 하는 안전장치.
UTC / KSTUTC는 세계 표준 시간, KST는 한국 시간. 저장은 UTC로 하고 화면에는 한국 시간으로 바꿔 보여 줌.

ClickHouse 백엔드 개발 시 발생하는 버그를 방지하기 위한 규칙입니다.

타입 안전성 규칙#

scalar() 메서드 타입 캐스팅#

문제: ClickHouse scalar<double>() 메서드는 값이 0일 때 int를 반환합니다.

// ❌ WRONG: 값이 0일 때 int 반환 → 타입 에러
final avgTime = await client.scalar<double>(
  'SELECT avg(reading_time) FROM ...',
);  // 결과가 0이면 int(0) 반환 → double 캐스팅 실패!

// ✅ CORRECT: num으로 받아서 toDouble() 변환
final avgTimeRaw = await client.scalar<num>(
  'SELECT avg(reading_time) FROM ...',
);
final avgTime = avgTimeRaw?.toDouble() ?? 0.0;

헬퍼 함수 패턴 (권장)#

// analytics_service.dart 참조
double _toDouble(Object? value) {
  if (value == null) return 0;
  if (value is double) return value;
  if (value is int) return value.toDouble();
  if (value is num) return value.toDouble();
  return 0;
}

// 사용 예시
final result = await client.scalar<num>('SELECT sum(amount) FROM ...');
final amount = _toDouble(result);

Row 데이터 처리#

// ❌ WRONG: 직접 캐스팅
final rows = await client.select('SELECT value FROM ...');
final value = rows.first['value'] as double;  // int일 수 있음!

// ✅ CORRECT: 헬퍼 함수 사용
final rows = await client.select('SELECT value FROM ...');
final value = _toDouble(rows.first['value']);

CDC 데이터 흐름 규칙#

데이터 저장과 조회 테이블 일치 확인#

문제: 데이터를 저장하는 테이블과 조회하는 테이블이 다르면 데이터가 보이지 않습니다.

// ❌ WRONG: 저장과 조회 테이블 불일치
// EventTrackingService → ClickHouse `events` 테이블에 저장
await eventService.track('screen_view', ...);

// AnalyticsService → ClickHouse `user_event` 테이블에서 조회
final result = await client.select('SELECT * FROM user_event');
// 결과: 빈 데이터! (다른 테이블)

// ✅ CORRECT: CDC 흐름 사용
// PostgreSQL `user_event` → CDC → ClickHouse `user_event`
await UserEvent.db.insertRow(session, event);
final result = await client.select('SELECT * FROM user_event');

CDC vs 직접 저장 선택 기준#

방식사용 시점장점단점
PostgreSQL CDC 트랜잭션 일관성 필요, 감사 추적 필요 ACID, 감사 로그, 백업 지연 시간 (1-5초)
직접 ClickHouse고빈도 이벤트, 실시간 분석 전용빠른 저장트랜잭션 없음
// CDC 방식 (권장 - 대부분의 비즈니스 이벤트)
final event = UserEvent(
  userId: userId,
  eventName: 'screen_view',
  properties: jsonEncode(properties),
  timestamp: DateTime.now(),
);
await UserEvent.db.insertRow(session, event);

// 직접 저장 방식 (고빈도 이벤트 전용)
await eventService.track('mouse_move', properties: {...});

ClickHouse 쿼리 규칙#

NULL 처리#

// ClickHouse에서 NULL은 기본적으로 0 또는 빈 문자열로 처리됨
// 명시적 NULL 처리 권장

// ❌ WRONG: NULL 미처리
SELECT avg(value) FROM table  // NULL 포함 시 예상과 다른 결과

// ✅ CORRECT: NULL 명시적 처리
SELECT avgIf(value, value IS NOT NULL) FROM table
SELECT coalesce(avg(value), 0) FROM table

날짜/시간 처리#

// ClickHouse는 UTC 기준으로 저장
// 한국 시간(KST) 변환 필요

// ✅ 시간대 변환
SELECT toDateTime(timestamp, 'Asia/Seoul') as local_time FROM ...

// ✅ 날짜 그룹핑 (한국 시간 기준)
SELECT toDate(timestamp, 'Asia/Seoul') as date, count() FROM ...
GROUP BY date

체크리스트#

새로운 분석 기능 구현 시:

  • scalar<num>() 사용 + _toDouble() 헬퍼 적용
  • 저장 테이블과 조회 테이블 일치 확인
  • CDC 흐름 vs 직접 저장 결정
  • NULL 처리 명시
  • 시간대(KST) 변환 적용

관련 파일#

  • backend/kobic_server/lib/src/feature/analytics/service/analytics_service.dart - _toDouble() 헬퍼 패턴
  • backend/kobic_server/lib/src/feature/analytics/endpoint/screen_view_endpoint.dart - CDC 방식 예시
  • .claude/skills/clickhouse/SKILL.md - CDC 테이블 목록