사내 데이터 디스커버리 도구인 데이터카탈로그는 DataHub를 기반으로 구축되었습니다. DataHub는 다양한 플랫폼과 연동되는 활발한 오픈소스 프로젝트로, 필요한 기능들을 새로 개발하지 않고도 활용할 수 있다는 장점이 있습니다.
그러나 DataHub를 처음 도입했을 때, 사내 구성원들로부터 UI/UX가 불편하다는 피드백을 받았습니다. 특히 데이터에 익숙하지 않은 사용자들에게는 DataHub의 다양한 기능들이 오히려 진입 장벽이 되었습니다.
저희는 사용자들이 원하는 데이터를 쉽게 찾고 활용하는 것이 가장 중요한 가치라고 판단했습니다. 따라서 DataHub를 그대로 사용하는 대신, 사용자들에게 더 친숙한 UI/UX를 가진 도구를 개발하기로 결정했습니다.
🗒️ 데이터카탈로그 기획과 관련된 자세한 내용은 데이터카탈로그 PM이 ‘데이터 디스커버리’라는 가치를 풀어내는 방법 글을 참고해 주시기 바랍니다.
DataHub 구성
들어가기에 앞서, 사내 DataHub가 어떻게 구성되어 있는지 간략하게 말씀드리겠습니다.
출처: https://datahubproject.io/docs/0.15.0/architecture/architecture
사내 DataHub는 위의 아키텍처와 동일하게 구성되어 있습니다. 공식 Helm 차트를 이용해서 EKS에 배포하고 있으며, Persistence Tier의 일부는 AWS의 서비스를 사용하고 있습니다.
Persistence Tier에서 MySQL은 주 저장소로 데이터의 원본을 저장하고 있습니다. MySQL에 반영된 데이터는 MetadataAuditEvent로 kafka에 발행되고 이 이벤트를 통해 Elasticsearch에도 반영되어 검색에 이용됩니다.
App Tier의 Metadata Service는 데이터의 CRUD를 담당하는 중심 모듈입니다. Frontend Server는 Metadata Service에서 받아온 데이터에 추가적인 정보를 더해 클라이언트에 제공합니다.
이제 DataHub에 대한 간단한 설명을 마치고 몇 가지 주제를 통해 데이터카탈로그가 어떻게 DataHub를 활용하고 있는지 설명드리겠습니다.
OpenSearch 직접 활용을 통한 검색 기능 구현
데이터카탈로그 개발 시 가장 중점을 둔 기능은 컬럼 검색이었습니다. 검색 결과의 총개수와 함께 각 항목의 명칭과 설명을 보여주는 UI를 구현하고자 했습니다.
최초 도입했던 DataHub 0.8.30 버전은 GraphQL과 Rest.li 두 가지 형태의 API를 제공했습니다. 두 API 모두 키워드 매칭 정보는 제공했으나, 컬럼과 컬럼 코멘트의 연관 관계는 확인할 수 없었습니다.
또한, DataHub의 검색은 테이블 단위로 이루어지므로, 컬럼 단위의 결과를 표시하기 위해서는 matchedFields의 결과들을 개별 행으로 변환해야 했습니다.
{ ... "searchResults": [ { "entity": "urn:li:dataset:(urn:li:dataPlatform:hive,schema.test_table,PROD)", ... "matchedFields": [ { "name": "name", "value": "test_table", }, { "name": "fieldPaths", "value": "test_col_1", }, { "name": "fieldPaths", "value": "test_col_2", } ] }, ... ] ... }GraphQL과 Rest.li API의 검색 결과 예시입니다. 실제로는 각각 다른 JSON 스키마를 가지고 있으나,
이해를 돕기 위해 동일한 형태로 간소화했습니다.
전체 검색 결과 수를 파악하기 위해서는 모든 searchResult의 matchedFields 개수를 합산해야 했습니다. 또한, API 응답에는 컬럼과 컬럼 코멘트의 연관 관계가 포함되어 있지 않아 DB 조회를 통해 컬럼 코멘트를 추가로 조회해야 했습니다.
DataHub 자체가 저희가 제공하는 서비스였기 때문에, API를 통한 우회적인 접근보다는 DB나 OpenSearch의 데이터를 직접 활용하는 것이 성능적으로나 데이터카탈로그만의 응답 형식을 구성하는 데 있어 더 효율적이라고 판단했습니다.
사내 DataHub 환경은 Amazon OpenSearch Service를 데이터 저장소로 사용하고 있습니다. OpenSearch의 검색 속도 저하 로그를 활성화하고, DataHub가 사용하는 인덱스에 index.search.slowlog.threshold.query.debug=0 설정을 적용하여 DataHub의 OpenSearch 쿼리를 쉽게 파악할 수 있었습니다. 이를 바탕으로 데이터카탈로그가 OpenSearch와 DB에 직접 쿼리 하는 방식으로 API 로직을 구현했습니다.
데이터 리니지 구현
OpenSearch를 직접 활용 방식은 데이터 리니지 기능 구현에도 적용되었습니다.
데이터 리니지는 중심 노드를 기준으로 upstream(상위)과 downstream(하위) 방향으로 확장될 수 있습니다.
리니지 데이터가 OpenSearch에 저장되어 있어, upstream/downstream으로 확장하려면 degree(단계) 별 검색이 필요했습니다. 예를 들어, upstream 방향으로 degree 2까지의 리니지를 조회하는 방법은 다음과 같습니다.
- 중심 노드의 upstream 노드를 검색하여 degree 1에 해당하는 노드 확인
- degree 1 노드들의 upstream 노드를 다시 검색하여 degree 2 노드 확인
그래프 뷰의 리니지 확장 시에는 한 번에 하나의 degree만 추가할 수도 있고, 리스트 뷰에서는 end-to-end로 모든 리니지 노드를 한 번에 검색할 수도 있습니다.
또한, 리니지 그래프를 시각화하는 프론트엔드 개발도 필요했기 때문에, 프론트엔드 로직에 맞는 결과 형식을 제공해야 했습니다.
degree별로 여러 번의 검색이 가능해야 하고, 데이터카탈로그에 적합한 형태로 데이터를 변환해야 했기에, API를 통한 접근보다는 OpenSearch를 활용한 재귀 로직 구현이 성능과 요구사항 충족 측면에서 더 적합하다고 판단했습니다.
이미 OpenSearch를 직접 활용하고 있었기 때문에, 리니지 검색에 필요한 인덱스별 쿼리를 쉽게 파악할 수 있었고, 데이터카탈로그만의 리니지 API를 구현할 수 있었습니다.
DataHub 버전업에 따른 수정
처음 도입한 DataHub의 버전은 0.8.30입니다.
0.8.30 버전의 Spark 리니지 모듈은 캐시된 DataFrame의 리니지를 제대로 추출하지 못하는 문제가 있었습니다. 하지만 0.10.2버전의 Spark 리니지 모듈에서는 이 문제가 해소된 것을 확인했습니다.
0.10.2버전에 추가된 기능들로 그대로 Spark 리니지 모듈을 적용할 수 없어서 DataHub의 버전을 0.10.2로 올리기로 결정했습니다.
DataHub에서는 업그레이드할 때 데이터 마이그레이션도 지원하므로, 버전 업그레이드 자체는 큰 문제 없이 잘 수행되었습니다.
하지만 버전업 이후 이전과 다른 검색 결과가 나오는 문제가 발생했습니다. 검색어와 유사한 키워드를 가진 결과가 많이 나왔고, 특히 언더바가 포함된 검색에서 이전에 비해 검색 결과가 매우 많아졌습니다.
검색 결과가 바뀐다는 것이 저희가 의도한 것이 아니었고, 갑작스러운 변경은 사용자에게 혼란을 줄 수 있다고 생각하여 검색 결과를 동일하게 유지하기 위해 인덱스 설정을 0.8.30 버전으로 덮어쓰기로 결정했습니다.
데이터플랫폼팀에서는 DataHub를 Helm chart를 통해 배포하고 있습니다. Helm 차트의 redexing 옵션인 enableMappingsReindex, enableSettingsReindex 를 활용해 0.10.2버전의 인덱스 설정이 아닌, 0.8.30버전의 인덱스 설정을 사용하여 검색 결과를 동일하게 유지할 수 있었습니다.
출처: https://github.com/acryldata/datahub-helm/blob/datahub-0.2.164/charts/datahub/values.yaml#L264
데이터 리니지 모듈 개선
0.10.2 버전의 Spark 리니지 모듈은 Spark 실행 이벤트를 감지하고 쿼리 플랜을 파싱하여 리니지 데이터를 생성합니다. 하지만 테스트 과정에서 몇 가지 문제점을 발견하여 다음과 같이 개선을 진행했습니다.
S3 경로 노드 변환
리니지의 테이블 노드는 Hive 형식으로 표현하고 있었으나, Spark의 DataSource 테이블은 S3 경로로 노드가 생성되는 문제가 있었습니다.
실제 데이터는 Hive를 통해 참조되므로, 전체 리니지 파악을 위해 S3 경로를 Hive 테이블로 변환하는 로직을 추가했습니다. 이를 통해 Hive 테이블 기준으로 리니지가 연결되도록 개선했습니다.
Spark 3.4 버전 호환성 문제 해결
여러 버전의 EMR 클러스터를 제공하고 있어 다양한 Spark 버전을 지원해야 했습니다. 그중 Spark 3.4 버전에서 다음과 같은 에러가 발생했습니다.
java.lang.NoSuchMethodError: 'org.json4s.JsonAST$JValue org.apache.spark.util.JsonProtocol.sparkEventToJson(org.apache.spark.scheduler.SparkListenerEvent)원인을 분석한 결과, Spark 3.3 버전까지 존재하던 sparkEventToJson 메소드가 3.4 버전에서 sparkEventToJsonString으로 변경된 것을 확인했습니다. DataHub GitHub에서 동일한 문제를 다룬 이슈를 발견했고, 리플렉션을 활용하여 Spark 버전별로 로직을 분리하는 해결책을 참고하여 문제를 해결했습니다.
DB 데이터 수집 시 리니지 생성 문제 해결
DB 테이블을 Hive로 수집할 때 Spark SQL을 사용하고 있습니다. 또한 DataHub에 DB 테이블을 수집할 때 특정 네이밍 규칙을 적용하고 있습니다. 하지만 Spark 리니지 모듈을 기본 설정대로 사용하면 다음과 같은 형태로 리니지 데이터가 생성되어 DB 테이블과 Hive 테이블 간의 연결 관계를 제대로 표현할 수 없었습니다.
{ "inputDatasets": [ "urn:li:dataset:(urn:li:dataPlatform:mysql,datacatalog.(SELECT col FROM table),PROD)" ], "outputDatasets": [ "urn:li:dataset:(urn:li:dataPlatform:hive,schema.table,PROD)" ] }inputDatasets에 db 테이블명을 식별할 수 없어서 리니지가 연결되지 않습니다.
문제를 해결하기 위해 Spark 리니지 모듈의 JDBCRelation 처리 로직을 수정하여 DB 테이블의 리니지 노드 생성 시 설정한 네이밍 규칙에 맞추어 생성되도록 개선했습니다.
출처: https://github.com/datahub-project/datahub/blob/v0.10.2/metadata-integration/java/spark-lineage/src/main/java/datahub/spark/DatasetExtractor.java#L197
BI 도구 통합 개선
우아한형제들은 여러 BI 도구를 사용하고 있으며, 그중 Redash를 가장 많이 활용합니다. Redash에는 수백 개의 활성 대시보드와 수천 개의 차트가 존재합니다.
DataHub의 기본 수집 모듈로 데이터를 수집해 보니 2시간 이상이 소요되었습니다. Redash 데이터가 지속적으로 증가할 것으로 예상되는 상황에서, 하루에 여러 번의 데이터 수집을 위해서는 처리 시간 단축이 필수적이었습니다.
기본 Redash 수집 로직을 분석한 결과 처리 시간이 길어지는 주요 원인은 다음과 같았습니다.
- 과도한 수의 차트 데이터 처리
- SQL 파싱 소요 시간
Redash의 모든 정보가 중요한게 아니라 활성 대시보드와 이에 사용되는 차트와 Hive 테이블 정보가 핵심이라고 판단했습니다. 따라서 전체 차트가 아닌 활성 대시보드의 차트만 선별적으로 수집하기로 결정했습니다.
SQL 파싱의 경우, 0.10.2버전의 Redash 입수 모듈은 sqllineage 패키지를 사용하고 있었습니다. 이 패키지는 SQL 문법을 엄격하게 검사하여 처리 속도가 느리고, 파싱 실패 사례도 많았습니다.
테이블 수준의 리니지만 필요했기 때문에 조금 더 가볍고 파싱이 잘 되는 sql-metadata와 sqlglot 라이브러리의 조합으로 교체했습니다.
개선 결과 배치 시간은 40분 내외로 줄어들었고, SQL 파싱이 실패하는 경우도 수천 개 중 1개로 줄어들게 되었습니다.
개선된 리니지 추출 로직을 다른 BI 도구에도 적용하여, 모든 BI 도구와 Hive 테이블 간의 리니지 연결을 성공적으로 구현했습니다.
마치며
지금까지 소개한 개선 사항들 외에도, 데이터 수집 파이프라인을 고도화하는 과정에서 다양한 수정이 이루어졌습니다. 또한 데이터 이용자들의 편의성 향상을 위한 데이터카탈로그만의 고유 기능도 지속적으로 개발하고 있습니다.
현재 수천 개의 테이블, 차트, 대시보드 데이터를 처리하는 과정에서 다양한 예외 상황이 발생하고 있으며, 전체 데이터 파이프라인을 데이터카탈로그에서 완벽하게 표현하지 못하는 한계도 있습니다.
그럼에도 "전사 구성원 누구도 데이터로부터 소외되지 않는다"라는 핵심 가치 아래, 구성원들이 더 쉽게 데이터를 찾고 활용할 수 있도록 끊임없이 고민하고 발전시켜 나가고 있습니다.
우아한형제들에서 데이터플랫폼을 개발하고 있습니다.