Tinybird로 실시간 분석 API 만들기: ClickHouse 기반 수집, Pipes SQL 변환, 엔드포인트, 로그 분석
이 글의 핵심
Tinybird는 ClickHouse를 기반으로 스트리밍 수집·SQL 변환·저지연 API를 한 플랫폼에서 제공하는 실시간 데이터 분석 서비스입니다. Data Source, Pipe, 엔드포인트, 대시보드 연동, 로그 분석 설계까지 한 번에 정리합니다.
이 글의 핵심
Tinybird는 이벤트·로그·메트릭을 수집하고, ClickHouse SQL로 변환한 결과를 HTTP API로 노출하는 관리형 실시간 분석 플랫폼입니다. “데이터 레이크에 넣고 나중에 배치로 돌린다”가 아니라, 운영 중인 제품이 바로 소비할 수 있는 지표·집계 결과를 만드는 데 초점이 맞춰져 있습니다.
이런 도구가 필요해지는 상황
API 트래픽 로그, 결제 이벤트, 게임 텔레메트리처럼 분당 오류율이나 지역별 지연을 바로 봐야 하는 데이터는 배치 파이프라인으로 처리하면 대시보드가 몇 시간 전 상태를 보여 줍니다. 배치는 신뢰도가 높지만, 장애 대응이나 A/B 판단에는 초~분 단위의 지연이 필요합니다.
그렇다고 ClickHouse를 직접 운영하면 스키마 마이그레이션, 샤딩, 백업, 쿼리 폭주 대응, 권한 분리가 모두 팀의 일이 됩니다. 또 데이터 팀이 요청이 올 때마다 임시 뷰와 크론 스크립트를 만들어 주다 보면, 어떤 숫자가 어떤 쿼리에서 나왔는지 추적하기 어려워집니다. Tinybird는 수집, SQL 변환, 버전과 토큰이 붙은 API 엔드포인트를 한 서비스로 묶어 이 문제를 줄이려는 제품입니다.
Tinybird란? 핵심 개념
제품이 해결하는 문제
Tinybird는 다음을 하나의 워크스페이스에서 이어 붙입니다.
| 구분 | 역할 |
|---|---|
| Data Source | 수집된 데이터가 랜딩하는 테이블(스키마·엔진·TTL 등 메타 포함) |
| Pipe | ClickHouse SQL로 변환·조인·집계하는 노드들의 유향 그래프(DAG) |
| Endpoint | Pipe의 최종 노드를 REST API로 노출 (쿼리 파라미터·JWT 등과 결합) |
| 토큰·워크스페이스 | 운영·앱·읽기 전용 등 접근 분리 |
즉, “저장소 + 변환 파이프라인 + API 게이트웨이”를 관리형으로 제공합니다.
왜 ClickHouse인가
ClickHouse는 컬럼 지향·압축·벡터화 실행에 강해 시계열·이벤트 로그·집계 워크로드에 적합합니다. Tinybird는 이를 기반으로 대량 append와 집계 쿼리를 동시에 만족시키려는 설계입니다. 사용자는 표준 SQL에 가까운 문법으로 Pipe를 작성하며, 엔진 세부(머지 트리, 파티션 키 등)는 Data Source 정의에서 다룹니다.
용어 정리
- Ingestion(수집): 애플리케이션·스트림·오브젝트 스토리지 등에서 이벤트 행을 Data Source로 넣는 과정입니다.
- 노드와 Materialized View: Pipe 안의 노드는 앞 노드의 결과를 이름으로 참조하는 쿼리 단계이며, 매 요청마다 다시 계산됩니다. 반면 Materialized View용 Pipe는 데이터가 들어오는 시점에 변환 결과를 별도 Data Source에 써 두므로, 읽기 시점의 계산량을 줄일 때 씁니다.
- 엔드포인트 SLA: “API가 얼마나 자주, 얼마나 빨리 응답해야 하는가”는 파티션·인덱스·사전 집계·캐시와 함께 정의됩니다.
ClickHouse 기반 아키텍처 이해
계층 구조(개념도)
애플리케이션과 분석 소비자 사이에 Tinybird가 끼면 대략 다음과 같습니다.
[프로듀서: 서비스·에이전트·스트림]
│ (HTTPS / Kafka / S3 …)
▼
[Tinybird Ingestion] ──► [Data Source: 원시 이벤트]
│
▼
[Pipe: SQL 변환 · 집계 · 조인]
│
▼
[Endpoint: REST/토큰] ──► [대시보드·알림·사내 포털]
설계 시 유의할 트레이드오프
- 정규화 vs 폭넓은 이벤트: 로그 한 줄에 컨텍스트를 넣을수록 조인은 줄지만 스키마 변경 비용이 커집니다.
- 실시간성 vs 비용: 모든 이벤트를 원시 저장하면 저장 비용이 커지고, 샘플링·집계 테이블을 두면 세부 드릴다운이 제한됩니다.
- 동일 API, 다른 소비자: 모바일 앱은 가벼운 JSON, BI는 대용량 CSV를 원할 수 있어 엔드포인트를 쪼개 파라미터와 응답 스키마를 분리하는 편이 안전합니다.
Data Sources와 Ingestion
Data Source의 역할
Data Source는 “어떤 컬럼으로, 어떤 타입으로, 얼마나 오래 보관할지”를 선언하는 첫 관문입니다. 여기서 파티션 키(보통 시간), 정렬 키, TTL을 잘못 잡으면 이후 Pipe가 아무리 깔끔해도 풀 스캔이 됩니다.
Ingestion 경로(개념)
실무에서는 다음과 같은 경로가 자주 등장합니다.
- HTTPS 이벤트 API: 서비스 코드에서 배치·스트림으로 POST
- 스트리밍 커넥터(환경에 따라 Kafka 등): 초당 많은 이벤트를 버퍼링해 적재
- 오브젝트 스토리지: 과거 로그 백필(backfill)에 적합
스키마 설계 예시 (HTTP 액세스 로그)
아래는 웹/API 액세스 로그를 위한 최소 스키마 예시입니다. 실제 프로덕션에서는 request_id, user_id_hash, trace_id 등을 추가합니다.
Tinybird에서는 CREATE TABLE 대신 .datasource 파일로 스키마를 정의하고, CLI로 워크스페이스에 반영합니다. 컬럼 뒤의 json:$.필드 경로는 NDJSON 이벤트에서 값을 꺼낼 위치입니다.
# datasources/access_logs.datasource
SCHEMA >
`ts` DateTime64(3) `json:$.ts`,
`method` LowCardinality(String) `json:$.method`,
`path` String `json:$.path`,
`status` UInt16 `json:$.status`,
`latency_ms` UInt32 `json:$.latency_ms`,
`region` LowCardinality(String) `json:$.region`
ENGINE "MergeTree"
ENGINE_PARTITION_KEY "toYYYYMM(ts)"
ENGINE_SORTING_KEY "region, ts"
ENGINE_TTL "toDateTime(ts) + INTERVAL 90 DAY"
월 단위 파티션은 오래된 데이터를 파티션째 지우기 쉽게 해 주고, TTL은 보관 기간을 넘은 행을 머지 과정에서 정리합니다. 정렬 키는 ClickHouse의 희소 기본 인덱스가 되므로 자주 거는 필터(지역, 시간)를 앞에 둘수록 읽는 그래뉼이 줄어듭니다. 다만 파티션을 너무 잘게 쪼개면(예: 일 단위에 보관 기간이 길면) 파트 수가 늘어 오히려 머지와 쿼리가 느려집니다. LowCardinality는 서로 다른 값이 적은 문자열을 사전 인코딩해 저장 공간과 비교 비용을 줄입니다.
수집 단계에서의 운영 팁
- 이벤트 크기 제한: 거대 JSON 한 덩어리보다 필드 분해가 쿼리·압축에 유리한 경우가 많습니다.
- 중복 제거: 네트워크 재시도로 중복 행이 들어올 수 있으므로, 이벤트 ID를 함께 보내 두어야 나중에 중복을 걸러 낼 수 있습니다.
- PII 분리: 이메일·전화번호는 해시·토큰화 후 적재하며, 원문은 별도 보안 저장소에 둡니다.
Pipes와 SQL 변환
Pipe란?
Pipe는 여러 SQL 노드로 이루어진 변환 파이프라인입니다. 앞 노드의 결과는 다음 노드에서 CTE처럼 참조합니다. 목적은 다음과 같습니다.
- 필터·정제: 봇 트래픽 제거, 상태 코드 범위 제한
- 집계: 분·시간 버킷, 지역별 카운트, 백분위(latency p95)
- 조인: 차원 테이블(제품 ID → 카테고리)과의 딕셔너리 조인
예시: 5분 단위 오류율
원시 access_logs에서 5xx 비율을 구하는 Pipe입니다. 첫 노드에 %로 시작하는 템플릿 문법을 쓰면 엔드포인트의 쿼리 파라미터를 SQL에 안전하게 넣을 수 있습니다.
# pipes/error_rate_5m.pipe
NODE buckets
SQL >
%
SELECT
toStartOfFiveMinutes(ts) AS bucket,
region,
countIf(status >= 500) AS err_cnt,
count() AS total
FROM access_logs
WHERE ts >= {{DateTime(start, '2026-04-17 00:00:00')}}
{% if defined(region) %}
AND region = {{String(region)}}
{% end %}
GROUP BY bucket, region
NODE endpoint
SQL >
SELECT bucket, region, err_cnt / total AS error_rate
FROM buckets
WHERE total > 0
ORDER BY bucket
두 번째 노드는 첫 노드를 이름(buckets)으로 참조하고, 마지막 노드가 엔드포인트로 노출됩니다. GROUP BY로 만든 버킷은 행이 있어야 생기므로 total이 0인 행은 실제로는 나오지 않지만, 조건을 남겨 두면 이후 쿼리를 바꿔도 0으로 나누기를 막을 수 있습니다. 반대로 요청이 하나도 없던 5분 구간은 결과에서 빠지므로, 차트에서 0으로 그려야 한다면 WITH FILL로 빈 버킷을 채우거나(지역별로 나뉜 결과에는 그대로 맞지 않으므로 주의) 프론트엔드에서 채웁니다. 버킷 크기(1분 대 5분)는 대시보드가 얼마나 출렁이는지를 바꾸므로 운영 쪽과 합의해 정합니다.
SQL 작성 시 주의할 점
쿼리 안에서 now()를 쓰면 같은 요청도 실행 시각마다 결과가 달라 테스트를 재현하기 어렵습니다. 위 예제처럼 시간 범위를 파라미터로 받으면 테스트와 캐시가 모두 단순해집니다. MergeTree 테이블에서는 ClickHouse가 선택도가 높은 WHERE 조건을 자동으로 PREWHERE로 옮기므로(optimize_move_to_prewhere, 기본 활성) 직접 PREWHERE를 쓸 일은 많지 않습니다. 성능에 더 큰 영향을 주는 것은 필터가 정렬 키 앞쪽 컬럼과 맞는지, 그리고 자주 쓰는 집계를 Materialized View로 미리 만들어 두었는지입니다.
API Endpoints 생성
왜 엔드포인트인가
BI 툴이든 프론트엔드든 결국 “잘 정의된 JSON을 안정적으로 가져오면” 됩니다. Tinybird는 Pipe의 결과를 HTTP 엔드포인트로 노출해 버전 가능한 계약을 만듭니다.
설계 포인트
엔드포인트가 받는 파라미터는 Pipe의 템플릿에 선언된 것뿐이므로, start, region, limit처럼 필요한 필터를 명시적으로 정의합니다. 토큰은 엔드포인트 읽기 권한만 가진 것을 대시보드에 주고, Data Source에 쓰는 적재 토큰은 백엔드에만 둡니다. 응답 필드 이름을 바꿔야 할 때는 기존 Pipe를 고치기보다 error_rate_5m_v2처럼 새 엔드포인트를 만들어 기존 클라이언트를 깨뜨리지 않는 편이 안전합니다.
호출 예시 (개념)
curl -s \
-H "Authorization: Bearer ${TB_READ_TOKEN}" \
"https://api.tinybird.co/v0/pipes/error_rate_5m.json?region=ap-northeast-2&start=2026-04-17%2000:00:00"
API 호스트는 워크스페이스가 있는 리전에 따라 다르므로(예: api.us-east.tinybird.co) 워크스페이스 설정에서 확인합니다. 브라우저에서 직접 호출하는 공개 대시보드라면 읽기 토큰이 그대로 노출되므로, 백엔드 프록시를 거치게 하거나 만료 시간과 고정 파라미터를 넣은 JWT를 발급해 노출 범위를 좁힙니다.
실시간 대시보드
Tinybird의 위치
Tinybird 자체가 화려한 차트 UI에 초점을 맞춘 제품이라기보다, 저지연 데이터를 공급하는 백엔드에 가깝습니다. 따라서 실시간 대시보드는 보통 다음과 조합합니다.
- Metabase / Grafana / 자체 React 대시보드: 엔드포인트를 JSON 데이터 소스로 연결
- 사내 포털: iframe 또는 API 프록시로 임베드
“실시간”의 의미 정하기
Tinybird 엔드포인트는 요청-응답 HTTP API라서, 대시보드의 “실시간”은 보통 10초 같은 주기로 폴링하는 것을 뜻합니다. 폴링 주기는 앞단에 둔 캐시의 TTL과 맞춰야 의미가 있습니다. 임계값을 넘을 때 바로 알려 주는 푸시가 필요하다면 Tinybird 쪽이 아니라 주기적으로 엔드포인트를 확인해 Slack 등으로 보내는 알림 레이어에서 설계합니다.
화면 쪽에서는 새 응답이 올 때까지 이전 값을 유지하면 쿼리 지연이 눈에 덜 띕니다. 또 지역·디바이스 필터 조합이 늘수록 같은 요청이 반복될 확률이 낮아져 캐시가 덜 맞으므로, 자주 쓰는 조합은 Materialized View로 미리 집계해 두는 편이 좋습니다.
실전 로그 분석 시스템
목표 KPI를 먼저 고정
로그 파이프라인은 “무엇을 보여줄지” 없이 만들면 컬럼 폭발만 남습니다. 예시 KPI는 다음과 같습니다.
- 가용성: 5xx 비율, 지역별 에러
- 성능: p95/p99 지연, 타임아웃 카운트
- 비즈니스: 결제 실패율, 퍼널 이탈
파이프라인 단계
- 수집: API 게이트웨이·서비스 메시·에이전트에서 구조화 로그(JSON)로 통일
- 정제 Pipe: 봇·헬스체크 제거, 샘플링 정책 적용
- 집계 Pipe: 분 단위 지표, 일별 롤업 Data Source로 이중 저장
- 엔드포인트: 운영 대시보드용 경량 JSON, 분석 팀용 상세 드릴다운 분리
알림과 연동
임계값은 엔드포인트 + 스케줄러(크론·워크플로 엔진)로 주기적으로 호출해 슬랙·PagerDuty로 보냅니다. 이때 같은 Pipe를 쓰면 대시보드 숫자와 알림 숫자가 일치합니다.
장애 시나리오
수집이 밀리면 이벤트가 늦게 도착하므로, 이벤트 발생 시각과 적재 시각을 별도 컬럼으로 두어야 늦게 온 데이터를 구분하고 다시 집계할 수 있습니다. 인기 대시보드 때문에 쿼리가 몰리면 해당 지표를 Materialized View로 옮겨 요청당 읽는 데이터 양을 줄입니다.
한계
Tinybird는 ClickHouse 위의 서비스이므로 행 단위 갱신이나 트랜잭션이 필요한 워크로드에는 맞지 않습니다. 같은 이벤트가 재시도로 두 번 들어오는 경우도 삽입 시점에 막아 주지 않으므로, 이벤트 ID를 두고 ReplacingMergeTree 엔진이나 쿼리 시점의 중복 제거로 처리해야 합니다. 이벤트에 schema_version 필드를 넣어 두면 스키마를 바꿀 때 신구 이벤트를 구분하기 쉽습니다. 고객별 데이터 격리가 필요하면 토큰의 고정 파라미터나 워크스페이스 분리를 설계해야 하고, 데이터 레지던시 요구가 있다면 워크스페이스 리전을 미리 확인해야 합니다.
참고
- Tinybird 공식 문서 — 최신 커넥터·CLI·가격 정책은 공식 문서를 기준으로 합니다.
- ClickHouse 문서 — SQL 함수·엔진·튜닝의 근거가 됩니다.