Scaling Grab’s Data Lake: Our journey to Apache Iceberg adoption

Author: Rahul Penti (Grab) | Source: engineering.grab.com | Published: 2026-07-10


한 줄 요약

페타바이트 규모에서 Hive Metastore + 디렉토리 레이아웃이 한계에 부딪혀 Iceberg로 전환하되, 전체를 한 번에 바꾸는 대신 스캔 빈도·API 비용 기준으로 고가치 테이블부터 옮기고 포맷 차이를 사용자에게 숨기는 UnifiedSparkCatalog를 만들어 마이그레이션이 다운스트림을 깨지 않게 했다.

핵심 주장/내용

  • Hive Parquet의 4가지 병목:
    • 카탈로그 레이턴시 — HMS가 중앙 실패 지점. 메타데이터 접근 고동시성에서 O(n) 리스팅 오버헤드, 쿼리 플래닝 시간이 파티션 수에 선형 비례
    • small file 문제 — 디렉토리 레이아웃이 심한 파일 파편화를 남김. 일부 ML 데이터셋은 평균 파일 크기 1MB 미만에 파티션당 수천 파일. S3 객체 리스팅과 메타데이터 요청 레이턴시 오버헤드가 API 비용을 올리고 스캔을 느리게
    • 운영 부담 — 파티션 등록 수작업. 네이티브 ACID(UPSERT·DELETE)가 없어 데이터 변경마다 복잡한 워크어라운드
    • 깨진 정보 루프 — 스토리지가 아니라 HMS가 source of truth로 취급되어 직접 S3 수정이 카탈로그를 stale하게 만듦
  • 마이그레이션은 플래그 하나 뒤집기가 아니다: 압도적으로 Hive Parquet인 레이크를 여러 엔진과 팀이 쿼리하는 상태에서, 의존하는 다운스트림 소비자를 깨지 않으면서 롤아웃해야 했다. 전부 한 번에 바꾸는 대신 가치 높은 테이블부터
  • UnifiedSparkCatalog — 저장·메타데이터 문제를 풀었더니 개발자 경험 층에서 새 문제가 생겼다: Delta·Iceberg·Hudi가 각자 SessionCatalog를 확장한 커스텀 카탈로그를 구현하는데, 표준 Spark 런타임에서는 하나만 기본 spark_catalog가 될 수 있다. 나머지는 iceberg_catalog.schema.table 같은 포맷별 접두사를 요구 → 엔지니어가 모든 테이블의 포맷을 알아야 하고, 포맷 마이그레이션이 접두사를 하드코딩한 모든 다운스트림 쿼리를 조용히 깨뜨린다
    • 동작: ① HMS에서 메타데이터 로드 ② TableTypeDetector가 메타데이터 속성(provider 필드) 또는 경로 추론으로 포맷 식별 ③ 접두사 없이 올바른 포맷별 카탈로그로 위임
    • lazy initialization — 포맷별 카탈로그를 처음 필요할 때만 초기화. JAR이 classpath에 없으면 세션 전체를 실패시키지 않고 그 포맷만 건너뜀
    • 이름을 spark_catalog로 — Spark 내부 코드 경로 다수가 legacy Hive DML(insert/update/delete)에 Hive 호환 로직을 쓸지 결정할 때 정확히 이 이름을 검사하므로 다른 이름을 쓰면 legacy Hive 테이블 연산이 깨진다
    • 카탈로그 재사용 — 새로 만들기 전에 Spark catalog manager에 이미 있는지 확인. 카탈로그 클래스 타입을 검사해 lineage를 추출하는 OpenLineage 같은 플러그인과의 호환 유지
    • fallback — 기대한 포맷별 카탈로그에 테이블이 없으면 base session catalog로
    • Trino의 Table Redirection에서 착안. 오픈소스 공개 (grab/unified-spark-catalog)
  • 겪은 기술적 함정:
    • Hive lock contention — HMS의 “zombie lock”이 커밋을 막음. 고부하 시 메타스토어 쪽 read timeout이 낮은 것이 원인. 재시도 간격 조정 + 타임아웃 증가로 해결
    • 타임스탬프 — Spark 3.4의 TIMESTAMP_NTZ vs Iceberg 기본 TIMESTAMP_LTZ 불일치가 legacy Hive 뷰와 충돌. 커스텀 마이그레이션 워크플로 + Trino 배포에 타겟 패치
    • 스토리지 티어 비용 — Iceberg 메타데이터 생성이 과거 데이터를 읽으므로 S3 티어 간 이동으로 일회성 비용 스파이크. 그래서 전체를 한 번에 옮기지 않고 테이블의 스캔 빈도와 API 연산 비용 기준으로 우선순위를 매김

주요 수치 / 사실

  • 고트래픽 내비게이션 데이터셋에서 Z-ordering으로 쿼리 런타임 약 10배 개선 (70초 → 6초). Z-order가 지정 차원에서 유사 값 row를 co-locate해 Trino가 data skipping과 min/max 통계로 파일 프루닝
  • 자주 쿼리되는 운영 테이블의 일일 S3 API 비용 최대 95% 감소 — 쿼리 변경 없이. 큰 파일 크기와 쿼리 플래닝 중 비싼 객체 리스팅 제거가 절감의 대부분
  • funnel 분석 데이터셋의 클러스터 리소스 사용량 약 절반으로 감소. 별도 ML 피처 파이프라인은 다운스트림 모델의 피처 신선도 개선
  • 사내 플랫폼: Slide(배치 변환), Hugo(온라인→데이터레이크 인제스션). Iceberg가 둘의 기본 포맷
  • 향후: Storage Partitioned Joins로 Spark 셔플 스테이지 제거 실험, 포맷 간 상호운용을 위한 Apache XTable 모니터링

관련 위키


Source: 원문 보기