주요 컨텐츠로 이동

(번역: Dongwook Kim) Original Blog Post

데이터브릭스 델타 라이브 테이블(DLT)은 데이터 엔지니어가 작성하고 유지 관리해야 하는 코드의 양을 줄여 강력한 데이터 처리 파이프라인의 개발을 획기적으로 간소화합니다. 또한 데이터 유지 관리 및 인프라 운영의 필요성을 줄여주며, 사용자가 환경 간에 코드 및 파이프라인 구성을 원활하게 추진할 수 있도록 지원합니다. 하지만 여전히 사람들은 파이프라인에서 코드 테스트를 수행해야 하며, 이를 효율적으로 수행할 수 있는 방법에 대한 질문을 자주 받습니다.

이 블로그 게시물에서는 여러 고객과 함께 일한 경험을 바탕으로 다음 항목을 다룹니다:

  • 델타 라이브 테이블에 데브옵스 모범 사례를 적용하는 방법.
  • 단위 및 통합 테스트를 용이하게 하기 위해 DLT 파이프라인의 코드를 구조화하는 방법.
  • DLT 파이프라인의 개별 변환에 대한 단위 테스트를 수행하는 방법.
  • 전체 DLT 파이프라인을 실행하여 통합 테스트를 수행하는 방법.
  • 개발 운영 스테이지 간에 DLT 구성을 프로모션 하는 방법
  • 모든 것을 통합하여 CI/CD 파이프라인을 구성하는 방법(Azure DevOps를 예로 들어 설명).

DLT에 데브옵스(DevOps) 사례 적용하기

데브옵스 수행은 소프트웨어 개발 수명 주기(SDLC)를 단축하는 동시에 높은 품질을 제공하는 것을 목표로 합니다. 일반적으로 아래 단계를 포함합니다:

  • 소스 코드 및 인프라의 버전 관리.
  • 코드 리뷰.
  • 환경 분리(개발/스테이지/프로덕션).
  • 단위 및 통합 테스트를 통해 개별 소프트웨어 구성 요소 및 전체 제품에 대한 자동화된 테스트.
  • 지속적인 통합(테스트) 및 변경 사항의 지속적인 배포(CI/CD).

이러한 모든 사례는 델타 라이브 테이블 파이프라인에도 적용할 수 있습니다:

Figure: DLT development workflow
그림: DLT 개발  워크플로우

이를 위해 데이터브릭스 제품 포트폴리오의 다음과 같은 기능을 사용합니다:

  • Databricks Repos는  다양한 Git 서비스에 대한 인터페이스를 제공하므로 코드 버전 관리, CI/CD 시스템과의 통합, 환경 간 코드 프로모션에 사용할 수 있습니다.
  • CI/CD 파이프라인을 구현하기 위한 Databricks CLI(또는 Databricks REST API).
  • 필요한 모든 인프라를 배포하고 최신 상태로 유지하기 위한 Databricks Terraform Provider.

DLT 파이프라인의 권장되는 개발 워크플로우 요약은 다음과 같습니다:

Applying software development & DevOps best practices to Delta Live Table pipelines

  1. 개발자가 변경 사항을 위해 별도의 Git 브랜치를 사용하여 Git 저장소의 자체 체크 아웃에서 DLT 코드를 개발 중입니다.
  2. 코드가 준비되고 테스트가 완료되면 코드가 Git에 커밋되고 Pull Request가 생성됩니다.
  3. CI/CD 시스템이 커밋에 반응하여 빌드 파이프라인(CI/CD의 CI 부분)을 시작하여 스테이징 데이터브릭스 Repo를  변경 사항으로 업데이트하고 단위 테스트 실행을 트리거합니다.
    a) 통합 테스트도 함께 실행할 수 있지만, 경우에 따라 일부 브랜치에 대해서만 또는 별도의 파이프라인만 실행할 수도 있습니다.
  4. 모든 테스트가 성공적으로 완료되고 코드가 검토되면 변경 사항이 Git 리포지토리의 메인(또는 전용 브랜치)에 병합됩니다.
  5. 변경 사항을 특정 브랜치(예: releases)로 병합하면 프로덕션 환경의 데이터브릭스 Repo를 업데이트하는 릴리스 파이프라인(CI/CD의 CD 부분)이 트리거될 수 있으므로 다음에 파이프라인이 실행될 때 코드 변경 사항이 적용됩니다.

블로그 포스트의 나머지 부분에서는 일반적인 레이크하우스 아키텍처의 전형적인 브론즈/실버 레이어를 설명하기 위해 두 개의 테이블로만 구성된 매우 간단한 DLT 파이프라인을 예시로 사용하겠습니다. 배포 지침과 함께 전체 소스 코드는 깃허브에서 확인할 수 있습니다.

 

Figure: Example DLT pipeline
Figure: Example DLT pipeline

Note: DLT는 SQL과 Python API를 모두 제공하지만, 대부분의 블로그에서는 Python 구현에 초점을 맞추고 있지만 대부분의 모범 사례는 SQL 기반 파이프라인에도 적용할 수 있습니다.

델타 라이브 테이블을 사용한 개발 주기

델타 라이브 테이블로 개발할 때 일반적인 개발 프로세스는 다음과 같습니다:

  1. 노트북에 코드가 작성됩니다.
  2. 다른 코드가 준비되면 사용자는 DLT UI로 전환하고 파이프라인을 시작합니다. (이 프로세스를 더 빠르게 진행하려면 개발 모드에서 파이프라인을 실행하는 것이 좋습니다. 그러면 컴퓨팅 리소스를 계속 기다릴 필요가 없습니다).
  3. 파이프라인이 완료되거나 오류로 인해 실패하면 사용자는 결과를 분석하고 코드를 추가/수정하면서 이 과정을 반복합니다.
  4. 코드가 준비되면 커밋됩니다.

복잡한 파이프라인의 경우, 수십 개의 테이블/뷰가 있는 복잡한 파이프라인과 많은 라이브러리가 연결된 경우 파이프라인의 시작이 상대적으로 길어질 수 있기 때문에 이러한 개발 주기는 상당한 오버헤드를 초래할 수 있습니다. 사용자 입장에서는 샘플 데이터로 대화형 클러스터에서 개별 데이터 변환을 평가하고 테스트하여 매우 빠른 피드백을 얻는 것이 더 쉬울 것입니다.

DLT 파이프라인의 코드 구조화하기

개별 함수를 평가하고 테스트할 수 있게 하려면 올바른 코드 구조를 갖는 것이 매우 중요합니다. 일반적인 접근 방식은 모든 데이터 변환을 Spark 데이터프레임을 수신 및 반환하는 개별 함수로 정의하고, DLT 파이프라인 함수에서 이러한 함수를 호출하여 DLT 실행 그래프를 형성하는 것입니다. 이를 달성하는 가장 좋은 방법은 files in repos 기능을 사용해 Python 파일을 일반 Python 모듈로 노출하여 데이터브릭스 노트북이나 다른 Python 코드로 가져올 수 있도록 하는 것입니다. DLT는 기본적으로 Python 파일을 Python 모듈로 가져올 수 있는 files in repos 기능을 지원합니다(files in repos을 사용할 때 Python의 sys.path에 두 개의 항목이 추가되는데, 하나는 Repo 최상위 디렉토리용이고 다른 하나는 호출하는 노트북의 현재 디렉터리용입니다). 이렇게 하면 Python 모듈로 가져올 Repo 루트 아래의 전용 폴더에 별도의 Python 파일로 코드 작성을 시작할 수 있습니다:

 

Figure: source code for a Python package
그림: 파이썬 패키지의 소스 코드

그리고 이 파이썬 패키지의 코드는 DLT 파이프라인 코드 내에서 사용할 수 있습니다:

Figure: Using functions from the Python package in the DLT code
그림: DLT 코드에서 Python 패키지의 함수 사용

이 특정 DLT 코드의 함수는 매우 간단합니다. 업스트림 테이블에서 데이터를 읽고 Python 모듈에 정의된 변환을 적용하는 것 뿐입니다. 이 접근 방식을 사용하면 DLT 코드를 더 간단하게 이해하고 로컬에서 또는 대화형 클러스터에 연결된 별도의 노트북을 사용해 테스트하기 쉽게 만들 수 있습니다. 변환 로직을 별도의 Python 모듈로 분할하면 노트북에서 변환을 대화형으로 테스트하고, 이러한 변환에 대한 단위 테스트를 작성하고, 전체 파이프라인을 테스트할 수 있습니다(다음 섹션에서 테스트에 대해 설명합니다).

단위 및 통합 테스트가 포함된 데이터브릭스 리포지토리의 최종 구조는 다음과 같습니다:

Figure: Recommended code layout in Databricks Repos
그림: 데이터브릭스 리포지토리의 권장 코드 레이아웃

이 코드 구조는 공통된 데이터 변환을 공유하는 여러 DLT 파이프라인으로 구성될 수 있는 대규모 프로젝트에서 특히 중요합니다.

단위 테스트 구현

위에서 언급했듯이 데이터 변환을 별도의 Python 모듈로 분리하면 개별 함수의 동작을 검사하는 단위 테스트를 더 쉽게 작성할 수 있습니다. 이러한 단위 테스트를 구현하는 방법을 선택할 수 있습니다:

  • 예를 들어 pytest 를 사용해서 우리는 로컬에서 실행할 수 있는 파이썬 파일로 정의할 수 있습니다. 해당 접근 방식은 다음과 같은 장점이 있습니다.
    • IDE를 사용하여 이러한 데이터 변환을 개발 및 테스트할 수 있으며, 예를 들어 다른 IDE를 사용하는 경우 Visual Studio Code용 Databricks extension 또는 databricks sync 명령을 사용하여 로컬 코드를 Databricks Repo와 동기화할 수 있습니다.
    • 이러한 테스트는 Databricks 리소스를 사용할 필요 없이 CI/CD 빌드 파이프라인 내에서 실행할 수 있습니다(일부 Databricks 전용 기능을 사용하거나 PySpark로 코드를 실행할 수 있는지에 따라 달라질 수 있음).
    • 정적 코드 및 코드 커버리지 분석, 코드 리팩토링 도구, 대화형 디버깅 등 더 많은 개발 관련 도구에 액세스할 수 있습니다.
    • Python 코드를 라이브러리로 패키징하여 여러 프로젝트에 첨부할 수도 있습니다.
  • 노트북 기반으로 정의할 수도 있습니다.
    • 항상 샘플 코드와 테스트를 대화형으로 실행할 수 있으므로 피드백을 더 빠르게 받을 수 있습니다.
    • Nutter와 같은 추가 도구를 사용하여 CI/CD 빌드 파이프라인(또는 로컬 머신)에서 노트북 실행을 트리거하고 보고를 위한 결과를 수집할 수 있습니다.

데모 리포지토리에는 테스트의 로컬 실행노트북으로 테스트를 실행하는 이 두 가지 접근 방식에 대한 샘플 코드가 포함되어 있습니다. CI 파이프라인은 두 가지 접근 방식을 모두 보여줍니다.

이 두 가지 접근 방식은 모두 Python 코드에만 적용 가능하며, SQL을 사용하여 DLT 파이프라인을 구현하는 경우 다음 섹션에서 설명하는 접근 방식을 따라야 합니다.

통합 테스트 구현

단위 테스트를 통해 개별 변환이 정상적으로 작동하는지 확인할 수 있지만, 전체 파이프라인도 제대로 작동하는지 확인해야 합니다. 이는 보통 전체 파이프라인을 실행하는 통합 테스트로 구현되지만, 보통은 소량의 데이터에 대해 실행되며 실행 결과를 검증해야 합니다. 델타 라이브 테이블을 사용하면 통합 테스트를 구현하는 여러 가지 방법이 있습니다:

  • DLT 를 사용하지 않는 코드에 대해 일반적으로 수행되는 것과 유사하게 여러 Task가 포함된 데이터브릭스 Workflows로 구현합니다.
  • DLT 기대치(expectations)를 사용하여 파이프라인의 결과를 확인합니다.

데이터브릭스 워크플로우(Workflows)로 통합 테스트 구현하기

이 경우 여러 개의 Task로 구성된 데이터브릭스 워크플로우를 사용하여 통합 테스트를 구현할 수 있습니다(task values을 사용하여 task 간에 데이터 저장소 경로등의 데이터를 전달할 수도 있습니다). 일반적으로 이러한 워크플로우는 다음과 같은 작업으로 구성됩니다:

  • DLT 파이프라인을 위한 데이터를 설정합니다.
  • 해당 데이터에서 파이프라인을 실행합니다.
  • 생성된 결과의 유효성 검사를 수행합니다.
Figure: Implementing integration test with Databricks Workflows
그림: 데이터브릭스 워크플로우로 통합 테스트 구현하기

이 접근 방식의 가장 큰 단점은 설정 및 유효성 검사 작업을 위해 상당한 양의 보조 코드를 작성해야 하며, 설정 및 유효성 검사 작업을 실행하는 데 추가 컴퓨팅 자원이 필요하다는 것입니다.

DLT 기대치(Expectations)를 사용하여 통합 테스트 구현하기

결과가 제공된 기대치와 일치하지 않을 경우 실패 연산자를 사용해 데이터에 DLT 기대치를 적용하고 파이프라인을 실패시키는 추가 DLT 테이블로 DLT 파이프라인을 확장하여 DLT에 대한 통합 테스트를 구현할 수 있습니다. 기대치가 첨부된 DLT 테이블을 정의하는 추가 노트북을 포함하는 별도의 DLT 파이프라인을 생성하기만 하면 매우 쉽게 구현할 수 있습니다.

예를 들어 실버 테이블에 유형 열에 허용된 데이터만 포함되는지 확인하려면 다음 DLT 테이블을 추가하고 기대치를 첨부하면 됩니다:

@dlt.table(comment="Check type")
@dlt.expect_all_or_fail({"valid type": "type in ('link', 'redlink')",
                         "type is not null": "type is not null"})
def filtered_type_check():
  return dlt.read("clickstream_filtered").select("type")

통합 테스트를 위한 DLT 파이프라인의 결과는 다음과 같습니다(실행 그래프에 데이터가 유효한지 확인하는 두 개의 추가 테이블이 있습니다)

Figure: Implementing integration tests using DLT expectations
그림: DLT 기대치(Expectations)를 사용하여 통합 테스트 구현하기

이는 DLT 파이프라인의 통합 테스트를 수행하는 데 권장되는 접근 방식입니다. 이 접근 방식을 사용하면 추가 컴퓨팅 리소스가 필요하지 않습니다. 모든 것이 동일한 DLT 파이프라인에서 실행되므로 클러스터 재사용이 가능하고, 모든 데이터가 보고 등에 사용할 수 있는 DLT 파이프라인의 이벤트 로그에 기록됩니다.

행의 고유성 확인, 결과에서 특정 행의 존재 여부 확인 등 고급 유효성 검사에 DLT 기대치를 사용하는 더 많은 예는 DLT 설명서를 참조하세요. 또한, 서로 다른 DLT 파이프라인 간에 재사용할 수 있도록 DLT 기대값 라이브러리를 공유 Python 모듈로 구축할 수도 있습니다.

개발 운영 환경에서 DLT 자산 전환 하기

DLT의 변경사항을 개발에서 운영환경으로 전환에 대해 이야기하기 위해서는 먼저 DLT의 여러 자산에 대해서 알아야합니다:

  • 파이프라인에서 데이터 변환을 정의하는 소스 코드
  • 델타 라이브 테이블(DLT) 파이프라인들에 대한 설정

코드를 프로모션하는 가장 간단한 방법은 데이터브릭스 Repos를 사용하여 Git Repo 저장된 코드로 작업하는 것입니다. 코드 버전을 유지하는 것 외에도, Databricks Repos를 사용하면 Repos REST API 또는 Databricks CLI를 사용하여 코드 변경 사항을 다른 환경으로 쉽게 전파할 수 있습니다.

DLT는 처음부터 파이프라인 구성에서 코드를 분리하여 스키마, 데이터 위치 등을 지정할 수 있게 함으로써 단계 간 개발 운영간 프로모션이 더 쉬워지도록 합니다. 따라서 각 개발 운영환경 마다 동일한 코드를 사용하는 별도의 DLT 구성을 정의하는 동시에 데이터를 다른 위치에 저장하고 다른 클러스터 크기 등을 사용할 수 있습니다.

파이프라인 설정을 정의하기 위해 델타 라이브 테이블 REST API 또는 데이터브릭스 CLI의 파이프라인 명령을 사용할 수 있지만, 인스턴스 풀, 클러스터 정책 또는 기타 종속성을 사용해야 하는 경우에는 이 작업이 어려워집니다. 이 경우 보다 유연한 대안은 다른 리소스에 대한 종속성을 보다 쉽게 처리할 수 있는 Databricks Terraform Provider의 databricks_pipeline 리소스이며, Terraform 모듈을 사용하여 Terraform 코드를 모듈화하여 재사용할 수 있도록 만들 수 있습니다. 제공된 코드 저장소에는 여러 환경에 DLT 파이프라인을 배포하기 위한 Terraform 코드의 예제가 포함되어 있습니다.

모든 것을 통합하여 CI/CD 파이프라인 형성하기

모든 개별 부분을 구현한 후에는 CI/CD 파이프라인을 구현하는 것이 비교적 쉽습니다. GitHub 리포지토리에는 Azure DevOps용 빌드 파이프라인이 포함되어 있습니다(다른 시스템도 지원 가능 - 일반적으로 파일 구조에 차이가 있음). 이 파이프라인은 특정 이벤트에 따라 서로 다른 테스트 집합을 실행하는 기능을 보여주는 두 단계로 구성되어 있습니다:

  • onPushrelease 브랜치 및 버전 태그를 제외한 모든 Git 브랜치로 푸시할 때 실행됩니다. 이 단계에서는 단위 테스트 결과(로컬 및 노트북 모두)만 실행하고 보고합니다.
  • onReleaserelease 브랜치에 대한 커밋에서만 실행되며, 단위 테스트 외에도 통합 테스트가 포함된 DLT 파이프라인을 실행합니다.
Figure: Structure of Azure DevOps build pipeline
그림: Azure DevOps 빌드 파이프라인의 구조

onRelease 단계에서 통합 테스트를 실행하는 것을 제외하면 두 단계의 구조는 동일하며, 다음 단계로 구성됩니다:

  1. 변경 사항이 있는 브랜치를 체크아웃합니다.
  2. 환경 설정 - Python 환경 관리 및 필수 종속성 설치에 사용되는 Poetry를 설치합니다.
  3. 스테이징 환경에서 데이터브릭스 리포지토리를 업데이트합니다.
  4. PySpark를 사용하여 로컬 단위 테스트를 실행합니다.
  5. Nutter를 사용하여 데이터브릭스 노트북으로 구현된 단위 테스트를 실행합니다.
  6. releases 브랜치의 경우 통합 테스트를 실행합니다.
  7. 테스트 결과를 수집하고 Azure DevOps에 게시합니다.
Figure: Tasks inside the onRelease stage of the build pipeline
그림: 빌드 파이프라인의 onRelease 단계 내 작업

테스트 실행 결과는 Azure DevOps에 다시 보고되므로 추적가능 합니다.

Figure: Reporting the tests execution results
그림: 테스트 실행 결과 보고

releases 브랜치에 커밋이 완료되고 모든 테스트가 성공하면 release pipeline이 트리거되어 프로덕션 Databricks Repos를 업데이트할 수 있으므로 코드의 변경 사항이 다음번 DLT 파이프라인 실행 시 반영됩니다.

Figure: Release pipeline to deploy code changes to production DLT pipeline
그림: 프로덕션 DLT 파이프라인에 코드 변경 사항을 배포하기 위한 릴리스 파이프라인

이 블로그 게시물에 설명된 접근 방식을 여러분의 델타 라이브 테이블 파이프라인에 적용해 보세요! 제공된 데모 리포지토리에는 필요한 모든 코드와 함께 설정 지침 및 Azure DevOps에 모든 것을 배포하기 위한 Terraform 코드가 포함되어 있습니다.

Databricks 무료로 시작하기

관련 포스트

모든 플랫폼 블로그 포스트 보기