Azure Databricks의 격리 수준 및 쓰기 충돌
테이블의 격리 수준은 트랜잭션이 동시 실행 작업에 의해 수정된 사항으로부터 격리되어야 하는 정도를 정의합니다. Azure Databricks의 쓰기 충돌은 격리 수준에 따라 다릅니다.
Delta Lake는 읽기와 쓰기 간의 ACID 트랜잭션 보장을 제공합니다. 이는 다음을 의미합니다.
- 여러 클러스터의 여러 작성자가 동시에 테이블 파티션을 수정할 수 있습니다. 작성자는 테이블의 일관된 스냅샷 보기를 보고 순차적으로 쓰기가 발생합니다.
- 읽기 권한자는 작업 중에 테이블이 수정된 경우에도 Azure Databricks 작업이 시작된 테이블의 일관된 스냅샷 보기를 계속 볼 수 있습니다.
Azure Databricks에서 ACID 보장이란?을 참조하세요.
참고 항목
Azure Databricks는 기본적으로 모든 테이블에 대해 Delta Lake를 사용합니다. 이 문서에서는 Azure Databricks의 Delta Lake에 대한 동작을 설명합니다.
Important
메타데이터 변경으로 인해 모든 동시 쓰기 작업이 실패합니다. 이러한 작업에는 테이블 프로토콜, 테이블 속성 또는 데이터 스키마에 대한 변경 내용이 포함됩니다.
스트리밍 읽기는 테이블 메타데이터를 변경하는 커밋이 발생하면 실패합니다. 스트림을 계속하려면 다시 시작해야 합니다. 권장 방법은 구조적 스트리밍에 대한 프로덕션 고려 사항을 참조하세요.
다음은 메타데이터를 변경하는 쿼리의 예입니다.
-- Set a table property.
ALTER TABLE table-name SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')
-- Enable a feature using a table property and update the table protocol.
ALTER TABLE table_name SET TBLPROPERTIES ('delta.enableDeletionVectors' = true);
-- Drop a table feature.
ALTER TABLE table_name DROP FEATURE deletionVectors;
-- Upgrade to UniForm.
REORG TABLE table_name APPLY (UPGRADE UNIFORM(ICEBERG_COMPAT_VERSION=2));
-- Update the table schema.
ALTER TABLE table_name ADD COLUMNS (col_name STRING);
행 수준 동시성과의 쓰기 충돌
행 수준 동시성은 행 수준에서 변경 내용을 검색하고 동시 쓰기가 동일한 데이터 파일에서 다른 행을 업데이트하거나 삭제할 때 발생하는 충돌을 자동으로 해결하여 동시 쓰기 작업 간의 충돌을 줄입니다.
행 수준 동시성은 일반적으로 Databricks Runtime 14.2 이상에서 사용할 수 있습니다. 행 수준 동시성은 다음 조건에 대해 기본적으로 지원됩니다.
- 삭제 벡터가 활성화되고 분할이 없는 테이블입니다.
- 삭제 벡터를 사용하지 않도록 설정하지 않은 경우 액체 클러스터링이 있는 테이블입니다.
파티션이 있는 테이블은 행 수준 동시성을 지원하지 않지만 삭제 벡터를 사용할 때 다른 모든 쓰기 작업 간의 OPTIMIZE
충돌을 방지할 수 있습니다. 행 수준 동시성에 대한 제한 사항을 참조 하세요.
다른 Databricks 런타임 버전은 행 수준 동시성 미리 보기 동작(레거시)을 참조하세요.
MERGE INTO
행 수준 동시성을 지원하려면 Databricks Runtime 14.2의 Photon이 필요합니다. Databricks Runtime 14.3 LTS 이상에서는 Photon이 필요하지 않습니다.
다음 표에서는 행 수준 동시성을 사용하도록 설정된 각 격리 수준에서 충돌할 수 있는 쓰기 작업 쌍에 대해 설명합니다.
참고 항목
ID 열이 있는 테이블은 동시 트랜잭션을 지원하지 않습니다. Delta Lake에서 ID 열 사용을 참조하세요.
INSERT (1) | UPDATE, DELETE, MERGE INTO | OPTIMIZE | |
---|---|---|---|
INSERT | 충돌할 수 없음 | ||
UPDATE, DELETE, MERGE INTO | WriteSerializable에서는 충돌할 수 없습니다. 동일한 행을 수정할 때 Serializable에서 충돌할 수 있습니다. 행 수준 동시성에 대한 제한 사항을 참조 하세요. | 동일한 행을 수정할 때 충돌할 수 있습니다. 행 수준 동시성에 대한 제한 사항을 참조 하세요. | |
최적화 | 충돌할 수 없음 | 사용할 때 ZORDER BY 충돌할 수 있습니다. 그렇지 않으면 충돌할 수 없습니다. |
사용할 때 ZORDER BY 충돌할 수 있습니다. 그렇지 않으면 충돌할 수 없습니다. |
Important
(1) 위의 테이블의 모든 INSERT
작업은 커밋하기 전에 동일한 테이블에서 데이터를 읽지 않는 추가 작업을 설명합니다. INSERT
동일한 테이블을 읽는 하위 쿼리를 포함하는 작업은 .와 동일한 동시성을 MERGE
지원합니다.
REORG
작업에는 삭제 벡터에 OPTIMIZE
기록된 변경 내용을 반영하도록 데이터 파일을 다시 쓸 때와 동일한 격리 의미 체계가 있습니다. 업그레이드를 적용하는 데 사용하는 REORG
경우 테이블 프로토콜이 변경되어 진행 중인 모든 작업과 충돌합니다.
행 수준 동시성 없는 쓰기 충돌
다음 표에서는 각 격리 수준에서 충돌할 수 있는 쓰기 작업 쌍을 설명합니다.
테이블은 파티션이 정의되어 있거나 삭제 벡터를 사용하도록 설정하지 않은 경우 행 수준 동시성을 지원하지 않습니다. 행 수준 동시성을 위해서는 Databricks Runtime 14.2 이상이 필요합니다.
참고 항목
ID 열이 있는 테이블은 동시 트랜잭션을 지원하지 않습니다. Delta Lake에서 ID 열 사용을 참조하세요.
INSERT (1) | UPDATE, DELETE, MERGE INTO | OPTIMIZE | |
---|---|---|---|
INSERT | 충돌할 수 없음 | ||
UPDATE, DELETE, MERGE INTO | WriteSerializable에서는 충돌할 수 없습니다. Serializable에서 충돌할 수 있습니다. 파티션과의 충돌 방지를 참조 하세요. | Serializable 및 WriteSerializable에서 충돌할 수 있습니다. 파티션과의 충돌 방지를 참조 하세요. | |
최적화 | 충돌할 수 없음 | 사용되지 않는 한 ZORDER BY 삭제 벡터가 활성화된 테이블에서 충돌할 수 없습니다. 그렇지 않으면 충돌할 수 있습니다. |
사용되지 않는 한 ZORDER BY 삭제 벡터가 활성화된 테이블에서 충돌할 수 없습니다. 그렇지 않으면 충돌할 수 있습니다. |
Important
(1) 위의 테이블의 모든 INSERT
작업은 커밋하기 전에 동일한 테이블에서 데이터를 읽지 않는 추가 작업을 설명합니다. INSERT
동일한 테이블을 읽는 하위 쿼리를 포함하는 작업은 .와 동일한 동시성을 MERGE
지원합니다.
REORG
작업에는 삭제 벡터에 OPTIMIZE
기록된 변경 내용을 반영하도록 데이터 파일을 다시 쓸 때와 동일한 격리 의미 체계가 있습니다. 업그레이드를 적용하는 데 사용하는 REORG
경우 테이블 프로토콜이 변경되어 진행 중인 모든 작업과 충돌합니다.
행 수준 동시성에 대한 제한 사항
행 수준 동시성에는 몇 가지 제한 사항이 적용됩니다. 다음 작업의 경우 충돌 해결은 Azure Databricks의 쓰기 충돌에 대한 일반적인 동시성을 따릅니다. 행 수준 동시성이 없는 쓰기 충돌을 참조 하세요.
- 다음을 포함하여 복잡한 조건부 절이 있는 명령:
- 구조체, 배열 또는 맵과 같은 복잡한 데이터 형식에 대한 조건입니다.
- 비결정적 식 및 하위 쿼리를 사용하는 조건입니다.
- 상호 관련된 하위 쿼리를 포함하는 조건입니다.
- Databricks Runtime 14.2
MERGE
에서 명령은 대상 테이블에서 명시적 조건자를 사용하여 원본 테이블과 일치하는 행을 필터링해야 합니다. 병합 확인의 경우 필터는 동시 작업의 필터 조건에 따라 충돌할 수 있는 행만 검색합니다.
참고 항목
행 수준 충돌 검색은 총 실행 시간을 늘릴 수 있습니다. 많은 동시 트랜잭션의 경우 기록기는 충돌 해결보다 대기 시간을 우선 순위로 지정하며 충돌이 발생할 수 있습니다.
삭제 벡터에 대한 모든 제한 사항도 적용됩니다. 제한 사항을 참조 하세요.
Delta Lake는 언제 테이블을 읽지 않고 커밋하나요?
Delta Lake INSERT
또는 추가 작업은 다음 조건이 충족되는 경우 커밋하기 전에 테이블 상태를 읽지 않습니다.
- 논리는 SQL 논리 또는 추가 모드를 사용하여
INSERT
표현됩니다. - 논리에는 쓰기 작업의 대상이 되는 테이블을 참조하는 하위 쿼리 또는 조건부가 없습니다.
다른 커밋과 마찬가지로 Delta Lake는 트랜잭션 로그에서 메타데이터를 사용하여 커밋 시 테이블 버전의 유효성을 검사하고 확인하지만 테이블의 버전은 실제로 읽지 않습니다.
참고 항목
많은 일반적인 패턴은 작업을 사용하여 MERGE
테이블 조건에 따라 데이터를 삽입합니다. 문을 사용하여 INSERT
이 논리를 다시 작성할 수 있지만 조건식이 대상 테이블의 열을 참조하는 경우 이러한 문은 동시성 제한과 MERGE
동일합니다.
직렬화 가능 격리 수준과 직렬화 가능 격리 수준 쓰기
테이블의 격리 수준은 트랜잭션이 동시 트랜잭션에 의해 수정되지 않도록 격리해야 하는 정도를 정의합니다. Azure Databricks의 Delta Lake는 Serializable 및 WriteSerializable의 두 가지 격리 수준을 지원합니다.
직렬화 가능: 가장 강력한 격리 수준입니다. 커밋된 쓰기 작업과 모든 읽기를 직렬화할 수 있도록 합니다. 연산은 테이블에 표시된 것과 동일한 결과를 생성하는 일련의 실행 시퀀스가 있는 한 허용됩니다. 쓰기 작업의 경우 직렬 시퀀스는 테이블의 기록에 표시된 것과 정확히 동일합니다.
WriteSerializable(기본값): Serializable보다 약한 격리 수준입니다. 즉, 읽기가 아닌 쓰기 작업만 직렬화할 수 있습니다. 그러나 여전히 스냅샷 격리보다 강력합니다. WriteSerializable은 가장 일반적인 작업에 대해 데이터 일관성과 가용성의 균형을 잘 유지하기 때문에 기본 격리 수준입니다.
이 모드에서는 델타 테이블의 내용이 테이블 기록에 표시된 작업 시퀀스에서 예상되는 내용과 다를 수 있습니다. 이 모드를 사용하면 기록에서 Y가 X 이후에 커밋되었음을 보여 주더라도 X(즉, X 및 Y 작업)의 특정 쌍이 동시 쓰기 쌍을 진행하여 Y가 X 이전에 수행된 것처럼(즉, 둘 사이에 직렬화 가능) 결과를 진행할 수 있기 때문입니다. 이 순서를 다시 정렬하지 않도록 하려면 Serializable로 테이블 격리 수준을 설정하여 이러한 트랜잭션이 실패하도록 합니다.
읽기 작업은 항상 스냅샷 격리를 사용합니다. 쓰기 격리 수준은 판독기가 기록에 따르면 "존재하지 않음" 테이블의 스냅샷을 볼 수 있는지 여부를 결정합니다.
직렬화 가능 수준의 경우 판독기는 항상 기록을 준수하는 테이블만 볼 수 있습니다. WriteSerializable 수준의 경우 판독기에서 델타 로그에 없는 테이블을 볼 수 있습니다.
예를 들어 txn1에서 삭제된 데이터를 삽입하는 txn1, 장기 실행 삭제 및 txn2를 고려합니다. txn2 및 txn1이 완료되고 기록에서 해당 순서로 기록됩니다. 기록에 따르면 txn2에 삽입된 데이터는 테이블에 없어야 합니다. 직렬화 가능 수준의 경우 판독기는 txn2로 삽입된 데이터를 볼 수 없습니다. 그러나 WriteSerializable 수준의 경우 판독기에서 txn2로 삽입된 데이터를 볼 수 있습니다.
각 격리 수준에서 서로 충돌할 수 있는 작업 형식과 가능한 오류에 대한 자세한 내용은 분할 및 분리 명령 조건을 사용하여 충돌 방지를 참조하세요.
격리 수준 설정
ALTER TABLE
명령을 사용하여 격리 수준을 설정합니다.
ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = <level-name>)
여기서 <level-name>
는 Serializable
또는 WriteSerializable
입니다.
예를 들어 격리 수준을 기본 WriteSerializable
에서 Serializable
로 변경하려면 다음을 실행합니다.
ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')
분할 및 비연속 명령 조건을 사용한 충돌 방지
"충돌할 수 있음"으로 표시된 모든 경우에 두 작업이 충돌할지 여부는 동일한 파일 집합에서 작동하는지 여부에 따라 다릅니다. 작업 조건에서 사용된 것과 동일한 열로 테이블을 분할하여 두 파일 집합을 분리할 수 있습니다. 예를 들어, 두 명령 UPDATE table WHERE date > '2010-01-01' ...
및 DELETE table WHERE date < '2010-01-01'
은 테이블이 날짜별로 분할되지 않은 경우 충돌합니다. 둘 다 동일한 파일 집합을 수정하려고 시도할 수 있기 때문입니다. date
로 테이블을 분할하면 충돌을 피할 수 있습니다. 따라서 명령에서 일반적으로 사용되는 조건에 따라 테이블을 분할하면 충돌을 크게 줄일 수 있습니다. 그러나 카디널리티가 높은 열로 테이블을 분할하면 많은 수의 하위 디렉터리로 인해 다른 성능 문제가 발생할 수 있습니다.
충돌 예외
트랜잭션 충돌이 발생하면 다음 예외 중 하나가 표시됩니다.
ConcurrentAppendException
이 예외는 동시 실행 작업이 사용자 작업에서 읽는 동일한 파티션(또는 파티션되지 않은 테이블의 임의의 위치)에 파일을 추가할 때 발생합니다. 파일 추가는 INSERT
, DELETE
, UPDATE
또는 MERGE
작업으로 인해 발생할 수 있습니다.
기본 격리 수준의 WriteSerializable
경우 맹목적인 INSERT
작업(즉, 데이터를 읽지 않고 맹목적으로 데이터를 추가하는 작업)에서 추가된 파일은 동일한 파티션(또는 분할되지 않은 테이블의 모든 위치)을 터치하더라도 작업과 충돌하지 않습니다. 격리 수준이 Serializable
로 설정되면 블라인드 추가가 충돌할 수 있습니다.
이 예외는 종종 동시 DELETE
, UPDATE
또는 MERGE
작업 중에 throw됩니다. 동시 실행 작업이 실제으로 다른 파티션 디렉터리를 업데이트할 수 있지만 그중 하나는 다른 하나가 동시에 업데이트하는 동일한 파티션을 읽을 수 있으므로 충돌이 발생할 수 있습니다. 작동 조건에서 분리를 명시적으로 지정하여 이를 방지할 수 있습니다. 아래 예제를 고려해 보세요.
// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
source.as("s"),
"s.user_id = t.user_id AND s.date = t.date AND s.country = t.country")
.whenMatched().updateAll()
.whenNotMatched().insertAll()
.execute()
다른 날짜 또는 국가에 대해 위의 코드를 동시에 실행한다고 가정합니다. 각 작업은 대상 Delta 테이블의 독립 파티션에서 작업하기 때문에 충돌이 예상되지 않습니다. 그러나 조건이 충분히 명시적이지 않고 전체 테이블을 검사할 수 있으며 다른 파티션을 업데이트하는 동시 실행 작업과 충돌할 수 있습니다. 대신 다음 예와 같이 병합 조건에 특정 날짜와 국가를 추가하도록 명세서를 다시 작성할 수 있습니다.
// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
source.as("s"),
"s.user_id = t.user_id AND s.date = t.date AND s.country = t.country AND t.date = '" + <date> + "' AND t.country = '" + <country> + "'")
.whenMatched().updateAll()
.whenNotMatched().insertAll()
.execute()
이 작업은 이제 다른 날짜와 국가에서 동시에 실행하는 것이 안전합니다.
ConcurrentDeleteReadException
이 예외는 동시 실행 작업이 사용자 작업에서 읽은 파일을 삭제할 때 발생합니다. 일반적인 원인은 파일을 다시 쓰는 DELETE
, UPDATE
또는 MERGE
작업입니다.
ConcurrentDeleteDeleteException
이 예외는 동시 실행 작업이 사용자 작업도 삭제하는 파일을 삭제한 경우에 발생합니다. 이는 동일한 파일을 다시 쓰는 두 개의 동시 압축 작업으로 인해 발생할 수 있습니다.
MetadataChangedException
이 예외는 동시 트랜잭션이 Delta 테이블의 메타데이터를 업데이트할 때 발생합니다. 일반적인 원인은 ALTER TABLE
작업 또는 테이블의 스키마를 업데이트하는 Delta 테이블에 대한 쓰기입니다.
ConcurrentTransactionException
동일한 검사점 위치를 사용하는 스트리밍 쿼리가 동시에 여러 번 시작되고 동시에 Delta 테이블에 쓰려고 시도하는 경우. 두 개의 스트리밍 쿼리가 동일한 검사점 위치를 사용하고 동시에 실행되어서는 안 됩니다.
ProtocolChangedException
이 예외는 다음과 같은 경우에 발생할 수 있습니다.
- Delta 테이블이 새 프로토콜 버전으로 업그레이드된 경우. 향후 작업이 성공하려면 Databricks Runtime을 업그레이드해야 할 수 있습니다.
- 여러 작성자가 동시에 테이블을 작성하거나 교체하는 경우.
- 여러 작성자가 동시에 빈 경로에 쓰는 경우.
자세한 내용은 Azure Databricks에서 Delta Lake 기능 호환성을 관리하는 방법을 참조 하세요 .
행 수준 동시성 미리 보기 동작(레거시)
이 섹션에서는 Databricks Runtime 14.1 이하의 행 수준 동시성에 대한 미리 보기 동작에 대해 설명합니다. 행 수준 동시성에는 항상 삭제 벡터가 필요합니다.
Databricks Runtime 13.3 LTS 이상에서 액체 클러스터링이 사용하도록 설정된 테이블은 행 수준 동시성을 자동으로 사용하도록 설정합니다.
Databricks Runtime 14.0 및 14.1에서 클러스터 또는 SparkSession에 대해 다음 구성을 설정하여 삭제 벡터가 있는 테이블에 대해 행 수준 동시성을 사용하도록 설정할 수 있습니다.
spark.databricks.delta.rowLevelConcurrencyPreview = true
Databricks Runtime 14.1 이하에서 Photon이 아닌 컴퓨팅은 작업에 대한 DELETE
행 수준 동시성만 지원합니다.