Tag: 스키마

  • 레이크하우스 아키텍처 설계: 데이터 레이크와 웨어하우스를 하나로

    레이크하우스 아키텍처 설계: 데이터 레이크와 웨어하우스를 하나로

    데이터 조직이 일정 규모를 넘어서면 두 가지 인프라를 동시에 운영하는 비용에 직면합니다. 원천 로그와 비정형 데이터를 담는 데이터 레이크, 그리고 BI와 리포팅을 위한 데이터 웨어하우스입니다. 두 시스템 사이에서 데이터를 복제하다 보면 동일한 지표가 두 곳에서 다른 값을 내는 일이 흔합니다. 레이크하우스는 이 이중 구조를 단일 저장 계층으로 통합하려는 시도입니다.

    이 글에서는 레이크하우스가 해결하려는 문제부터 테이블 포맷 선택, 계층 설계, 운영 시 마주치는 함정까지 단계적으로 다룹니다. 약 500TB 규모의 분석 환경을 기준으로 설명하지만 원리는 더 작은 환경에도 그대로 적용됩니다.

    왜 레이크하우스인가

    전통적인 구조에서는 객체 스토리지(S3, GCS)에 원본을 쌓고, ETL로 정제한 뒤 별도의 웨어하우스로 적재합니다. 문제는 두 가지입니다. 첫째, 웨어하우스 스토리지 비용이 객체 스토리지의 5배에서 10배에 달합니다. 둘째, 복제 지연 때문에 데이터 신선도가 떨어집니다.

    레이크하우스는 값싼 객체 스토리지 위에 ACID 트랜잭션, 스키마 강제, 타임 트래블 같은 웨어하우스급 기능을 제공하는 테이블 포맷을 얹어 이 문제를 해결합니다. 동일한 데이터를 SQL 엔진과 ML 프레임워크가 함께 읽을 수 있다는 점이 핵심 가치입니다.

    테이블 포맷 선택

    레이크하우스의 심장은 테이블 포맷입니다. 대표적으로 Delta Lake, Apache Iceberg, Apache Hudi 세 가지가 경쟁합니다. 선택 기준을 표로 정리하면 다음과 같습니다.

    포맷강점적합 시나리오
    Delta LakeSpark 생태계 통합, 성숙도Databricks 중심 환경
    Iceberg엔진 중립성, 숨은 파티셔닝멀티 엔진 운영
    Hudi업서트, 증분 처리CDC 기반 적재

    여러 쿼리 엔진(Trino, Spark, Flink)을 함께 쓴다면 Iceberg의 엔진 중립성이 유리합니다. CDC로 잦은 업데이트가 발생한다면 Hudi의 업서트 성능이 빛을 발합니다.

    메달리온 계층 설계

    실무에서 가장 널리 쓰이는 구조는 브론즈-실버-골드로 나누는 메달리온 아키텍처입니다. 브론즈는 원천을 거의 그대로 적재한 불변 레이어, 실버는 정제와 조인을 거친 레이어, 골드는 비즈니스 지표가 집계된 소비 레이어입니다.

    • 브론즈: append-only, 스키마 검증 최소화, 재처리 기준점
    • 실버: 중복 제거, 타입 정규화, 품질 규칙 적용
    • 골드: 차원 모델 또는 와이드 테이블, BI 직접 연결

    이 분리의 이점은 명확합니다. 비즈니스 로직이 바뀌어도 브론즈는 그대로 두고 실버부터 다시 만들면 됩니다. 원본 손실 없이 전체 파이프라인을 재현할 수 있다는 뜻입니다.

    운영과 트러블슈팅

    레이크하우스에서 가장 흔한 운영 이슈는 작은 파일 문제입니다. 스트리밍이나 잦은 커밋으로 수십 KB짜리 파일이 수백만 개 쌓이면 쿼리 시 메타데이터 스캔만으로 수십 초가 소요됩니다. 해결책은 정기적인 컴팩션(OPTIMIZE)과 적절한 파일 크기 목표(128MB에서 1GB) 설정입니다.

    또 하나는 메타데이터 누적입니다. 타임 트래블을 위해 보관하는 스냅샷이 무한정 쌓이면 스토리지와 메타 처리 비용이 증가합니다. VACUUM 또는 expire_snapshots를 7일에서 30일 보존 정책으로 스케줄링하세요. 단, 진행 중인 장기 쿼리가 참조하는 스냅샷을 삭제하지 않도록 보존 기간을 보수적으로 잡는 것이 안전합니다.

    레이크하우스의 성패는 포맷 선택보다 컴팩션과 보존 정책 같은 운영 자동화에서 갈립니다.

    정리

    레이크하우스는 레이크와 웨어하우스의 이중 구조를 단일 계층으로 합쳐 비용과 신선도를 동시에 개선합니다. 핵심은 적합한 테이블 포맷을 고르고, 메달리온 계층으로 책임을 분리하며, 컴팩션과 스냅샷 관리를 자동화하는 것입니다. 작게 시작해 브론즈부터 구축하고 점진적으로 골드 레이어를 확장하는 접근을 권합니다.

  • dbt로 데이터 변환 모델링하기: 분석 엔지니어링의 표준

    dbt로 데이터 변환 모델링하기: 분석 엔지니어링의 표준

    웨어하우스에 데이터를 적재한 뒤 그것을 분석 가능한 형태로 변환하는 일은 오랫동안 복잡한 SQL 스크립트 더미와 수작업으로 이뤄졌습니다. 누가 어떤 테이블을 만들었는지, 의존 관계가 무엇인지 아무도 모르는 상태가 흔했습니다. dbt는 이 변환 계층(ELT의 T)을 소프트웨어 엔지니어링 원칙으로 다루게 해주는 도구입니다.

    이 글에서는 dbt의 모델, 참조 시스템, 테스트, 문서화를 차례로 살펴보고, 실무에서 모델 구조를 어떻게 잡아야 하는지 다룹니다.

    dbt가 바꾸는 것

    dbt의 출발점은 단순합니다. 모든 변환은 SELECT 문 하나로 표현되는 모델이고, dbt가 그것을 CREATE TABLE 또는 VIEW로 감싸 실행합니다. 엔지니어는 결과 테이블이 아니라 그 테이블을 정의하는 쿼리에 집중합니다. 여기에 버전 관리, 테스트, 의존성 그래프가 따라옵니다.

    핵심 마법은 ref 함수입니다. 모델끼리 직접 테이블명을 쓰지 않고 ref로 참조하면 dbt가 의존 그래프를 자동 구성하고 올바른 순서로 빌드합니다. 환경(dev, prod) 간 스키마 전환도 ref가 알아서 처리합니다.

    모델 계층 구조

    잘 설계된 dbt 프로젝트는 보통 세 계층으로 나눕니다. 이 구조는 변경에 강하고 재사용이 쉽습니다.

    • staging: 원천 테이블당 1:1, 컬럼명 정리와 타입 캐스팅만
    • intermediate: 여러 staging을 조합한 중간 로직, 재사용 단위
    • marts: 비즈니스 도메인별 최종 산출물, BI 연결
    -- models/marts/fct_orders.sql
    with orders as (
        select * from {{ ref('stg_orders') }}
    ),
    payments as (
        select * from {{ ref('stg_payments') }}
    )
    select o.order_id, o.customer_id, sum(p.amount) as total
    from orders o left join payments p using (order_id)
    group by 1, 2

    테스트와 문서화

    dbt의 진짜 강점은 데이터 품질 테스트를 선언적으로 붙일 수 있다는 점입니다. YAML에 not_null, unique, accepted_values, relationships 같은 제약을 명시하면 빌드마다 자동 검증됩니다. 기본 테스트로 부족하면 SQL로 커스텀 테스트를 작성합니다.

    같은 YAML에 컬럼 설명을 적으면 dbt docs가 자동으로 데이터 카탈로그와 계보 그래프를 생성합니다. 코드와 문서가 한곳에 있어 문서가 낡지 않는다는 점이 큰 가치입니다.

    운영과 성능

    모델이 수백 개를 넘으면 전체 빌드 시간이 부담스러워집니다. 매번 전체 테이블을 새로 만드는 대신 incremental 모델로 새 데이터만 추가 처리하세요. is_incremental 분기로 증분 조건을 걸면 빌드 시간이 수십 분에서 수 분으로 줄어듭니다.

    또한 dbt build와 함께 상태 비교 선택(state:modified)을 쓰면 변경된 모델과 그 하위만 빌드해 CI 시간을 절약할 수 있습니다. 한 가지 주의점은 증분 모델의 고유 키 설정 오류로 인한 중복인데, unique 테스트로 반드시 방어하세요.

    dbt는 분석가를 엔지니어로 만든다. 버전 관리, 테스트, 문서화가 SQL 워크플로에 자연스럽게 녹아들기 때문이다.

    정리

    dbt는 변환 계층을 모듈화된 SQL 모델로 다루며 ref 기반 의존성, 선언적 테스트, 자동 문서화를 제공합니다. staging-intermediate-marts 계층으로 구조를 잡고, 증분 모델로 성능을 확보하며, 테스트로 품질을 지키는 것이 분석 엔지니어링의 표준 워크플로입니다.

  • 스키마 진화 관리: 깨지지 않는 데이터 계약 만들기

    스키마 진화 관리: 깨지지 않는 데이터 계약 만들기

    데이터 파이프라인을 운영하다 보면 새벽에 깨어 가장 흔하게 마주치는 알람이 스키마 불일치입니다. 누군가 원천 테이블에 컬럼을 추가하거나 타입을 바꾸면 다운스트림 파이프라인이 줄줄이 무너집니다. 스키마는 변하지 않는 것이 아니라 반드시 변하는 것이며, 문제는 그 변화를 어떻게 안전하게 관리하느냐입니다.

    이 글에서는 스키마 진화의 유형, 하위 호환성의 의미, 스키마 레지스트리 활용, 그리고 데이터 계약(data contract) 개념까지 다룹니다.

    스키마는 왜 깨지는가

    문제의 근원은 생산자와 소비자의 분리입니다. 백엔드 팀이 API 응답에 필드를 추가하거나 이름을 바꿀 때, 그 데이터를 소비하는 데이터 팀은 통보받지 못하는 경우가 많습니다. 변경은 정당하지만 조율되지 않은 변경이 파이프라인을 깨뜨립니다.

    변경 유형은 크게 호환되는 변경과 깨뜨리는 변경으로 나뉩니다. 선택적 필드 추가는 보통 안전하지만, 필드 삭제나 타입 변경, 필수 필드 추가는 소비자를 망가뜨립니다.

    호환성의 세 가지 방향

    스키마 호환성은 방향에 따라 정의됩니다. 이 구분을 명확히 해야 어떤 변경이 허용되는지 판단할 수 있습니다.

    • 하위 호환(backward): 새 스키마로 옛 데이터를 읽을 수 있음. 필드 삭제, 선택 필드 추가 허용
    • 상위 호환(forward): 옛 스키마로 새 데이터를 읽을 수 있음. 필드 추가 허용
    • 완전 호환(full): 양방향 모두 가능. 가장 안전하지만 제약이 큼

    스트리밍 환경에서는 보통 하위 호환을 기본으로 둡니다. 소비자를 먼저 업데이트한 뒤 생산자가 따라오는 배포 순서를 강제할 수 있기 때문입니다.

    스키마 레지스트리 활용

    Kafka 환경에서는 Confluent Schema Registry가 사실상 표준입니다. Avro나 Protobuf 스키마를 중앙에 등록하고, 호환성 규칙을 위반하는 스키마는 등록 자체를 거부합니다. 즉 깨뜨리는 변경이 배포 단계에서 차단됩니다.

    # 호환성 정책을 BACKWARD로 설정
    curl -X PUT http://registry:8081/config/orders-value 
      -H "Content-Type: application/json" 
      -d '{"compatibility": "BACKWARD"}'

    레지스트리는 메시지에 스키마 전체가 아니라 작은 ID만 실어 보내게 해 네트워크 효율도 높입니다. 소비자는 ID로 스키마를 조회해 역직렬화합니다.

    데이터 계약과 운영

    최근에는 한 걸음 더 나아가 데이터 계약을 코드로 명시하는 흐름이 강합니다. 생산자가 제공하는 스키마, 품질 기준, SLA를 문서가 아니라 검증 가능한 명세로 정의하고, CI에서 변경을 자동 검사합니다. 변경이 계약을 위반하면 머지 전에 빌드가 실패합니다.

    운영 팁으로, 컬럼을 즉시 삭제하지 말고 사용 중단(deprecated) 표시 후 일정 기간 유예하세요. 또한 새 필드는 항상 기본값을 지정하고, 타입 변경은 새 컬럼 추가와 점진적 마이그레이션으로 우회하는 것이 안전합니다.

    좋은 스키마 관리란 변화를 막는 것이 아니라 변화를 예측 가능하게 만드는 것이다.

    정리

    스키마는 반드시 변하므로, 핵심은 하위 호환성을 기준으로 변경을 통제하는 것입니다. 스키마 레지스트리로 깨뜨리는 변경을 배포 단계에서 차단하고, 데이터 계약으로 생산자와 소비자의 기대를 코드화하세요. 필드는 즉시 삭제하지 말고 유예하며, 타입 변경은 점진적으로 우회하는 습관이 파이프라인을 지킵니다.

  • 데이터 파이프라인 관측성: 신뢰를 만드는 모니터링 체계

    데이터 파이프라인 관측성: 신뢰를 만드는 모니터링 체계

    전통적인 모니터링은 잡이 성공했는지 실패했는지를 봅니다. 그런데 데이터 세계에는 더 교묘한 문제가 있습니다. 잡은 분명히 초록불로 성공했는데, 실제 데이터는 절반이 비어 있거나 어제보다 10분의 1로 줄어든 경우입니다. 이런 침묵의 장애를 잡으려면 잡 상태가 아니라 데이터 자체를 관측해야 합니다. 이것이 데이터 관측성입니다.

    이 글에서는 데이터 관측성의 다섯 기둥, 구현 방법, 그리고 경보 피로를 피하는 운영 전략을 다룹니다.

    왜 잡 성공만으로는 부족한가

    파이프라인이 성공했다는 신호는 코드가 예외 없이 끝났다는 뜻일 뿐입니다. 원천 API가 빈 응답을 주거나, 업스트림 조인 키가 바뀌어 매칭이 0건이 되어도 코드는 멀쩡히 성공합니다. 비즈니스 대시보드의 숫자가 이상하다고 누군가 제보하기 전까지 아무도 모릅니다.

    실제로 데이터 장애의 상당수는 코드 버그가 아니라 데이터 자체의 변화에서 옵니다. 그래서 관측 대상이 인프라에서 데이터로 확장되어야 합니다.

    데이터 관측성의 다섯 기둥

    업계에서 합의된 관측성의 다섯 축은 다음과 같습니다. 각각이 서로 다른 유형의 장애를 잡아냅니다.

    • 신선도(Freshness): 데이터가 기대한 시점에 갱신됐는가
    • 양(Volume): 행 수가 평소 범위 안에 있는가
    • 분포(Distribution): 값의 범위와 null 비율이 정상인가
    • 스키마(Schema): 컬럼과 타입이 바뀌지 않았는가
    • 계보(Lineage): 어느 업스트림이 어느 다운스트림에 영향을 주는가

    신선도와 양만 잘 잡아도 침묵의 장애 대부분을 조기에 발견할 수 있습니다. 계보는 장애 발생 시 영향 범위를 빠르게 파악하는 데 결정적입니다.

    구현 방법

    가장 단순한 시작점은 핵심 테이블에 품질 검사를 거는 것입니다. dbt test, Great Expectations, Soda 같은 도구로 행 수 임계, null 비율, 고유성, 값 범위를 선언적으로 검증합니다.

    # Soda 체크 예시
    checks for fct_orders:
      - row_count between 9000 and 15000
      - missing_percent(customer_id) < 1%
      - freshness(created_at) < 6h
      - duplicate_count(order_id) = 0

    한 단계 더 나아가면 과거 패턴을 학습해 동적 임계치를 만드는 이상 탐지를 도입할 수 있습니다. 요일별, 시간대별 패턴을 반영하면 고정 임계보다 오탐이 크게 줍니다.

    경보 피로를 피하는 운영

    관측성을 도입하면 처음엔 경보가 폭주합니다. 너무 많은 경보는 곧 무시되고, 무시되는 경보는 없는 것과 같습니다. 따라서 심각도를 계층화하세요. 비즈니스 핵심 테이블의 신선도 장애는 즉시 호출(page), 부차적 테이블의 분포 경고는 일일 다이제스트로 묶는 식입니다.

    • SLA가 정의된 골드 테이블에만 강한 경보를 건다
    • 경보마다 담당자(owner)와 런북을 명시한다
    • 오탐이 잦은 검사는 임계를 조정하거나 제거한다

    데이터 신뢰는 정확한 데이터에서 오는 것이 아니라, 틀렸을 때 가장 먼저 아는 데서 온다.

    정리

    데이터 관측성은 잡 성공을 넘어 데이터 자체의 신선도, 양, 분포, 스키마, 계보를 감시합니다. 핵심 테이블부터 선언적 품질 검사를 걸고, 동적 임계로 오탐을 줄이며, 심각도를 계층화해 경보 피로를 막으세요. 목표는 완벽한 데이터가 아니라, 문제가 생겼을 때 사용자보다 먼저 아는 것입니다.

  • 데이터 적재 품질을 지키는 7가지 실전 원칙

    데이터 적재 품질을 지키는 7가지 실전 원칙

    데이터 품질 논의는 흔히 분석 단계나 대시보드 단계에 집중되지만, 진짜 싸움은 파이프라인 가장 앞단인 적재(ingestion)에서 벌어집니다. 입구에서 오염된 데이터는 하류로 내려갈수록 정화 비용이 기하급수적으로 커집니다. 한 번 잘못 들어간 데이터는 수십 개 다운스트림 테이블을 오염시키고, 추적과 복구에 며칠이 걸립니다.

    이 글에서는 적재 단계에서 품질을 지키기 위한 일곱 가지 실전 원칙을 사례와 함께 정리합니다.

    1. 입구에서 검증하라

    가장 기본은 적재 시점의 스키마와 제약 검증입니다. 필수 필드 존재, 타입 일치, 값 범위를 적재 직전에 확인하세요. 검증을 뒤로 미룰수록 오염 범위가 넓어집니다. 입구에서 거른 한 건이 하류의 백 건 복구를 막습니다.

    2. 멱등성을 보장하라

    파이프라인은 반드시 재실행됩니다. 같은 데이터가 두 번 적재돼도 결과가 같아야 합니다. INSERT 대신 자연 키 기반 MERGE나 파티션 덮어쓰기를 사용하세요. 멱등하지 않은 적재는 재시도 한 번에 중복 폭탄이 됩니다.

    MERGE INTO target t
    USING staging s ON t.event_id = s.event_id
    WHEN MATCHED THEN UPDATE SET *
    WHEN NOT MATCHED THEN INSERT *;

    3. 불량 레코드를 격리하라

    검증에 실패한 레코드를 만났을 때 전체 배치를 통째로 실패시키면 정상 데이터까지 막힙니다. 반대로 조용히 버리면 손실을 모릅니다. 정답은 불량 레코드 격리(quarantine)입니다. 실패한 행을 별도 데드레터 테이블로 보내 정상 흐름은 유지하되 추적은 가능하게 하세요.

    4. 원본을 보존하라

    적재 단계에서 변환을 과하게 하지 마세요. 원본을 거의 그대로 담는 불변 레이어(브론즈)를 두면, 로직 오류를 발견했을 때 원천 재요청 없이 재처리할 수 있습니다. 원본 보존은 가장 값싼 보험입니다.

    5. 메타데이터를 함께 적재하라

    각 레코드에 적재 시각, 원천 식별자, 배치 ID, 파일명 같은 메타데이터를 붙이세요. 문제가 터졌을 때 어느 배치, 어느 파일에서 왔는지 즉시 추적할 수 있습니다. 이 작은 컬럼들이 장애 대응 시간을 시간 단위에서 분 단위로 줄입니다.

    • _ingested_at: 적재 타임스탬프
    • _source_file: 원천 파일 경로
    • _batch_id: 적재 배치 식별자
    • _record_hash: 변경 감지용 해시

    6. 양과 신선도를 감시하라

    적재된 행 수가 평소 범위를 벗어나거나 기대 시각에 데이터가 없으면 즉시 경보하세요. 원천 API가 빈 응답을 줘 0건이 적재돼도 잡은 성공으로 끝나기 때문에, 양 기반 검사가 이런 침묵의 장애를 잡는 마지막 방어선입니다.

    7. 백필을 설계에 포함하라

    과거 데이터를 다시 적재하는 백필은 예외가 아니라 일상입니다. 처음부터 특정 기간을 지정해 재적재할 수 있도록 파라미터화하고, 백필이 운영 트래픽을 방해하지 않도록 자원을 분리하세요. 사후에 백필을 끼워 넣으면 항상 고통스럽습니다.

    품질은 마지막에 검사하는 것이 아니라 입구에서 설계하는 것이다. 쓰레기가 들어오면 쓰레기가 나간다.

    정리

    적재 품질의 핵심은 입구 검증, 멱등성, 불량 격리, 원본 보존, 메타데이터, 양·신선도 감시, 백필 설계입니다. 이 일곱 원칙은 화려하지 않지만, 하류의 수많은 복구 작업을 미리 막아주는 가장 효율적인 투자입니다. 파이프라인 품질은 결국 입구에서 결정됩니다.