주요 컨텐츠로 이동

Delta Lake 자세히 알아보기: 트랜잭션 로그 살펴보기

Diving Into Delta Lake: Unpacking The Transaction Log

발행일: 2019년 8월 21일

제품Less than a minute

Delta Lake logo.
트랜잭션 로그는 Delta Lake를 이해하는 데 핵심적인 요소입니다. ACID 트랜잭션, 확장 가능한 메타데이터 처리, 특정 시점으로의 데이터 복구 등 가장 중요한 기능의 공통된 연결 고리이기 때문입니다. 이 문서에서는 Delta Lake 트랜잭션 로그가 무엇인지, 파일 수준에서 어떻게 작동하는지, 여러 동시 읽기 및 쓰기 문제에 대한 효과적인 솔루션을 어떻게 제공하는지 살펴보겠습니다.

Delta Lake 트랜잭션 로그란 무엇인가요?

Delta Lake 트랜잭션 로그(DeltaLog이라고도 함)는 Delta Lake 테이블이 생성된 이후 테이블에서 수행된 모든 트랜잭션의 순서가 지정된 기록입니다.

트랜잭션 로그는 어디에 사용되나요?

단일 정보 소스

Delta Lake는 Apache Spark™ 위에 구축되어 특정 테이블의 여러 읽기 및 쓰기 작업자가 동시에 테이블에서 작업할 수 있도록 합니다. 사용자에게 항상 올바른 데이터 뷰를 표시하기 위해 Delta Lake 트랜잭션 로그는 단일 정보 소스 역할을 합니다. 즉, 사용자가 테이블에 적용하는 모든 변경 사항을 추적하는 중앙 리포지토리입니다.

사용자가 Delta Lake 테이블을 처음 읽거나 마지막으로 읽은 후 수정된 열려 있는 테이블에 대해 새 쿼리를 실행하면 Spark는 트랜잭션 로그를 확인하여 테이블에 게시된 새 트랜잭션을 확인한 다음 최종 사용자의 테이블을 해당 새 변경 사항으로 업데이트합니다. 이렇게 하면 사용자의 테이블 버전이 최신 쿼리 시점을 기준으로 마스터 레코드와 항상 동기화되고 사용자가 테이블에 대해 서로 다르고 충돌하는 변경을 수행할 수 없습니다.

Delta Lake에서 원자성 구현

ACID 트랜잭션의 네 가지 속성 중 하나인 원자성데이터 레이크에서 수행된 작업(예: INSERT 또는 UPDATE)이 완전히 완료되거나 전혀 완료되지 않도록 보장합니다. 이 속성이 없으면 하드웨어 오류나 소프트웨어 버그로 인해 데이터가 테이블에 부분적으로만 기록되어 데이터가 엉망이 되거나 손상되기 쉽습니다.

트랜잭션 로그는 Delta Lake가 원자성을 보장할 수 있는 메커니즘입니다. 모든 의도와 목적을 위해 트랜잭션 로그에 기록되지 않은 내용은 발생하지 않은 것입니다. 완전히 실행되고 완료된 트랜잭션만 기록하고 해당 기록을 단일 정보 소스로 사용함으로써 Delta Lake 트랜잭션 로그를 통해 사용자는 데이터에 대해 추론하고 페타바이트 규모에서 데이터의 기본적인 신뢰성에 대해 안심할 수 있습니다.

트랜잭션 로그는 어떻게 작동하나요?

트랜잭션을 원자 커밋으로 분할

사용자가 테이블을 수정하기 위해 작업(예: INSERT, UPDATE 또는 DELETE)을 수행할 때마다 Delta Lake는 해당 작업을 아래의 작업 중 하나 이상으로 구성된 일련의 개별 단계로 나눕니다.

  • 파일 추가 - 데이터 파일을 추가합니다.
  • 파일 제거 - 데이터 파일을 제거합니다.
  • 메타데이터 업데이트 - 테이블의 메타데이터를 업데이트합니다(예: 테이블 이름, 스키마 또는 파티셔닝 변경).
  • 트랜잭션 설정 - 구조적 스트리밍 작업이 지정된 ID로 마이크로 일괄 처리를 커밋했음을 기록합니다.
  • 프로토콜 변경 - Delta Lake 트랜잭션 로그를 최신 소프트웨어 프로토콜로 전환하여 새로운 기능을 활성화합니다.
  • 커밋 정보 - 커밋에 대한 정보, 수행된 작업, 위치 및 시간에 대한 정보를 포함합니다.

그러면 이러한 작업은 커밋이라고 하는 순서가 지정된 원자 단위로 트랜잭션 로그에 기록됩니다.

예를 들어 사용자가 테이블에 새 열을 추가하고 일부 데이터를 더 추가하는 트랜잭션을 만든다고 가정합니다. Delta Lake는 해당 트랜잭션을 구성 요소 부분으로 나누고 트랜잭션이 완료되면 다음 커밋으로 트랜잭션 로그에 추가합니다.

  1. 메타데이터 업데이트 - 새 열을 포함하도록 스키마 변경
  2. 파일 추가 - 추가된 각 새 파일에 대해

파일 수준의 Delta Lake 트랜잭션 로그

사용자가 Delta Lake 테이블을 만들면 해당 테이블의 트랜잭션 로그가 _delta_log 하위 디렉터리에 자동으로 생성됩니다. 사용자가 해당 테이블을 변경하면 해당 변경 사항은 트랜잭션 로그에 순서가 지정된 원자 커밋으로 기록됩니다. 각 커밋은 000000.json부터 시작하여 JSON 파일로 기록됩니다. 테이블에 대한 추가 변경 사항은 후속 JSON 파일을 오름차순으로 생성하므로 다음 커밋은 000001.json으로, 다음 커밋은 000002.json 등으로 기록됩니다.

Delta Lake 트랜잭션 로그 파일 구조 다이어그램입니다.

예를 들어 데이터 파일 1.parquet2.parquet에서 테이블에 추가 레코드를 추가할 수 있습니다. 해당 트랜잭션은 트랜잭션 로그에 자동으로 추가되어 커밋 000000.json으로 디스크에 저장됩니다. 그런 다음 마음이 바뀌어 해당 파일을 제거하고 대신 새 파일(3.parquet)을 추가하기로 결정할 수 있습니다. 이러한 작업은 아래와 같이 트랜잭션 로그에서 다음 커밋으로 000001.json으로 기록됩니다.

동일한 파일에 대한 작업을 수행하는 두 개의 커밋을 보여 주는 다이어그램입니다.

1.parquet2.parquet이 더 이상 Delta Lake 테이블의 일부가 아니더라도 해당 추가 및 제거는 여전히 트랜잭션 로그에 기록됩니다. 이러한 작업은 테이블에서 수행되었기 때문입니다. 궁극적으로 서로 상쇄되더라도 말입니다. Delta Lake는 테이블을 감사하거나 "특정 시점으로의 데이터 복구"를 사용하여 특정 시점에 테이블이 어떻게 보이는지 확인해야 하는 경우 정확하게 수행할 수 있도록 이러한 원자 커밋을 유지합니다.

또한 Spark는 기본 데이터 파일을 테이블에서 제거했더라도 디스크에서 파일을 즉시 제거하지 않습니다. 사용자는 VACUUM을 사용하여 더 이상 필요하지 않은 파일을 삭제할 수 있습니다.

체크포인트 파일을 사용하여 상태를 빠르게 다시 계산

트랜잭션 로그에 여러 커밋을 수행하면 Delta Lake는 동일한 _delta_log 하위 디렉터리에 Parquet 형식으로 체크포인트 파일을 저장합니다. Delta Lake는 우수한 읽기 성능을 유지하기 위해 필요에 따라 체크포인트를 자동으로 생성합니다.

파티션 디렉터리를 포함하여 Delta Lake 트랜잭션 로그 파일 구조를 보여 주는 다이어그램입니다.

이러한 체크포인트 파일은 특정 시점의 테이블 전체 상태를 저장합니다. Spark가 빠르고 쉽게 읽을 수 있는 기본 Parquet 형식으로 저장합니다. 즉, Spark 판독기에 테이블 상태를 완전히 재현할 수 있는 일종의 "바로 가기"를 제공하여 Spark가 수천 개의 작고 비효율적인 JSON 파일을 다시 처리하지 않도록 합니다.

속도를 높이기 위해 Spark는 listFrom 작업을 실행하여 트랜잭션 로그의 모든 파일을 보고 최신 체크포인트 파일로 빠르게 건너뛰고 가장 최근의 체크포인트 파일이 저장된 이후에 수행된 JSON 커밋만 처리할 수 있습니다.

이것이 어떻게 작동하는지 보여주기 위해 아래 다이어그램에 표시된 대로 000007.json을 통해 모든 커밋을 만들었다고 가정해 보겠습니다. Spark는 이 커밋을 통해 속도를 높여 테이블의 최신 버전을 자동으로 메모리에 캐시했습니다. 그러나 그 동안 다른 여러 작성자(아마도 지나치게 열성적인 팀원)가 테이블에 새 데이터를 써서 0000012.json을 통해 모든 커밋을 추가했습니다.

이러한 새 트랜잭션을 통합하고 테이블 상태를 업데이트하기 위해 Spark는 listFrom version 7 작업을 실행하여 테이블에 대한 새로운 변경 사항을 확인합니다.

Spark가 최신 체크포인트 파일을 읽어 테이블 상태를 빠르게 계산하는 방법을 보여 주는 다이어그램입니다.

Spark는 모든 중간 JSON 파일을 처리하는 대신 커밋 #10에서 테이블의 전체 상태를 포함하므로 최신 체크포인트 파일로 건너뛸 수 있습니다. 이제 Spark는 0000011.json0000012.json의 증분 처리만 수행하면 테이블의 현재 상태를 가질 수 있습니다. 그런 다음 Spark는 테이블의 버전 12를 메모리에 캐시합니다. 이 워크플로를 따르면 Delta Lake는 Spark를 사용하여 테이블 상태를 항상 효율적인 방식으로 업데이트할 수 있습니다.

여러 동시 읽기 및 쓰기 처리

이제 Delta Lake 트랜잭션 로그가 높은 수준에서 어떻게 작동하는지 이해했으므로 동시성에 대해 이야기해 보겠습니다. 지금까지 우리의 예는 사용자가 트랜잭션을 선형으로 커밋하거나 적어도 충돌 없이 커밋하는 시나리오를 주로 다루었습니다. 그러나 Delta Lake가 여러 동시 읽기 및 쓰기를 처리할 때는 어떻게 될까요?

대답은 간단합니다. Delta Lake는 Apache Spark로 구동되므로 여러 사용자가 테이블을 한 번에 수정할 수 있을 뿐만 아니라 예상됩니다. 이러한 상황을 처리하기 위해 Delta Lake는 낙관적 동시성 제어를 사용합니다.

낙관적 동시성 제어란 무엇인가요?

낙관적 동시성 제어는 서로 다른 사용자가 테이블에 대해 수행한 트랜잭션(변경 사항)이 서로 충돌하지 않고 완료될 수 있다고 가정하는 동시 트랜잭션 처리 방법입니다. 페타바이트의 데이터를 처리할 때 사용자가 데이터의 서로 다른 부분에서 완전히 작업할 가능성이 높기 때문에 매우 빠르며, 이를 통해 충돌하지 않는 트랜잭션을 동시에 완료할 수 있습니다.

예를 들어 당신과 내가 함께 직소 퍼즐을 맞추고 있다고 상상해 보세요. 우리 둘 다 서로 다른 부분에서 작업하고 있는 한(예를 들어 당신은 모서리에서, 나는 가장자리에서) 우리 각자가 더 큰 퍼즐의 일부를 동시에 작업하고 퍼즐을 두 배 더 빨리 완성하지 못할 이유가 없습니다. 우리가 동시에 같은 조각을 필요로 할 때만 충돌이 발생합니다. 이것이 낙관적 동시성 제어입니다.

물론 낙관적 동시성 제어를 사용하더라도 사용자가 동시에 데이터의 동일한 부분을 수정하려고 시도하는 경우가 있습니다. 다행히 Delta Lake에는 이를 위한 프로토콜이 있습니다.

낙관적으로 충돌 해결

ACID 트랜잭션을 제공하기 위해 Delta Lake에는 커밋 순서를 파악하고(데이터베이스에서 직렬화 가능성 개념으로 알려짐) 두 개 이상의 커밋이 동시에 이루어질 경우 수행할 작업을 결정하는 프로토콜이 있습니다. Delta Lake는 상호 배제 규칙을 구현한 다음 충돌을 낙관적으로 해결하려고 시도하여 이러한 경우를 처리합니다. 이 프로토콜을 통해 Delta Lake는 격리라는 ACID 원칙을 제공할 수 있습니다. 즉, 여러 동시 쓰기 후 테이블의 결과 상태는 해당 쓰기가 서로 격리된 상태에서 직렬로 발생한 것과 동일합니다.

일반적으로 프로세스는 다음과 같이 진행됩니다.

  1. 시작 테이블 버전을 기록합니다.
  2. 읽기/쓰기를 기록합니다.
  3. 커밋을 시도합니다.
  4. 다른 사람이 이기면 읽은 내용이 변경되었는지 확인합니다.
  5. 반복합니다.

이 모든 것이 실시간으로 어떻게 진행되는지 확인하기 위해 아래 다이어그램을 살펴보고 Delta Lake가 충돌이 발생할 때 어떻게 관리하는지 살펴보겠습니다. 두 명의 사용자가 동일한 테이블에서 읽은 다음 일부 데이터를 추가하려고 시도한다고 상상해 보세요.

충돌하는 커밋이 있는 두 명의 사용자를 보여줌으로써 낙관적 동시성 제어를 보여줍니다.

  • Delta Lake는 변경하기 전에 읽은 테이블의 시작 테이블 버전(버전 0)을 기록합니다.
  • 사용자 1과 2는 모두 동시에 테이블에 일부 데이터를 추가하려고 시도합니다. 여기서 하나의 커밋만 다음에 와서 000001.json으로 기록될 수 있기 때문에 충돌이 발생했습니다.
  • Delta Lake는 "상호 배제" 개념으로 이 충돌을 처리합니다. 즉, 한 명의 사용자만 커밋 000001.json을 성공적으로 수행할 수 있습니다. 사용자 1의 커밋은 수락되고 사용자 2의 커밋은 거부됩니다.
  • Delta Lake는 사용자 2에 대한 오류를 발생시키는 대신 이 충돌을 낙관적으로 처리하는 것을 선호합니다. 테이블에 새로운 커밋이 이루어졌는지 확인하고 해당 변경 사항을 반영하도록 테이블을 자동으로 업데이트한 다음 새로 업데이트된 테이블에서 사용자 2의 커밋을 다시 시도하여(데이터 처리 없이) 000002.json을 성공적으로 커밋합니다.

대부분의 경우 이러한 조정은 자동으로 원활하게 성공적으로 이루어집니다. 그러나 Delta Lake가 낙관적으로 해결할 수 없는 해결할 수 없는 문제가 있는 경우(예: 사용자 1이 사용자 2도 삭제한 파일을 삭제한 경우) 유일한 옵션은 오류를 발생시키는 것입니다.

마지막으로 Delta Lake 테이블에서 이루어진 모든 트랜잭션이 디스크에 직접 저장되므로 이 프로세스는 시스템 오류가 발생하더라도 지속됨을 의미하는 내구성이라는 ACID 속성을 충족합니다.

5X 리더

Gartner®: Databricks 클라우드 데이터베이스 리더

기타 사용 사례

특정 시점으로의 데이터 복구

모든 테이블은 Delta Lake 트랜잭션 로그에 기록된 모든 커밋의 총합의 결과입니다. 트랜잭션 로그는 테이블의 원래 상태에서 현재 상태로 정확히 이동하는 방법을 자세히 설명하는 단계별 지침 가이드를 제공합니다.

따라서 원래 테이블부터 시작하여 해당 시점 이전에 이루어진 커밋만 처리하여 특정 시점에 테이블의 상태를 다시 만들 수 있습니다. 이 강력한 기능을 "특정 시점으로의 데이터 복구" 또는 데이터 버전 관리라고 하며 여러 상황에서 생명의 은인이 될 수 있습니다. 자세한 내용은 블로그 게시물 대규모 데이터 레이크를 위한 Delta Time Travel 소개를 읽거나 Delta Lake 특정 시점으로의 데이터 복구 설명서를 참조하십시오.

데이터 계보 및 디버깅

테이블에 대한 모든 변경 사항의 최종 기록인 Delta Lake 트랜잭션 로그는 사용자에게 거버넌스, 감사 및 규정 준수 목적으로 유용한 검증 가능한 데이터 계보를 제공합니다. 또한 파이프라인의 부주의한 변경 또는 버그의 원인을 유발한 정확한 작업으로 추적하는 데 사용할 수도 있습니다. 사용자는 DESCRIBE HISTORY를 실행하여 변경 사항에 대한 메타데이터를 볼 수 있습니다.

Delta Lake 트랜잭션 로그 요약

이 블로그에서는 다음을 포함하여 Delta Lake 트랜잭션 로그가 작동하는 방식에 대한 세부 정보를 자세히 살펴보았습니다.

  • 트랜잭션 로그가 무엇인지, 구조화된 방식, 커밋이 디스크에 파일로 저장되는 방식
  • 트랜잭션 로그가 단일 정보 소스 역할을 하여 Delta Lake가 원자성 원칙을 구현할 수 있도록 하는 방법
  • Delta Lake가 각 테이블의 상태를 계산하는 방법(가장 최근 체크포인트에서 따라잡기 위해 트랜잭션 로그를 사용하는 방법 포함)
  • 테이블이 변경되더라도 여러 동시 읽기 및 쓰기를 허용하기 위해 낙관적 동시성 제어를 사용하는 방법
  • Delta Lake가 상호 배제를 사용하여 커밋이 올바르게 직렬화되도록 하고 충돌이 발생할 경우 자동으로 다시 시도되는 방법
오픈 소스 Delta Lake에 관심이 있으신가요?
자세한 내용을 알아보고 최신 코드를 다운로드하고 Delta Lake 커뮤니티에 가입하려면 Delta Lake 온라인 허브를 방문하세요.

관련 항목

이 시리즈의 문서:
Delta Lake 심층 분석 #1: 트랜잭션 로그 압축 풀기
Delta Lake 심층 분석 #2: 스키마 적용 및 진화
Delta Lake 심층 분석 #3: DML 내부(업데이트, 삭제, 병합)

관련 문서:
데이터 레이크란 무엇인가요?
Delta Lake를 사용한 머신 러닝 프로덕션화

(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)

게시물을 놓치지 마세요

관심 있는 카테고리를 구독하고 최신 게시물을 받은편지함으로 받아보세요