Lineage와 Catalog 운영: column-level lineage, ownership, 영향도 분석
Lineage는 예쁜 그래프가 아니라 변경 전 확인 절차다
데이터 플랫폼에서 가장 곤란한 질문은 보통 장애가 난 뒤에 나온다. “이 테이블을 누가 쓰고 있나요?”, “이 컬럼 타입을 바꾸면 어떤 대시보드가 깨지나요?”, “어제 매출 지표가 틀렸다면 원천은 어디까지 거슬러 올라가야 하나요?” 테이블 목록만 있는 catalog로는 이 질문에 답하기 어렵다. 반대로 lineage 그래프만 있고 owner, SLA, 품질 상태가 없으면 운영자가 연락할 사람도, 위험도를 판단할 기준도 없다.
그래서 lineage와 catalog는 따로 운영하면 안 된다. Catalog는 자산의 이름, 설명, owner, domain, tag, glossary, 품질 상태를 담고, lineage는 자산 사이의 upstream/downstream 관계를 설명한다. 둘이 연결될 때 비로소 “무엇이 어디에서 왔고, 누가 책임지며, 바꾸면 무엇이 영향을 받는가”를 확인할 수 있다.
운영 관점의 metadata graph
OLTP / Event / File
Airflow / Kafka / CDC
Raw / Silver / Gold
BI / ML / API
좋은 metadata graph는 단순한 그림 저장소가 아니다. 각 노드는 dataset, dashboard, job, topic, model 같은 자산이고, 각 edge는 “읽었다”, “썼다”, “변환했다”, “집계했다”, “대시보드가 참조한다” 같은 관계다. 여기에 owner와 domain, tag, 품질 결과가 붙으면 운영자는 그래프를 실제 업무에 쓸 수 있다.
예를 들어 raw.orders에서 freshness 알림이 발생했다면 lineage를 upstream 방향으로 따라가 수집 job과 원천 시스템을 확인한다. 동시에 downstream 방향으로는 mart_daily_revenue, CFO 대시보드, 정산 리포트가 영향을 받는지 본다. catalog에 owner가 정확히 있으면 알림은 “데이터팀 전체”가 아니라 해당 domain owner와 소비자 owner에게 갈 수 있다.
1. Table-level lineage: 먼저 80%의 변경 위험을 줄인다
Table-level lineage는 테이블 또는 dataset 단위의 입력과 출력을 추적한다. raw_orders를 읽어 stg_orders를 만들고, stg_orders와 stg_payments를 조인해 mart_revenue_daily를 만든다는 관계가 여기에 해당한다.
처음부터 column-level lineage를 완벽하게 만들려고 하면 오래 걸린다. SQL dialect, UDF, 동적 쿼리, 임시 테이블, stored procedure, Spark transformation까지 모두 해석해야 하기 때문이다. 운영 초기에는 table-level lineage만으로도 많은 가치를 얻는다.
| 운영 질문 | Table-level lineage로 가능한 답 |
|---|---|
| 원천 장애 영향 | 이 source table을 읽는 downstream job과 mart 목록 |
| 배포 전 점검 | 이 model을 바꾸면 어떤 dashboard와 ML feature가 영향을 받는지 |
| 삭제 후보 검토 | 최근 사용량이 없고 downstream이 없는 자산인지 |
| 장애 원인 추적 | 깨진 mart의 upstream job과 source가 어디인지 |
중요한 것은 coverage를 측정하는 것이다. “lineage 도입 완료”라고 말하기 전에 핵심 production table 중 몇 %가 upstream/downstream edge를 갖는지, BI dashboard까지 연결되는 비율이 얼마인지, owner가 비어 있는 자산이 얼마나 되는지 봐야 한다.
2. Column-level lineage: schema 변경과 민감정보 전파를 다룬다
Column-level lineage는 출력 컬럼이 어떤 입력 컬럼에서 왔는지 추적한다. OpenLineage의 column lineage facet은 output field별 input field를 기록하고, 관계가 직접 파생인지 또는 filter, join, group by, sort처럼 간접 영향인지도 구분할 수 있다. 이 차이가 중요하다. customer_email_hash는 customer.email에서 직접 변환된 값일 수 있고, revenue는 order.amount를 집계한 값일 수 있으며, vip_revenue는 customer.segment가 filter 조건으로 간접 영향을 준 값일 수 있다.
-- 예시: 컬럼 수준 영향이 서로 다르게 생기는 쿼리
select
date(o.created_at) as order_date, -- created_at -> order_date (transformation)
c.country as country, -- country -> country (identity)
sum(o.amount) as gross_revenue -- amount -> gross_revenue (aggregation)
from raw_orders o
join dim_customers c on o.customer_id = c.id -- customer_id/id는 join 조건으로 간접 영향
where c.is_test_account = false -- is_test_account는 filter로 간접 영향
group by 1, 2;Column-level lineage가 특히 필요한 경우는 세 가지다.
- Breaking change 검토:
raw_orders.amount의 타입이나 의미를 바꿀 때 실제로 어떤 mart 컬럼과 dashboard metric이 영향을 받는지 확인한다. - PII 전파 추적: 이메일, 전화번호, 주민등록번호 같은 민감 컬럼이 hash, masking, join, export를 거쳐 어디까지 흘러가는지 확인한다.
- 품질 장애 범위 축소: source table 전체가 아니라 특정 컬럼만 null이 늘어난 경우, 그 컬럼을 직접 또는 간접 사용하는 downstream만 선별한다.
다만 column-level lineage는 신뢰도 관리가 필요하다. 자동 SQL parser가 UDF 내부 로직을 모를 수 있고, Python/PySpark 코드의 동적 변환은 정적 분석만으로 놓치기 쉽다. 운영 문서에는 lineage edge마다 source를 남기는 편이 좋다. 예를 들어 “dbt manifest 기반”, “OpenLineage runtime event 기반”, “manual curation”, “warehouse query history 기반”처럼 provenance를 구분하면, 그래프를 어디까지 믿어도 되는지 판단할 수 있다.
3. Catalog는 owner 없는 자산을 줄이는 시스템이다
Catalog의 실패 패턴은 자산은 많은데 책임자가 없는 상태다. 설명이 비어 있고 owner가 퇴사자이며 tag가 오래되면 catalog는 검색 포털처럼 보여도 운영에는 쓸 수 없다. 운영 catalog의 최소 필드는 다음처럼 정한다.
| 필드 | 운영 의미 | 비어 있을 때 생기는 문제 |
|---|---|---|
| Owner | 장애·변경·문의의 1차 책임자 | 알림이 팀 채널에 묻히고 의사결정이 지연됨 |
| Domain | 주문, 결제, 마케팅 등 비즈니스 경계 | 영향도 분석 결과를 담당 조직으로 묶기 어려움 |
| Description | 자산이 무엇을 의미하는지 | 같은 이름의 테이블과 metric을 잘못 사용함 |
| SLA/Freshness | 언제까지 최신이어야 하는지 | 늦어도 장애인지 판단할 수 없음 |
| Classification | PII, financial, confidential 등 | 접근제어와 masking 정책 적용이 누락됨 |
| Lifecycle status | active, deprecated, experimental | 삭제·마이그레이션 판단이 어려움 |
Owner는 개인 한 명보다 팀 또는 역할 기반 alias가 안전하다. 단, 실제 escalation 담당자는 on-call이나 runbook에서 찾을 수 있어야 한다. data-platform@, payments-analytics@처럼 팀 소유를 두고, incident runbook에는 현재 rotation을 연결하는 방식이 운영에 맞다.
Apache Atlas 계열 도구는 entity detail에서 properties, lineage, relationships, classifications, audits, schema 같은 탭으로 자산 맥락을 보여준다. 여기서 classifications는 단순 label보다 강한 의미를 갖는다. 예를 들어 PII classification은 lineage를 따라 전파하거나, Ranger 같은 정책 엔진과 연결해 masking/access control에 사용할 수 있다. 즉 catalog tag는 장식이 아니라 정책 입력값이 될 수 있다.
4. Impact analysis는 배포 전 체크리스트에 들어가야 한다
Impact analysis는 특정 자산의 upstream/downstream 의존성을 따라가 변경 영향 범위를 찾는 작업이다. DataHub는 lineage가 있는 entity에서 upstream/downstream dependency를 UI와 GraphQL로 조회하고, entity type, platform, owner 등으로 필터링하거나 CSV로 내보내는 workflow를 제공한다. 운영 관점에서는 이 기능을 “궁금할 때 보는 화면”이 아니라 변경 관리 절차에 넣어야 한다.
변경 전 질문
| 변경 유형 | 확인할 lineage 방향 | 승인 기준 |
|---|---|---|
| source 컬럼 삭제 | downstream | 참조하는 mart/dashboard/job이 없거나 migration 완료 |
| mart metric 정의 변경 | downstream | 소비자 owner에게 공지, dashboard 비교 완료 |
| upstream freshness 장애 | downstream | SLA 영향 소비자 식별, 임시 공지 범위 결정 |
| dashboard 지표 이상 | upstream | source, ingestion, transform 중 의심 구간 좁히기 |
| PII classification 추가 | downstream | 파생 컬럼과 export sink에 masking/권한 정책 반영 |
Impact analysis에서 흔한 실수는 degree를 무조건 크게 잡는 것이다. 5-hop downstream을 한 번에 펼치면 그래프가 커져서 아무도 보지 않는다. 기본은 1-hop으로 실제 owner를 확인하고, 위험한 자산만 2-hop, 3-hop으로 확장한다. DataHub 문서도 기본 dependency degree를 낮게 잡아 무거운 lineage query를 줄이는 방식을 설명한다. 실무에서도 “넓게 펼치기”보다 “owner, domain, platform 필터로 좁히기”가 더 유용하다.
5. Lineage 수집 방식: compile-time과 runtime을 섞는다
Lineage는 한 가지 방식으로 완성되지 않는다. dbt manifest처럼 compile-time에 SQL 모델 관계를 알 수 있는 경우도 있고, Airflow/Spark/Flink/OpenLineage처럼 실행 중 event로 수집하는 편이 정확한 경우도 있다. Warehouse query history는 실제 실행된 쿼리를 바탕으로 관계를 보완할 수 있지만, 임시 분석 쿼리까지 섞이면 production lineage가 지저분해질 수 있다.
| 수집 방식 | 장점 | 주의점 |
|---|---|---|
| dbt manifest / docs | 모델 의존성과 column metadata를 안정적으로 수집 | dbt 밖의 job, dashboard, ad hoc query는 빠짐 |
| OpenLineage runtime event | job run, input/output dataset, run 상태를 실행 시점에 수집 | instrumentation 누락 시 coverage gap 발생 |
| Airflow/Spark integration | orchestration과 처리 엔진 맥락을 같이 볼 수 있음 | task dependency와 data dependency를 구분해야 함 |
| Warehouse query history | 실제 실행된 SQL 기반으로 보완 가능 | 임시 쿼리, 권한, 비용, parser 한계 관리 필요 |
| Manual curation | 핵심 metric·owner·business term 보정에 유용 | 오래되기 쉬우므로 review 주기가 필요 |
OpenLineage의 핵심 모델은 run, job, dataset이고, event는 job lifecycle에 맞춰 START, COMPLETE 같은 상태와 input/output dataset metadata를 보낸다. 이 모델은 “실제로 이 job run이 무엇을 읽고 무엇을 썼는가”를 남기는 데 강하다. 반면 catalog의 설명, owner, glossary는 실행 이벤트만으로는 충분하지 않다. 그래서 runtime lineage와 curated catalog metadata를 합쳐야 한다.
6. Lineage 품질도 모니터링해야 한다
Lineage 자체도 데이터다. 따라서 품질 기준이 필요하다. 잘못된 lineage는 없는 lineage보다 위험할 수 있다. 변경 영향이 없다고 믿고 컬럼을 삭제했는데 실제 dashboard가 깨지면 catalog 신뢰가 무너진다.
Lineage 운영 지표
| 지표 | 질문 | 알림 또는 리뷰 기준 |
|---|---|---|
| Coverage | production asset 중 lineage가 있는 비율은? | Tier-1 자산 95% 이상 목표 |
| Owner completeness | owner가 비어 있는 자산은? | Tier-1 owner missing은 배포 차단 |
| Freshness of metadata | lineage edge가 최근에도 갱신됐는가? | 30일 이상 미갱신 edge review |
| Orphan asset | upstream/downstream/usage가 없는 자산은? | 삭제 후보 또는 catalog 누락 확인 |
| Broken reference | 존재하지 않는 URN/entity를 참조하는가? | ingestion job 또는 soft-delete 정리 |
| Manual edge ratio | 수동 edge가 너무 많은가? | 자동 수집 대상 확대 검토 |
운영자는 lineage ingestion job 자체도 관측해야 한다. metadata 수집 파이프라인이 실패하면 실제 데이터 파이프라인은 정상이어도 catalog가 오래된 상태로 남는다. 특히 migration 기간에는 예전 테이블과 새 테이블이 동시에 존재하므로 stale edge와 deprecated asset을 주기적으로 정리해야 한다.
7. 실전 runbook: 컬럼 변경 요청이 들어왔을 때
예를 들어 raw_orders.status에 새 enum 값이 추가되거나 컬럼명이 바뀌는 변경 요청이 들어왔다고 하자. 운영 절차는 다음 순서가 안전하다.
- Catalog에서 자산 owner와 domain을 확인한다.
- Column-level lineage가 있으면
raw_orders.status를 직접·간접 참조하는 downstream 컬럼을 조회한다. - Table-level lineage로 관련 mart, dashboard, ML feature, export job을 1-hop부터 확인한다.
- Owner 기준으로 영향 목록을 묶고, Tier-1 소비자부터 확인 요청을 보낸다.
- dbt tests, data quality rules, accepted values, dashboard filter가 새 값을 허용하는지 점검한다.
- 배포 후 lineage와 catalog metadata가 새 schema로 갱신됐는지 확인한다.
- 문제가 생기면 upstream source, transform job, serving layer 중 어느 구간에서 의미가 깨졌는지 lineage로 범위를 좁힌다.
여기서 핵심은 lineage 결과를 그대로 믿고 끝내지 않는 것이다. 중요한 변경은 실제 쿼리 사용량, dashboard usage, 최근 job run, owner 확인을 함께 본다. Lineage는 의사결정을 빠르게 해주는 근거이지, 모든 판단을 자동으로 대신하는 시스템은 아니다.
마무리: “찾을 수 있음”보다 “운영에 쓸 수 있음”이 중요하다
Catalog와 lineage의 목표는 멋진 데이터 지도 자체가 아니다. 변경 전에 영향을 줄이고, 장애 때 원인을 빨리 좁히고, 민감정보와 책임 경계를 분명히 하며, 사용하지 않는 자산을 정리하는 것이다. 그러려면 table-level lineage로 넓은 coverage를 먼저 만들고, 위험한 컬럼과 Tier-1 자산부터 column-level lineage를 깊게 붙인다. 동시에 owner, domain, classification, SLA 같은 catalog metadata를 비워 두지 않는다.
운영자가 물어야 할 마지막 질문은 단순하다. “내일 이 컬럼을 바꿔야 한다면, 누구에게 물어보고 무엇이 깨질지 10분 안에 알 수 있는가?” 이 질문에 답할 수 있으면 lineage와 catalog는 문서가 아니라 운영 도구가 된 것이다.
References
- OpenLineage, “OpenLineage,” https://openlineage.io/
- OpenLineage, “Column Level Lineage Dataset Facet,” https://openlineage.io/docs/spec/facets/dataset-facets/column_lineage_facet/
- DataHub, “About DataHub Lineage,” https://docs.datahub.com/docs/features/feature-guides/lineage
- DataHub, “Lineage Impact Analysis,” https://docs.datahub.com/docs/act-on-metadata/impact-analysis
- Cloudera Docs, “Apache Atlas dashboard tour,” https://docs.cloudera.com/runtime/7.3.1/cdp-governance-overview/topics/atlas-dashboard.html
- Cloudera Docs, “Working with Atlas classifications and labels,” https://docs.cloudera.com/runtime/7.2.17/atlas-working-with-classifications/atlas-classifications.pdf