본문 바로가기
클라우드2026년 9월 12일19분 읽기

Cloudflare Town Lake 공개 — Trino·Iceberg·R2로 짠 레이크하우스 뜯어보기

YS
김영삼
조회 10
Cloudflare Town Lake 공개 — Trino·Iceberg·R2로 짠 레이크하우스 뜯어보기

Cloudflare가 사내 통합 데이터 플랫폼 'Town Lake'를 공개했다. Trino(쿼리 엔진)·Iceberg(테이블 포맷)·R2(오브젝트 스토리지)·DataHub(메타데이터 카탈로그)를 엮은 전형적인 레이크하우스 아키텍처로, 하나의 SQL로 Postgres·ClickHouse·R2 위의 Iceberg 테이블을 중간 적재 없이 조인한다.

Cloudflare 엔지니어링 블로그에 따르면 이 플랫폼은 측정 기간 동안 빌링 관련 쿼리만 91,760건을 처리했고, 이는 전체 쿼리의 약 53%에 해당한다. 데이터 엔지니어 입장에서 흥미로운 건 화려한 신기술이 아니라, '오브젝트 스토리지 위의 개방형 테이블 포맷 + 연합 쿼리 엔진 + 거버넌스 메타데이터'라는 지금의 정석 조합을 대규모 실서비스가 어떻게 조립했는지다. 이 글은 그 아키텍처를 데이터 엔지니어 관점에서 뜯어본다.

솔직히 말하면 나는 "우리도 레이크하우스 갑시다" 같은 말을 별로 안 믿는 편이다. 대부분은 S3에 Parquet 파일 몇 개 던져놓고 레이크하우스라고 부른다. 그런데 Cloudflare가 이번에 푼 Town Lake 사례는 결이 좀 다르다. 왜 Iceberg를 골랐는지, 연합 쿼리로 무엇을 안 하기로 했는지, 거버넌스를 어떻게 코드로 강제했는지가 구체적으로 나와 있어서, 레이크하우스를 실제로 굴려본 사람이라면 고개를 끄덕일 대목이 많다.

한눈에 보는 Town Lake

먼저 숫자와 구성요소부터 정리하자. 아래 표는 Cloudflare 블로그와 InfoQ 보도에서 확인된 수치를 모은 것이다.

항목내용
아키텍처데이터 레이크하우스 (오브젝트 스토리지 + 개방형 테이블 포맷 + 연합 쿼리 엔진)
쿼리 엔진Apache Trino — Postgres·ClickHouse·Iceberg를 한 쿼리로 연합
테이블 포맷Apache Iceberg (R2 Data Catalog로 관리)
스토리지Cloudflare R2 (S3 호환 오브젝트 스토리지, 이그레스 요금 없음)
메타데이터/거버넌스DataHub (테이블·컬럼·소유자·리니지·용어집)
빌링 쿼리 수91,760건 (직원 324명, 측정 기간 기준)
빌링 비중전체 쿼리의 약 53%
전형 BI 쿼리 지연약 3초
네트워크 규모초당 10억 건 이상 이벤트, 330+ 도시·120개국

레이크하우스, 왜 지금 다시 이 이야기인가

레이크하우스는 데이터 레이크(값싼 오브젝트 스토리지에 원본을 쌓는 방식)와 데이터 웨어하우스(스키마·트랜잭션·성능)의 장점을 한곳에 합치려는 아키텍처다. 핵심은 딱 하나다 — 데이터는 오브젝트 스토리지(R2)에 열린 포맷(Parquet)으로 두고, 그 위에 테이블의 '약속'을 얹는 메타데이터 계층(Iceberg)을 올린다. 이렇게 하면 특정 웨어하우스 벤더에 데이터를 가두지 않고도 SQL·트랜잭션·스키마 진화를 쓸 수 있다.

과거 Hive 테이블의 고질병이 여기서 갈린다. Hive는 "이 디렉터리 아래 파일들이 곧 테이블"이라는 식이라, 파일 하나 추가/삭제가 원자적이지 않고 파티션 변경도 지옥이었다. Iceberg는 테이블 상태를 스냅샷 메타데이터로 관리한다. 어떤 데이터 파일이 지금 테이블에 속하는지를 매니페스트가 명시하니까, 커밋이 원자적이고 동시 쓰기 충돌도 다룰 수 있다. Town Lake가 Iceberg를 고른 이유의 절반은 여기에 있다고 본다.

참고 "레이크하우스 = Iceberg"는 아니다. Iceberg 말고도 Delta Lake, Apache Hudi가 같은 문제를 푼다. 셋 다 오브젝트 스토리지 위 Parquet에 ACID·타임트래블·스키마 진화를 붙이는 개방형 테이블 포맷이다. Cloudflare는 Iceberg를 택했고, R2 Data Catalog라는 관리형 Iceberg 서비스로 이를 상품화까지 했다. 개인적으로는 엔진 중립성(Trino·Spark·Flink 다 붙음)과 카탈로그 생태계 때문에 신규 프로젝트라면 Iceberg를 먼저 검토한다.

Iceberg가 실제로 주는 것 — 스키마 진화·타임트래블·컴팩션

개방형 테이블 포맷의 값어치는 "파일을 안 건드리고 테이블을 바꿀 수 있다"는 데 있다. Cloudflare 블로그에 따르면 R2 Data Catalog는 스키마 진화, 타임트래블, 파티션 진화를 제공하고, 데이터가 나이 들수록 자동으로 컴팩션된다 — 지난주 분 단위 사용량은 시간 단위로, 지난 분기 시간 단위는 일 단위로 굵어진다. 콜드/웜 데이터를 이렇게 굴리는 게 스토리지 비용과 쿼리 성능을 동시에 잡는 정석이다.

각 기능을 데이터 엔지니어가 실무에서 쓰는 방식으로 보면 이렇다.

Iceberg 기능무엇을 해결현장에서의 의미
스키마 진화컬럼 추가/삭제/이름변경/타입확장을 데이터 재작성 없이배포마다 테이블 통째로 다시 쓰던 고통이 사라짐. ID 기반이라 rename도 안전
타임트래블과거 스냅샷 시점의 테이블을 그대로 조회/롤백"어제 파이프라인이 뭘 망쳤나" 디버깅과 감사에 직빵
파티션 진화파티션 전략(일→시간 등)을 기존 데이터 재작성 없이 변경Hive에선 사실상 불가능했던 것. 데이터 늘면 파티션 촘촘하게
컴팩션/만료작은 파일 병합, 오래된 스냅샷·데이터 만료small-file 문제 해결. Town Lake는 나이별 롤업까지 자동화

타임트래블은 말은 멋진데 함정이 하나 있다. 스냅샷을 오래 보관할수록 메타데이터와 옛 데이터 파일이 쌓여 스토리지가 부푼다. 그래서 expire_snapshots로 보관 기간을 정해 정리해야 한다. Town Lake의 "나이 들면 굵게 롤업" 전략은 사실상 이 만료·컴팩션을 정책화한 것으로 읽힌다. 아래는 Trino에서 Iceberg 테이블을 다룰 때 실제로 쓰는 명령들이다.

-- 타임트래블: 특정 스냅샷 시점으로 조회 (Trino)
SELECT * FROM billing.usage
FOR TIMESTAMP AS OF TIMESTAMP '2026-09-01 00:00:00 UTC';

-- 특정 스냅샷 ID로 조회
SELECT * FROM billing.usage FOR VERSION AS OF 3821550127947089009;

-- 작은 파일 병합(컴팩션)
ALTER TABLE billing.usage EXECUTE optimize;

-- 오래된 스냅샷 만료로 스토리지 회수
ALTER TABLE billing.usage EXECUTE expire_snapshots(retention_threshold => '7d');

Trino 연합 쿼리 — 데이터를 옮기지 않는다는 발상

Town Lake의 핵심 설계 결정은 "모든 걸 한 창고에 다 때려박지 않는다"는 것이다. Cloudflare 블로그의 표현을 빌리면, 하나의 Trino SQL이 Postgres 테이블, ClickHouse 테이블, R2 위의 Iceberg 테이블을 중간 결과를 다른 시스템으로 물질화(materialize)하지 않고 조인한다. 운영 DB는 Postgres에, 시계열/분석 원천은 ClickHouse에, 콜드·웜 데이터는 R2의 Iceberg에 그대로 두고, 쿼리 시점에 엔진이 각 커넥터로 가서 필요한 만큼만 끌어온다.

이게 왜 중요하냐면, 전통적 웨어하우스 방식은 분석하려면 일단 전부 ETL로 복사해 넣어야 했다. 복사본은 늘 신선도가 떨어지고, 파이프라인이 깨지고, 저장 비용이 이중으로 든다. 연합 쿼리는 "원천에 그대로 두고 질의"라 이 복사 지옥을 상당 부분 없앤다. 물론 공짜는 아니다.

주의 연합 쿼리를 만능으로 보면 데인다. Trino가 Postgres로 큰 조인을 밀어넣으면 그쪽 운영 DB가 신음한다. 커넥터가 필터·집계를 원천으로 내려주는 푸시다운(pushdown)이 얼마나 되느냐가 성능을 가른다. 나는 연합 쿼리를 "탐색·조인의 접착제"로 쓰고, 무거운 반복 집계는 결국 Iceberg 테이블로 실체화(CTAS)해두는 편이다. 실제로 Town Lake도 빌링 대시보드는 R2의 Iceberg 테이블을 Trino로 질의하도록 만들어뒀다 — 뜨거운 워크로드는 레이크에 정착시킨 셈이다.

-- 연합 쿼리: 운영 Postgres의 계정 정보 + R2 Iceberg의 사용량을 한 방에
SELECT a.account_name, SUM(u.bytes) AS total_bytes
FROM postgres.public.accounts a
JOIN iceberg.billing.usage u ON u.account_id = a.id
WHERE u.day >= DATE '2026-09-01'
GROUP BY a.account_name
ORDER BY total_bytes DESC
LIMIT 20;

-- 뜨거운 집계는 아예 Iceberg 테이블로 실체화해 재사용
CREATE TABLE iceberg.billing.usage_daily AS
SELECT account_id, day, SUM(bytes) AS bytes
FROM iceberg.billing.usage GROUP BY account_id, day;

데이터 거버넌스 — 메타데이터를 코드로 강제하기

레이크하우스에서 제일 얕보이다가 제일 발목 잡는 게 거버넌스다. 아무나 아무 데이터에 SQL을 던질 수 있으면, 어느 순간 개인정보가 대시보드에 흘러나온다. Town Lake는 기본 폐쇄(default-closed) 모델을 쓴다 — 새로 온보딩된 데이터셋은 자동 스캔과 사람의 리뷰가 끝나기 전까지 아무도 못 본다. 이게 거버넌스의 기본 자세다. "기본 공개, 문제 생기면 막기"가 아니라 "기본 차단, 검증되면 열기".

Cloudflare 블로그에 따르면 이를 떠받치는 컴포넌트가 셋이다.

  • DataHub — 모든 테이블·컬럼·소유자·리니지(계보)·용어집이 사는 중앙 메타데이터 카탈로그. "이 컬럼 어디서 왔고 누가 주인이냐"에 답한다.
  • Lifeguard — 접근 제어. D1에 정책을 두고 Trino로 HTTP를 통해 동적으로 정책을 렌더링해 내려준다. 즉 권한이 쿼리 엔진 레벨에서 강제된다.
  • Skimmer — Workers AI로 PII를 상시 탐지. 테이블 문맥까지 확인하는 2패스 분류로 민감 데이터를 사람 검증 전에 걸러낸다.

여기서 데이터 엔지니어가 배울 지점은 리니지와 소유권을 메타데이터로 "강제"한다는 것이다. 리니지가 없으면 "이 지표 이상한데 원천이 뭐죠?"라는 질문에 매번 사람이 코드를 뒤져야 한다. 소유자가 없으면 깨진 파이프라인의 책임자가 증발한다. Town Lake는 이걸 위키 문서가 아니라 쿼리 경로에 붙은 시스템으로 만들었다. 문서는 썩지만 시스템은 안 썩는다 — 적어도 덜 썩는다.

인제스천 파이프라인 — Parquet로 바꿔 Iceberg에 싣기

데이터가 어떻게 레이크로 들어가는지도 짚어보자. Cloudflare에 따르면 Transformer라는 컴포넌트가 Workflows 위에서 ELT 파이프라인을 방향성 비순환 그래프(DAG)로 조율한다. 인제스천 오케스트레이터는 Kubernetes 디플로이먼트로 돌면서 워커 잡을 띄우는데, 각 잡이 추출 → Parquet 변환 → Iceberg 적재를 수행한다. 적재 모드는 전체 교체(full-replace)와 증분 추가(incremental-append) 두 가지다.

적재 모드동작언제 쓰나
full-replace테이블 전체를 새 스냅샷으로 교체원천이 작거나, 스냅샷 단위로 통째 다시 뽑는 게 안전한 차원 테이블
incremental-append신규/변경분만 이어붙임로그·사용량처럼 계속 쌓이는 대용량 팩트 테이블

Parquet를 중간 포맷으로 쓰는 건 정석이다. 컬럼 지향이라 분석 쿼리에서 필요한 컬럼만 읽고, 압축률이 좋고, Iceberg가 네이티브로 다룬다. ELT(추출·적재 먼저, 변환은 나중)를 택한 것도 눈여겨볼 만하다 — 원본을 일단 레이크에 안전하게 착지시킨 뒤, SQL로 변환 레이어를 쌓는 방식이 재처리와 디버깅에 훨씬 유리하다. 변환 로직이 바뀌어도 원천을 다시 안 뽑아도 되니까.

우리 팀이 이 구조를 흉내 내려면

Cloudflare급 규모가 아니어도 이 청사진은 쓸 만하다. 핵심 원칙만 추리면 이렇다. 첫째, 데이터는 오브젝트 스토리지에 개방형 포맷으로 둔다(S3/R2 + Iceberg/Delta). 벤더 웨어하우스에 원본을 가두지 않는 것만으로 미래의 이사 비용이 확 준다. 둘째, 쿼리 엔진은 데이터와 분리한다(Trino/DuckDB/Spark). 엔진을 바꿔도 데이터는 그대로다.

셋째, 거버넌스는 처음부터. 나중에 붙이려면 이미 늦다. 카탈로그(DataHub/OpenMetadata 같은 오픈소스도 있다)로 소유자·리니지·민감도 태그를 데이터셋 생성 시점에 필수화하라. 넷째, 뜨거운 워크로드는 실체화, 탐색은 연합으로. 연합 쿼리는 접착제이지 최종 성능 계층이 아니다.

참고 소규모라면 굳이 Trino 클러스터를 세울 필요도 없다. R2/S3 위 Iceberg를 DuckDB로 직접 읽는 조합이 요즘 상당히 쓸 만하다. 핵심은 도구가 아니라 "원본을 열린 포맷으로, 스토리지·컴퓨트·카탈로그를 분리"라는 구조 그 자체다. 이 구조만 지키면 나중에 엔진을 Trino로 올리든 Spark로 붙이든 데이터 마이그레이션이 필요 없다.

덤: Skipper라는 AI 데이터 에이전트

Town Lake 발표에서 헤드라인을 가져간 건 사실 그 위에 올린 AI 에이전트 Skipper다. 자연어로 물으면 검증된 SQL로 바꿔 실행해주는 에이전트인데, 스키마 메타데이터·사람이 단 주석·원천 SQL 문서·큐레이션된 데이터 모델·런타임 인트로스펙션을 컨텍스트로 쓴다. Workers와 MCP 서버로 동작하고, 다단계 작업은 샌드박스 아이솔레이트에서 Code Mode로 처리한다고 한다.

데이터 엔지니어로서 여기서 얻는 교훈은 좀 뻔하지만 확실하다 — AI 에이전트가 쓸모 있으려면 그 밑의 메타데이터가 잘 정리돼 있어야 한다. Skipper가 정확한 SQL을 뽑는 건 마법이 아니라 DataHub의 리니지·용어집·소유권이 받쳐주기 때문이다. Cloudflare의 엔터프라이즈 엔지니어링 책임자 Dmitry Alexeenko가 "예전엔 복잡한 SQL이나 수작업 조사가 필요하던 일을 이제 몇 초 만에 끝낸다"고 한 것도, 결국 정돈된 데이터 계층이 있었기에 가능한 얘기다. 거버넌스 투자가 AI 시대에 이자를 붙여 돌아오는 셈이다.

자주 묻는 질문

레이크하우스와 데이터 웨어하우스는 뭐가 다른가요?

웨어하우스는 데이터를 자체 스토리지에 독점 포맷으로 넣고 스토리지·컴퓨트가 묶여 있는 경우가 많습니다. 레이크하우스는 데이터를 값싼 오브젝트 스토리지(S3/R2)에 Parquet 같은 개방형 포맷으로 두고, 그 위에 Iceberg/Delta 같은 테이블 포맷으로 ACID·스키마 진화·타임트래블을 얹은 뒤, Trino 같은 엔진으로 질의합니다. 스토리지·컴퓨트·카탈로그가 분리돼 벤더 종속이 약하다는 게 핵심 차이입니다.

왜 Iceberg인가요? Delta Lake나 Hudi는요?

셋 다 오브젝트 스토리지 위 Parquet에 ACID·타임트래블·스키마 진화를 붙이는 개방형 테이블 포맷으로 목적이 같습니다. Iceberg는 엔진 중립성(Trino·Spark·Flink·DuckDB 등 폭넓게 지원)과 카탈로그 생태계가 강점이라 멀티엔진 환경에서 선호됩니다. Cloudflare는 Iceberg를 택하고 R2 Data Catalog라는 관리형 서비스로 만들었습니다. Databricks 중심이면 Delta, 업서트 위주의 스트리밍이면 Hudi도 후보입니다.

연합 쿼리(Trino)를 쓰면 ETL이 아예 필요 없어지나요?

아닙니다. 연합 쿼리는 데이터를 옮기지 않고 여러 원천을 조인하는 데 강하지만, 무거운 반복 집계를 운영 DB로 밀어넣으면 그쪽에 부하가 갑니다. 필터·집계가 원천으로 내려가는 푸시다운이 잘 되는지 확인하고, 뜨거운 워크로드는 Iceberg 테이블로 실체화해두는 게 정석입니다. 탐색·조인은 연합으로, 성능이 중요한 반복 쿼리는 정착된 테이블로 나눠 쓰세요.

Iceberg의 타임트래블은 스토리지를 얼마나 더 먹나요?

과거 스냅샷을 보관하는 만큼 옛 데이터 파일과 메타데이터가 남아 스토리지가 늘어납니다. 그래서 expire_snapshots로 보관 기간을 정하고, optimize로 작은 파일을 병합하는 유지보수가 필요합니다. Cloudflare Town Lake가 데이터를 나이별로 굵게 롤업하는(분→시간→일) 것도 사실상 이 만료·컴팩션을 정책화한 것입니다.

작은 조직도 이런 레이크하우스를 만들 수 있나요?

네. Trino 클러스터 없이도 R2/S3 위 Iceberg를 DuckDB로 직접 읽는 조합이면 소규모에서 충분합니다. 핵심은 도구 규모가 아니라 구조입니다 — 원본을 개방형 포맷으로 오브젝트 스토리지에 두고, 스토리지·컴퓨트·카탈로그를 분리하는 것. 이 구조만 지키면 나중에 엔진이나 규모를 키울 때 데이터 마이그레이션이 필요 없습니다.

데이터 거버넌스를 처음부터 해야 하는 이유가 뭔가요?

나중에 붙이려면 이미 데이터가 여기저기 흩어지고 소유자·계보가 사라진 뒤라 훨씬 비쌉니다. Town Lake는 기본 폐쇄 모델로 새 데이터셋을 자동 스캔·사람 리뷰 전까지 차단하고, 카탈로그에 소유자·리니지·민감도를 필수화했습니다. 이렇게 해두면 지표 이상 추적, 개인정보 노출 방지, 그리고 나중에 AI 에이전트가 정확한 SQL을 생성하는 것까지 모두 이 메타데이터 위에서 돌아갑니다.

빌링 워크로드가 전체 쿼리의 53%라는 게 무슨 의미인가요?

Cloudflare 블로그에 따르면 측정 기간에 직원 324명이 던진 빌링 관련 쿼리가 91,760건으로 전체의 약 53%였습니다. 종량제 고객에게 청구액을 보여주는 대시보드가 R2의 Iceberg 테이블을 Trino로 질의하는 구조라, 사내 데이터 플랫폼이 실제 매출·청구라는 미션크리티컬 업무를 떠받치고 있다는 뜻입니다. 분석용 장난감이 아니라 운영 기반이라는 신호죠.

출처

  • Cloudflare Blog, "How we built Cloudflare's data platform and an AI agent on top of it" — https://blog.cloudflare.com/our-unified-data-platform/
  • InfoQ, "Cloudflare Details Unified Data Platform Where Billing Workloads Account for 53% of Queries" (2026-07) — https://www.infoq.com/news/2026/07/cloudflare-unified-data-platform/
  • Cloudflare, Data Platform 제품 페이지 — https://www.cloudflare.com/products/data-platform/

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록