액티브-액티브 충돌 이해
액티브-액티브 모드에서 pgactive를 사용하는 경우 여러 노드에서 동일한 테이블에 쓰면 데이터 충돌이 발생할 수 있습니다. 일부 클러스터링 시스템은 분산 잠금을 사용하여 동시 액세스를 방지하지만 pgactive는 지리적으로 분산된 애플리케이션에 더 적합한 낙관적 접근 방식을 취합니다.
일부 데이터베이스 클러스터링 시스템은 분산 잠금을 사용하여 동시 데이터 액세스를 방지합니다. 이 접근 방식은 서버가 가까이에 있을 때 작동하지만 우수한 성능을 위해 매우 짧은 지연 시간을 요구하기 때문에 지리적으로 분산된 애플리케이션을 지원하지 않습니다. pgactive 확장은 분산 잠금(비관적 접근 방식)을 사용하는 대신 낙관적 접근 방식을 사용합니다. 이는 다음을 의미합니다.
-
가능하면 충돌을 방지하는 데 도움이 됩니다.
-
특정 유형의 충돌이 발생하도록 허용합니다.
-
충돌이 발생할 때 충돌을 해결합니다.
이 접근 방식은 분산 애플리케이션을 구축할 때 더 많은 유연성을 제공합니다.
충돌이 발생하는 방법
노드 간 충돌은 관련된 모든 트랜잭션이 동일한 노드에서 동시에 발생한 경우 발생할 수 없는 이벤트 시퀀스에서 발생합니다. 노드는 트랜잭션 커밋 후에만 변경 사항을 교환하기 때문에 각 트랜잭션은 커밋된 노드에서 개별적으로 유효하지만 그동안 다른 작업을 수행한 다른 노드에서 실행되는 경우에는 유효하지 않습니다. pgactive 적용은 기본적으로 다른 노드에서 트랜잭션을 재생하므로 적용 중인 트랜잭션과 수신 노드에서 커밋된 트랜잭션 간에 충돌이 있는 경우 재생 작업이 실패할 수 있습니다.
모든 트랜잭션이 단일 노드에서 실행될 때 대부분의 충돌이 발생할 수 없는 이유는 PostgreSQL에 이를 방지하기 위한 다음과 같은 트랜잭션 간 통신 메커니즘이 있기 때문입니다.
-
UNIQUE 인덱스
-
SEQUENCE
-
행 및 관계 잠금
-
SERIALIZABLE 종속성 추적
이러한 모든 메커니즘은 원치 않는 동시성 문제를 방지하기 위해 트랜잭션 간에 통신하는 방법입니다.
pgactive는 분산 트랜잭션 관리자 또는 잠금 관리자를 사용하지 않으므로 지연 시간을 줄이고 네트워크 파티션을 잘 처리합니다. 그러나 이는 서로 다른 노드의 트랜잭션이 서로 완전히 격리되어 실행됨을 의미합니다. 격리는 일반적으로 데이터베이스 일관성을 개선하지만 이 경우 충돌을 방지하려면 격리를 줄여야 합니다.
충돌 유형
발생할 수 있는 충돌은 다음과 같습니다.
주제
PRIMARY KEY 또는 UNIQUE 충돌
행 충돌은 여러 작업이 단일 노드에서 불가능한 방식으로 동일한 행 키를 수정하려고 할 때 발생합니다. 이러한 충돌은 가장 일반적인 유형의 데이터 충돌을 나타냅니다.
pgactive는 마지막 업데이트 우선 처리 또는 사용자 지정 충돌 핸들러를 통해 감지된 충돌을 해결합니다.
행 충돌에는 다음이 포함됩니다.
-
INSERT 및 INSERT
-
INSERT 및 UPDATE
-
UPDATE 및 DELETE
-
INSERT 및 DELETE
-
DELETE 및 DELETE
-
INSERT 및 DELETE
INSERT/INSERT 충돌
이 가장 일반적인 충돌은 서로 다른 두 노드의 INSERT가 동일한 PRIMARY KEY 값(또는 PRIMARY KEY가 없는 경우 동일한 UNIQUE 제약 조건 값)으로 튜플을 만들 때 발생합니다.
pgactivelink는 최신 튜플을 유지하기 위해 원본 호스트의 타임스탬프를 사용하여 INSERT 충돌을 해결합니다. 사용자 지정 충돌 핸들러를 사용하여 이 기본 동작을 재정의할 수 있습니다. 이 프로세스에는 특별한 관리자 작업이 필요하지 않지만 pgactivelink는 모든 노드에서 INSERT 작업 중 하나를 삭제합니다. 사용자 지정 핸들러가 구현하지 않는 한 자동 데이터 병합이 수행되지 않습니다.
pgactivelink는 단일 제약 조건 위반과 관련된 충돌만 해결할 수 있습니다. INSERT가 여러 UNIQUE 제약 조건을 위반하는 경우 추가 충돌 해결 전략을 구현해야 합니다.
여러 UNIQUE 제약 조건을 위반하는 INSERT
INSERT/INSERT 충돌은 PRIMARY KEY를 포함하여 여러 UNIQUE 제약 조건을 위반할 수 있습니다. pgactivelink는 단일 UNIQUE 제약 조건과 관련된 충돌만 처리할 수 있습니다. 충돌이 여러 UNIQUE 제약 조건을 위반하면 적용 작업자가 실패하고 다음 오류를 반환합니다.
multiple unique constraints violated by remotely INSERTed tuple.
이전 버전에서는 이 상황으로 인해 '분산된 고유성 충돌' 오류가 대신 발생했습니다.
이러한 충돌을 해결하려면 수동 조치를 취해야 합니다. 충돌하는 로컬 튜플을 DELETE하거나 UPDATE하여 새 원격 튜플과의 충돌을 제거합니다. 여러 충돌하는 튜플을 해결해야 할 수 있습니다. 현재 pgactivelink는 여러 고유 제약 조건을 위반하는 튜플을 무시, 삭제 또는 병합하는 기능을 기본적으로 제공하지 않습니다.
참고
자세한 내용은 여러 UNIQUE 제약 조건을 위반하는 UPDATE를 참조하세요.
UPDATE/UPDATE 충돌
이 충돌은 PRIMARY KEY를 변경하지 않고 두 노드가 동시에 동일한 튜플을 수정할 때 발생합니다. pgactivelink는 마지막 업데이트 우선 로직 또는 사용자 지정 충돌 핸들러(정의된 경우)를 사용하여 이러한 충돌을 해결합니다. PRIMARY KEY는 튜플 매칭 및 충돌 해결에 필수적입니다. PRIMARY KEY가 없는 테이블의 경우 pgactivelink는 다음 오류와 함께 UPDATE 작업을 거부합니다.
Cannot run UPDATE or DELETE on table (tablename) because it does not have a
primary key.
PRIMARY KEY의 UPDATE 충돌
pgactive에는 PRIMARY KEY 업데이트를 처리할 때 제한 사항이 있습니다. PRIMARY KEY에서 UPDATE 작업을 수행할 수 있지만 pgactive는 이러한 작업에 대해 마지막 업데이트 우선 로직을 사용하여 충돌을 자동으로 해결할 수 없습니다. PRIMARY KEY 업데이트가 기존 값과 충돌하지 않도록 해야 합니다. PRIMARY KEY 업데이트 중에 충돌이 발생하면 수동 개입이 필요한 분산 충돌이 됩니다. 이러한 상황 처리에 대한 자세한 내용은 분산 충돌 섹션을 참조하세요.
여러 UNIQUE 제약 조건을 위반하는 UPDATE
수신 UPDATE가 여러 UNIQUE 제약 조건 또는 PRIMARY KEY 값을 위반하는 경우 pgactivelink는 마지막 업데이트 우선 충돌 해결을 적용할 수 없습니다. 이 동작은 여러 제약 조건 위반이 있는 INSERT 작업과 유사합니다. 이러한 상황에서는 수동 개입이 필요한 분산 충돌이 발생합니다. 자세한 내용은 분산 충돌 섹션을 참조하세요.
UPDATE/DELETE 충돌
이 충돌은 한 노드가 행을 UPDATE하는 동시에 다른 노드가 해당 행을 DELETE할 때 발생합니다. 이 경우 재생 시 UPDATE/DELETE 충돌이 발생합니다. 해결 방법은 사용자 지정 충돌 핸들러에서 달리 지정하지 않는 한 DELETE 이후에 도착하는 모든 UPDATE를 삭제하는 것입니다.
pgactivelink는 튜플을 매칭하고 충돌을 해결하기 위해 PRIMARY KEY가 필요합니다. PRIMARY KEY가 없는 테이블의 경우 다음 오류와 함께 DELETE 작업을 거부합니다.
Cannot run UPDATE or DELETE on table (tablename) because it does not have a
primary key.
참고
pgactivelink는 UPDATE/DELETE와 INSERT/UPDATE 충돌을 구분할 수 없습니다. 두 경우 모두 UPDATE는 존재하지 않는 행에 영향을 미칩니다. 비동기식 복제와 노드 간 재생 순서의 부재로 인해 pgactivelink는 UPDATE가 새 행(INSERT가 아직 수신되지 않음)인지 아니면 삭제된 행인지 확인할 수 없습니다. 두 시나리오 모두에서 pgactivelink는 UPDATE를 삭제합니다.
INSERT/UPDATE 충돌
이 충돌은 다중 노드 환경에서 발생할 수 있습니다. 이는 한 노드가 행을 INSERT, 두 번째 노드를 UPDATE하고, 세 번째 노드가 원래 INSERT보다 먼저 UPDATE를 수신할 때 발생합니다. 사용자 지정 충돌 트리거에서 달리 지정하지 않는 한 기본적으로 pgactivelink는 UPDATE를 삭제하여 이러한 충돌을 해결합니다. 이 해결 방법을 사용하면 노드 간에 데이터 불일치가 발생할 수 있습니다. 유사한 시나리오 및 처리에 대한 자세한 내용은 UPDATE/DELETE 충돌 섹션을 참조하세요.
DELETE/DELETE 충돌
이 충돌은 서로 다른 두 노드가 동시에 동일한 튜플을 삭제할 때 발생합니다. pgactivelink는 두 DELETE 작업의 최종 결과가 동일하기 때문에 이러한 충돌을 무해하게 간주합니다. 이 시나리오에서 pgactivelink는 데이터 일관성에 영향을 주지 않고 DELETE 작업 중 하나를 무시합니다.
외래 키 제약 조건 충돌
FOREIGN KEY 제약 조건은 기존 로컬 데이터에 원격 트랜잭션을 적용할 때 충돌을 일으킬 수 있습니다. 이러한 충돌은 일반적으로 트랜잭션이 원본 노드의 논리적 순서와 다른 순서로 적용될 때 발생합니다.
기본적으로 pgactive는 session_replication_role이 replica
인 상태로 변경 사항을 적용하여 복제 중에 외래 키 검사를 우회합니다. 액티브-액티브 구성에서는 외래 키 위반이 발생할 수 있습니다. 대부분의 위반은 일시적이며 복제가 동기화되면 해결됩니다. 그러나 pgactive는 노드 간 행 잠금을 지원하지 않기 때문에 참조 대상이 없는 외래 키가 발생할 수 있습니다.
이 동작은 파티션 내성 비동기식 액티브-액티브 시스템에 고유합니다. 예를 들어 노드 A는 새 하위 행을 삽입하는 반면 노드 B는 상위 행을 동시에 삭제합니다. 시스템은 노드 간에 이러한 유형의 동시 수정을 방지할 수 없습니다.
외래 키 충돌을 최소화하려면 다음을 수행하는 것이 좋습니다.
-
외래 키 관계를 밀접하게 관련된 엔터티로 제한합니다.
-
가능하면 단일 노드에서 관련 엔터티를 수정합니다.
-
수정이 거의 필요하지 않은 개체를 선택합니다.
-
수정을 위한 애플리케이션 수준 동시성 제어를 구현합니다.
제외 제약 조건 충돌
pgactive 링크는 제외 제약 조건을 지원하지 않으며 이러한 조건이 만들어지지 못하게 합니다.
참고
기존 독립 실행형 데이터베이스를 pgactivelink 데이터베이스로 변환하는 경우 모든 제외 제약 조건을 수동으로 삭제합니다.
분산 비동기 시스템에서는 제약 조건을 위반하는 행 집합이 없음을 보장할 수 없습니다. 이는 서로 다른 노드의 모든 트랜잭션이 완전히 격리되기 때문입니다. 제외 제약 조건으로 인해 노드에서 다른 노드로 재생을 진행할 수 없는 재생 교착 상태가 발생할 수 있습니다.
pgactive Link가 제외 제약 조건을 만들도록 강제하거나 독립 실행형 데이터베이스를 pgactive Link로 변환할 때 기존 제외 제약 조건을 삭제하지 않으면 복제가 중단될 수 있습니다. 복제 진행 상황을 복원하려면 원격 트랜잭션을 적용할 수 있도록 수신되는 원격 튜플과 충돌하는 로컬 튜플을 제거하거나 변경합니다.
글로벌 데이터 충돌
pgactivelink를 사용하는 경우 노드에 역할과 같은 서로 다른 글로벌 PostgreSQL 시스템 전체 데이터가 있을 때 충돌이 발생할 수 있습니다. 이러한 충돌로 인해 주로 DDL과 같은 작업이 성공하고 한 노드에서 커밋되지만 다른 노드에는 적용되지 않을 수 있습니다.
사용자가 한 노드에 있지만 다른 노드에는 없는 경우 다음 복제 문제가 발생할 수 있습니다.
-
Node1에는 이름이
fred
인 사용자가 있지만 이 사용자는 Node2에 존재하지 않습니다. -
fred
가 Node1에서 테이블을 만들면fred
를 소유자로 사용하여 테이블이 복제됩니다. -
이 DDL 명령이 Node2에 적용되면
fred
사용자가 없기 때문에 실패합니다. -
이 실패는 Node2의 PostgreSQL 로그에서 ERROR를 생성하고
pgactive.pgactive_stats.nr_rollbacks
카운터를 증가시킵니다.
해결 방법: Node2에서 사용자 fred
를 생성합니다. 사용자는 동일한 권한이 필요하지 않지만 두 노드 모두에 있어야 합니다.
한 노드에는 테이블이 있지만 다른 노드에는 없는 경우 데이터 수정 작업이 실패합니다.
-
Node1에는 Node2에 존재하지 않는
foo
라는 테이블이 있습니다. -
Node1의
foo
테이블에 대한 모든 DML 작업은 Node2에 복제되었을 때 실패합니다.
해결 방법: 동일한 구조로 Node2에 foo
테이블을 생성합니다.
참고
pgactivelink는 현재 CREATE USER 명령 또는 DDL 작업을 복제하지 않습니다. DDL 복제는 향후 릴리스에 계획되어 있습니다.
잠금 충돌 및 교착 상태 중단
pgactive 적용 프로세스는 일반 사용자 세션처럼 작동하므로 표준 행 및 테이블 잠금 규칙을 따릅니다. 이로 인해 pgactivelink 적용 프로세스가 사용자 트랜잭션 또는 다른 적용 프로세스가 보유한 잠금을 대기할 수 있습니다.
다음 유형의 잠금은 적용 프로세스에 영향을 미칠 수 있습니다.
-
사용자 세션별 명시적 테이블 수준 잠금(LOCK TABLE ...)
-
사용자 세션에 의한 명시적 행 수준 잠금(SELECT ... FOR UPDATE/FOR SHARE)
-
외래 키로 인한 잠금
-
로컬 활동이나 다른 서버의 적용으로 인해 발생하는 행의 UPDATE, INSERT, DELETE에 따른 암시적 잠금
다음 사이에 교착 상태가 발생할 수 있습니다.
-
pgactivelink 적용 프로세스 및 사용자 트랜잭션
-
두 개의 적용 프로세스
교착 상태가 발생하면 PostgreSQL의 교착 상태 감지기는 문제 트랜잭션 중 하나를 종료합니다. pgactivelink 적용 작업자의 프로세스가 종료되면 자동으로 재시도되고 일반적으로 성공합니다.
참고
-
이러한 문제는 일시적이며 일반적으로 관리자의 개입이 필요하지 않습니다. 유휴 사용자 세션의 잠금으로 인해 적용 프로세스가 장기간 차단된 경우 사용자 세션을 종료하여 복제를 재개할 수 있습니다. 이 상황은 사용자가 다른 사용자 세션에 영향을 미치는 긴 잠금을 유지하는 경우와 유사합니다.
-
잠금 관련 재생 지연을 식별하려면 PostgreSQL에서
log_lock_waits
기능을 활성화합니다.
분산 충돌
노드 간에 동일해야 하는 데이터가 예기치 않게 다를 때 분산 충돌이 발생합니다. 이러한 충돌은 발생하지 않아야 하지만 현재 구현에서 모든 충돌을 안정적으로 방지할 수 있는 것은 아닙니다.
참고
모든 노드가 변경을 처리하기 전에 다른 노드가 동일한 행의 키를 변경할 경우 행의 PRIMARY KEY를 수정하면 분산 충돌이 발생할 수 있습니다. 프라이머리 키를 변경하지 않거나 지정된 노드 하나로 변경을 제한합니다. 자세한 내용은 PRIMARY KEY의 UPDATE 충돌 섹션을 참조하세요.
행 데이터와 관련된 분산 충돌에는 일반적으로 관리자의 개입이 필요합니다. 이러한 충돌을 해결하려면 pgactive.pgactive_do_not_replicate
를 사용하여 복제를 일시적으로 비활성화하면서 한 노드의 데이터를 다른 노드와 일치하도록 수동으로 조정해야 합니다. pgactive를 문서화된 대로 사용하고 안전하지 않은 것으로 표시된 설정이나 함수를 사용하지 않는 경우 이러한 충돌이 발생하지 않습니다.
관리자는 이러한 충돌을 수동으로 해결해야 합니다. 충돌 유형에 따라와 pgactive.pgactive_do_not_replicate
같은 고급 옵션을 사용해야 합니다. 이러한 옵션을 잘못 사용하면 상황이 악화될 수 있으므로 주의해서 사용하세요. 가능한 충돌이 다양하기 때문에 범용 해결 지침을 제공할 수 없습니다.
서로 다른 노드에서 동일해야 하는 데이터가 예기치 않게 다를 때 분산 충돌이 발생합니다. 이러한 충돌은 발생하지 않아야 하지만 현재 구현에서 이러한 모든 충돌을 안정적으로 방지할 수 있는 것은 아닙니다.
충돌 방지 또는 허용
대부분의 경우 적절한 애플리케이션 설계를 사용하여 충돌을 방지하거나 애플리케이션이 충돌을 허용하도록 할 수 있습니다.
충돌은 여러 노드에서 동시 작업이 수행되는 경우에만 발생합니다. 충돌을 방지하려면:
-
하나의 노드에만 쓰기
-
각 노드의 독립 데이터베이스 하위 집합에 쓰기(예: 각 노드에 별도의 스키마 할당)
INSERT 및 INSERT 충돌의 경우 글로벌 시퀀스를 사용하여 충돌을 완전히 방지합니다.
사용 사례에서 충돌이 허용되지 않는 경우 애플리케이션 수준에서 분산 잠금을 구현하는 것이 좋습니다. 가장 좋은 방법은 모든 충돌을 방지하려고 하지 않고 pgactive의 충돌 해결 메커니즘과 연계되도록 애플리케이션을 설계하는 것입니다. 자세한 내용은 충돌 유형 섹션을 참조하세요.
충돌 로깅
pgactivelink는 pgactive.pgactive_conflict_history
테이블에 충돌 인시던트를 로깅하여 액티브-액티브 충돌을 진단하고 처리하는 데 도움이 됩니다. 이 테이블에 대한 충돌 로깅은 pgactive.log_conflicts_to_table
을 true로 설정한 경우에만 발생합니다. 또한 pgactive 확장은 pgactive.log_conflicts_to_table
설정에 관계없이 log_min_messages가 LOG
또는 lower
로 설정된 경우 PostgreSQL 로그 파일에 충돌을 기록합니다.
충돌 기록 테이블을 사용하여 다음을 수행할 수 있습니다.
-
애플리케이션이 충돌을 생성하는 빈도 측정
-
충돌이 발생하는 위치 식별
-
애플리케이션을 개선하여 충돌률 감소
-
충돌 해결로 원하는 결과가 나오지 않는 경우 감지
-
사용자 정의 충돌 트리거 또는 애플리케이션 설계 변경이 필요한 위치 확인
행 충돌의 경우 선택적으로 행 값을 로깅할 수 있습니다. 이는 pgactive.log_conflicts_to_table
설정에 의해 제어됩니다. 참고:
-
이는 글로벌 데이터베이스 전체 옵션입니다.
-
행 값 로깅에 대한 테이블별 제어는 없습니다.
-
필드 번호, 배열 요소 또는 필드 길이에는 제한이 적용되지 않습니다.
-
충돌을 트리거할 수 있는 수 메가바이트 크기의 행으로 작업하는 경우 이 기능을 활성화하지 않는 것이 좋습니다.
충돌 기록 테이블에는 데이터베이스의 모든 테이블(각각 스키마가 다를 수 있음)의 데이터가 포함되어 있으므로 로깅된 행 값은 JSON 필드로 저장됩니다. JSON은 SQL에서 직접적으로 호출하는 것과 마찬가지로 row_to_json
을 사용하여 만들어집니다. PostgreSQL은 json_to_row
함수를 제공하지 않으므로 로깅된 JSON에서 복합 형식 튜플을 재구성하려면 테이블별 코드(PL/pgSQL, PL/Python, PL/Perl 등)가 필요합니다.
참고
사용자 정의 충돌에 대한 지원은 향후 확장 기능으로 계획되어 있습니다.