Loading...
MySQL 9.5 Reference Manual 9.5의 25.7.12 NDB Cluster Replication Conflict Resolution의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
여러 개의 source(순환 replication 포함)가 관련된 replication 구성을 사용하는 경우, 서로 다른 source가 replica의 동일한 row를 서로 다른 데이터로 갱신하려고 시도할 수 있습니다. NDB Cluster Replication의 conflict resolution은 사용자 정의 resolution column을 사용하여, 특정 source의 update를 replica에 적용할 것인지 여부를 결정함으로써 이러한 conflict를 해결하는 수단을 제공합니다.
NDB Cluster에서 지원되는 몇 가지 유형의 conflict resolution (NDB$OLD(), NDB$MAX(), NDB$MAX_DELETE_WIN(), NDB$MAX_INS() 및 NDB$MAX_DEL_WIN_INS())은 이 사용자 정의 column을 “timestamp” column으로 구현합니다(그러나 이 column의 type은 이 절에서 나중에 설명하듯이 TIMESTAMP가 될 수 없습니다). 이러한 유형의 conflict resolution은 항상 transaction 단위가 아닌 row 단위로 적용됩니다. epoch 기반 conflict resolution 함수인 NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 epoch가 복제되는 순서를 비교하며(따라서 이들 함수는 트랜잭션 단위입니다). 이 절에서 나중에 설명하듯이, conflict 발생 시 replica에서 resolution column 값을 비교하는 데 서로 다른 방법을 사용할 수 있습니다. 사용되는 방법은 단일 table, database, server에 대해 설정할 수 있으며, 또는 패턴 매칭을 사용하여 하나 이상의 table 집합에 대해 설정할 수 있습니다. 패턴 매치 사용에 대한 정보는 Matching with wildcards를 참조하십시오. 여기서는 mysql.ndb_replication table의 db, table_name, server_id column에서 패턴 매칭을 사용하는 방법에 대해 설명합니다.
또한 resolution 함수가 update 적용 여부를 결정할 때 적절한 선택을 할 수 있도록, resolution column에 관련된 값이 올바르게 채워지도록 하는 책임은 애플리케이션에 있음을 명심해야 합니다.
Conflict resolution을 위해서는 source와 replica 모두에서 준비 작업을 수행해야 합니다. 이러한 작업은 다음 목록과 같습니다.
--ndb-log-updated-only (이 절에서 나중에 설명)을 적용하거나, 또는 하나 이상의 특정 table에 대해 mysql.ndb_replication table에 적절한 row를 넣음으로써 수행합니다(자세한 내용은 ndb_replication Table 참조).참고
매우 큰 column(예: TEXT나 BLOB column)을 가진 table을 복제하는 경우, --ndb-log-updated-only는 binary log 크기를 줄이고 max_allowed_packet을 초과하여 발생할 수 있는 replication 실패를 방지하는 데에도 유용합니다.
이 문제에 대한 추가 정보는 Section 19.5.1.21, “Replication and max_allowed_packet”을 참조하십시오.
replica에서는, 어떤 유형의 conflict resolution을 적용할지(“latest timestamp wins”, “same timestamp wins”, “primary wins”, “primary wins, complete transaction”, 또는 없음)를 결정해야 합니다. 이는 mysql.ndb_replication system table을 사용하여 수행되며, 하나 이상의 특정 table에 적용됩니다(자세한 내용은 ndb_replication Table 참조).
NDB Cluster는 또한 read conflict detection(한 cluster에서 특정 row에 대한 read와 다른 cluster에서 동일한 row에 대한 update 또는 delete 간의 conflict 탐지)을 지원합니다. 이를 위해서는 replica에서 ndb_log_exclusive_reads를 1로 설정하여 exclusive read lock을 획득해야 합니다. conflict가 발생한 read에 의해 읽힌 모든 row는 exceptions table에 기록됩니다. 자세한 내용은 Read conflict detection and resolution을 참조하십시오.
NDB$MAX_INS() 또는 NDB$MAX_DEL_WIN_INS()를 사용하는 경우, NDB는 들어오는 row가 이미 존재하지 않으면 insert로, 존재하면 update로 매핑하여 WRITE_ROW event를 멱등적으로 적용할 수 있습니다.
NDB$MAX_INS() 또는 NDB$MAX_DEL_WIN_INS() 이외의 어떤 conflict resolution 함수를 사용하는 경우에는, row가 이미 존재하면 들어오는 write는 항상 거부됩니다.
NDB$OLD(), NDB$MAX(), NDB$MAX_DELETE_WIN(), NDB$MAX_INS(), NDB$MAX_DEL_WIN_INS() 함수를 사용하여 timestamp 기반 conflict resolution을 수행할 때, 우리는 종종 update 결정에 사용되는 column을 “timestamp” column이라고 부릅니다. 그러나 이 column의 data type은 절대 TIMESTAMP가 아니어야 하며, 대신 그 data type은 INT (INTEGER) 또는 BIGINT여야 합니다. “timestamp” column은 UNSIGNED이고 NOT NULL이어야 합니다.
이 절의 뒷부분에서 논의하는 NDB$EPOCH() 및 NDB$EPOCH_TRANS() 함수는 primary 및 secondary NDB Cluster에서 적용된 replication epoch의 상대적인 순서를 비교하여 작동하며, timestamp를 사용하지 않습니다.
update 작업은 “before” image와 “after” image, 즉 update가 적용되기 전후의 table 상태 관점에서 볼 수 있습니다. 일반적으로 primary key를 가진 table을 update할 때 “before” image는 크게 중요하지 않습니다. 그러나 replica에서 update된 값을 사용할지 여부를 update 단위로 결정해야 할 때는, source의 binary log에 두 image가 모두 기록되도록 해야 합니다. 이는 이 절 뒤에서 설명하는 대로 mysqld에 대한 --ndb-log-update-as-write 옵션을 사용하여 수행합니다.
주의
전체 row를 log할지, update된 column만 log할지는 MySQL server가 시작될 때 결정되며 online으로 변경할 수 없습니다. 따라서 다른 logging 옵션으로 mysqld를 재시작하거나 새로운 mysqld 인스턴스를 시작해야 합니다.
Conflict resolution은 일반적으로 conflict가 발생할 수 있는 server에서 활성화합니다. logging 방법 선택과 마찬가지로, 이는 mysql.ndb_replication table의 row로 활성화합니다.
NBT_UPDATED_ONLY_MINIMAL 및 NBT_UPDATED_FULL_MINIMAL은 “before” 값이 primary key가 아닌 column에 대해 필요하지 않기 때문에 NDB$EPOCH(), NDB$EPOCH2(), NDB$EPOCH_TRANS()와 함께 사용할 수 있습니다. NDB$MAX() 및 NDB$OLD()와 같이 기존 값을 필요로 하는 conflict resolution 알고리즘은 이러한 binlog_type 값과 함께 올바르게 작동하지 않습니다.
이 절에서는 NDB Replication에서 conflict detection 및 resolution에 사용할 수 있는 함수에 대한 자세한 정보를 제공합니다.
_column_name_의 값이 source와 replica 모두에서 동일하면 update가 적용되며, 그렇지 않으면 update는 replica에 적용되지 않고 예외가 log에 기록됩니다. 이는 다음 pseudocode로 나타낼 수 있습니다.
1if (source_old_column_value == replica_current_column_value) 2 apply_update(); 3else 4 log_exception();
이 함수는 “same value wins” conflict resolution에 사용할 수 있습니다. 이 유형의 conflict resolution은 잘못된 source에서 온 update가 replica에 적용되지 않도록 보장합니다.
주의
이 함수는 source의 “before” image에서 가져온 column 값을 사용합니다.
update 또는 delete 작업의 경우, source에서 들어오는 row의 “timestamp” column 값이 replica의 값보다 크면 적용되고, 그렇지 않으면 replica에 적용되지 않습니다. 이는 다음 pseudocode로 나타낼 수 있습니다.
1if (source_new_column_value > replica_current_column_value) 2 apply_update();
이 함수는 “greatest timestamp wins” conflict resolution에 사용할 수 있습니다. 이 유형의 conflict resolution은 conflict가 발생했을 때 가장 최근에 update된 row 버전이 유지되도록 보장합니다.
이 함수는 write 작업 간 conflict에 대해 별도의 효과를 갖지 않으며, 단지 동일한 primary key를 가진 이전 write가 존재하는 경우 항상 새로운 write를 거부합니다. 동일한 primary key를 사용하는 write 작업이 존재하지 않는 경우에만 새로운 write가 허용되고 적용됩니다. write 간 conflict resolution에는 NDB$MAX_INS()를 사용할 수 있습니다.
주의
이 함수는 source의 “after” image에서 가져온 column 값을 사용합니다.
이 함수는 NDB$MAX()의 변형입니다. delete 작업의 경우 timestamp가 존재하지 않기 때문에, NDB$MAX()를 사용하는 delete는 사실상 NDB$OLD처럼 처리됩니다. 그러나 일부 use case에서 이는 최선이 아닐 수 있습니다. NDB$MAX_DELETE_WIN()에서는 source에서 들어오는 row가 기존 row를 추가 또는 update할 때 “timestamp” column 값이 replica의 값보다 크면 적용됩니다. 그러나 delete 작업은 항상 더 큰 값을 가진 것으로 처리됩니다. 이는 다음 pseudocode로 나타낼 수 있습니다.
1if ( (source_new_column_value > replica_current_column_value) 2 || 3 operation.type == "delete") 4 apply_update();
이 함수는 “greatest timestamp, delete wins” conflict resolution에 사용할 수 있습니다. 이 유형의 conflict resolution은 conflict가 발생했을 때, 삭제되었거나(또는) 가장 최근에 update된 row 버전이 유지되도록 보장합니다.
참고
NDB$MAX()와 마찬가지로, 이 함수는 source의 “after” image에서 가져온 column 값을 사용합니다.
이 함수는 write 작업 간 conflict resolution을 지원합니다. 그러한 conflict는 “NDB$MAX_INS()”에서 다음과 같이 처리됩니다.
conflict하는 write가 없으면 해당 write를 적용합니다(NDB$MAX()와 동일).
그렇지 않으면, 다음과 같이 “greatest timestamp wins” conflict resolution을 적용합니다.
들어오는 write의 timestamp가 conflict하는 write의 timestamp보다 크면, 들어오는 작업을 적용합니다.
들어오는 write의 timestamp가 더 크지 않으면, 들어오는 write 작업을 거부합니다.
insert 작업을 처리할 때, NDB$MAX_INS()는 source와 replica의 timestamp를 다음 pseudocode와 같이 비교합니다.
1if (source_new_column_value > replica_current_column_value) 2 apply_insert(); 3else 4 log_exception();
update 작업의 경우, source의 update된 timestamp column 값이 replica의 timestamp column 값과 다음과 같이 비교됩니다.
1if (source_new_column_value > replica_current_column_value) 2 apply_update(); 3else 4 log_exception();
이는 NDB$MAX()가 수행하는 것과 동일합니다.
delete 작업의 처리 역시 NDB$MAX()(따라서 NDB$OLD())와 동일하며, 다음과 같이 수행됩니다.
1if (source_new_column_value == replica_current_column_value) 2 apply_delete(); 3else 4 log_exception();
이 함수는 write 작업 간 conflict resolution을 지원하며, 동시에 NDB$MAX_DELETE_WIN()과 같은 “delete wins” resolution을 제공합니다. write conflict는 NDB$MAX_DEL_WIN_INS()에 의해 다음과 같이 처리됩니다.
conflict하는 write가 없으면 해당 write를 적용합니다(NDB$MAX_DELETE_WIN()과 동일).
그렇지 않으면, 다음과 같이 “greatest timestamp wins” conflict resolution을 적용합니다.
들어오는 write의 timestamp가 conflict하는 write의 timestamp보다 크면, 들어오는 작업을 적용합니다.
들어오는 write의 timestamp가 더 크지 않으면, 들어오는 write 작업을 거부합니다.
NDB$MAX_DEL_WIN_INS()가 수행하는 insert 작업 처리는 다음 pseudocode로 나타낼 수 있습니다.
1if (source_new_column_value > replica_current_column_value) 2 apply_insert(); 3else 4 log_exception();
update 작업의 경우, source의 update된 timestamp column 값이 replica의 timestamp column 값과 다음과 같이 비교됩니다(역시 pseudocode 사용).
1if (source_new_column_value > replica_current_column_value) 2 apply_update(); 3else 4 log_exception();
delete 작업은 “delete always wins” 전략(즉 NDB$MAX_DELETE_WIN()과 동일)으로 처리됩니다. DELETE는 timestamp 값과 상관없이 항상 적용되며, 이는 다음 pseudocode로 나타낼 수 있습니다.
1if (operation.type == "delete") 2 apply_delete();
update와 delete 작업 간 conflict의 경우, 이 함수는 NDB$MAX_DELETE_WIN()과 동일하게 동작합니다.
NDB$EPOCH() 함수는 replica cluster에서, replica에서 발생한 변경과 관련하여 복제된 epoch가 적용되는 순서를 추적합니다. 이 상대적 순서는 replica에서 발생한 변경이 local에서 발생한 변경과 concurrent인지(따라서 잠재적으로 conflict인지) 여부를 결정하는 데 사용됩니다.
이후에 나오는 NDB$EPOCH()에 대한 설명 대부분은 NDB$EPOCH_TRANS()에도 적용됩니다. 예외 사항은 본문에서 별도로 설명합니다.
NDB$EPOCH()는 bidirectional replication 구성(“active-active” replication이라고도 함)에서 하나의 NDB Cluster에서 비대칭적으로 동작합니다. 여기서는 이 함수가 동작하는 cluster를 primary, 다른 cluster를 secondary라고 부릅니다. primary의 replica는 conflict 탐지 및 처리에 책임이 있으며, secondary의 replica는 어떤 conflict 탐지나 처리에도 관여하지 않습니다.
primary의 replica가 conflict를 탐지하면, 이를 보상하기 위해 자신의 binary log에 event를 삽입합니다. 이는 secondary NDB Cluster가 결국 primary와 다시 정렬되도록 보장하여 primary와 secondary가 서로 다른 상태가 되는 것을 방지합니다. 이러한 보상 및 재정렬 메커니즘은 primary NDB Cluster가 secondary와의 conflict에서 항상 승리해야 한다는(즉 conflict 발생 시 secondary의 변경이 아니라 primary의 변경이 항상 사용되어야 한다는) 요구 사항에 기반합니다. 이 “primary always wins” 규칙은 다음과 같은 의미를 가집니다.
primary에서 commit된 data 변경 작업은 conflict detection 및 resolution에 의해 되돌려지거나 rollback되지 않으며, 완전히 persistent합니다.
primary에서 읽은 data는 완전히 일관성을 가집니다. primary에서 commit된 모든 변경(local 또는 replica를 통한 변경)은 나중에 revert되지 않습니다.
secondary에서 data를 변경하는 작업은 나중에 primary가 conflict라고 판단하면 revert될 수 있습니다.
secondary에서 읽은 개별 row는 항상 self-consistent하며, 각 row는 항상 secondary에서 commit된 상태나 primary에서 commit된 상태 중 하나를 반영합니다.
secondary에서 읽은 row 집합은 특정 시점에서 반드시 일관성을 가지는 것은 아닐 수 있습니다. NDB$EPOCH_TRANS()의 경우 이는 일시적인 상태이며, NDB$EPOCH()의 경우 지속적인 상태가 될 수 있습니다.
conflict가 발생하지 않는 충분히 긴 기간이 있다고 가정하면, secondary NDB Cluster의 모든 data는 (결국) primary의 data와 일관성을 갖게 됩니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 conflict detection을 위해 사용자 schema 변경이나 애플리케이션 변경을 요구하지 않습니다. 다만 전체 시스템이 허용된 한도 내에서 동작하는지 확인하기 위해, 사용되는 schema 및 access 패턴에 대해 신중히 고려해야 합니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 모두 선택적 parameter를 하나 받을 수 있습니다. 이는 epoch의 하위 32bit를 표현하는 데 사용할 bit 수이며, 다음과 같이 계산된 값보다 작지 않게 설정해야 합니다.
1CEIL( LOG2( TimeBetweenGlobalCheckpoints / TimeBetweenEpochs ), 1)
이 configuration parameter의 기본값(각각 2000ms, 100ms)을 사용하면 이 값은 5bit이며, 따라서 기본값 6은 TimeBetweenGlobalCheckpoints, TimeBetweenEpochs 또는 둘 다에 대해 다른 값을 사용하지 않는 한 충분합니다. 값이 너무 작으면 false positive가 발생할 수 있고, 값이 너무 크면 데이터베이스에서 공간 낭비가 심해질 수 있습니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 이 절의 다른 부분에서 설명한 exceptions table schema 규칙에 따라 정의된 관련 exceptions table이 있는 경우, conflict하는 row에 대해 해당 exceptions table에 row를 삽입합니다(자세한 내용은 NDB$OLD() 참조). exceptions table은 이 table과 함께 사용될 data table을 생성하기 전에 반드시 생성해야 합니다.
이 절에서 논의된 다른 conflict detection 함수와 마찬가지로, NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 mysql.ndb_replication table에 관련 row를 추가함으로써 활성화됩니다(자세한 내용은 ndb_replication Table 참조). 이 시나리오에서 primary와 secondary NDB Cluster의 역할은 전적으로 mysql.ndb_replication table row에 의해 결정됩니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS()가 사용하는 conflict detection 알고리즘은 비대칭이므로, primary와 secondary replica의 server_id 값은 서로 달라야 합니다.
DELETE 작업 간 conflict만으로는 NDB$EPOCH() 또는 NDB$EPOCH_TRANS()에서 conflict를 trigger하기에 충분하지 않으며, epoch 내에서의 상대적 위치는 중요하지 않습니다.
Limitations on NDB$EPOCH()
NDB$EPOCH()를 사용하여 conflict detection을 수행할 때는 현재 다음과 같은 제한 사항이 적용됩니다.
conflict는 NDB Cluster epoch 경계 및 TimeBetweenEpochs (기본값: 100ms)에 비례하는 granularity를 사용하여 탐지됩니다. minimum conflict window는 두 cluster에서 동일 data에 동시에 update가 발생할 때 항상 conflict를 보고하는 최소 시간 길이입니다. 이 값은 항상 0이 아닌 길이를 가지며, 대략적으로 2 * (latency + queueing + TimeBetweenEpochs)에 비례합니다. 이는 TimeBetweenEpochs의 기본값을 사용하고, cluster 간 latency 및 queuing 지연을 무시한다고 가정하면, minimum conflict window 크기는 약 200ms임을 의미합니다. 예상되는 애플리케이션 “race” 패턴을 고려할 때 이 최소 window를 함께 고려해야 합니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS() 함수를 사용하는 table에는 추가 storage가 필요합니다. 함수에 전달되는 값에 따라 row당 1~32bit의 추가 공간이 필요합니다.
delete 작업 간 conflict는 primary와 secondary 간 divergence를 초래할 수 있습니다. 동일 row가 두 cluster에서 동시에 delete되는 경우 conflict를 탐지할 수는 있지만, row가 delete되었으므로 기록되지 않습니다. 이는 이후 realignment 작업이 전파되는 동안 추가 conflict가 탐지되지 않게 되어, divergence가 발생할 수 있음을 의미합니다.
delete는 외부에서 serialize되거나, 한 cluster로만 라우팅해야 합니다. 또는 별도의 row를 사용하여, 해당 delete 및 이후 insert를 transaction 단위로 함께 update하여, row delete를 넘어서 conflict를 추적할 수 있도록 해야 합니다. 이는 애플리케이션 변경이 필요할 수 있습니다.
NDB$EPOCH() 또는 NDB$EPOCH_TRANS()를 사용하여 conflict detection을 수행할 때는, 현재 “active-active” 구성의 두 개 NDB Cluster만 지원됩니다.
BLOB 또는 TEXT column을 가진 table은 현재 NDB$EPOCH() 또는 NDB$EPOCH_TRANS()와 함께 지원되지 않습니다.
NDB$EPOCH_TRANS()는 NDB$EPOCH() 함수를 확장한 것입니다. conflict는 “primary wins all” 규칙(자세한 내용은 NDB$EPOCH() 참조)에 따라 동일한 방식으로 탐지 및 처리되지만, conflict가 발생한 transaction에서 update된 다른 row도 conflict로 간주된다는 추가 조건이 있습니다. 다시 말해, NDB$EPOCH()가 secondary에서 개별 conflict row를 재정렬하는 반면, NDB$EPOCH_TRANS()는 conflict transaction 전체를 재정렬합니다.
또한 conflict transaction에 의존하는 것으로 탐지 가능한 모든 transaction 역시 conflict로 간주되며, 이러한 의존성은 secondary cluster의 binary log 내용을 통해 결정됩니다. binary log에는 data 변경 작업(insert, update, delete)만 포함되어 있으므로, transaction 간 의존성을 결정하는 데는 겹치는 data 변경만 사용됩니다.
NDB$EPOCH_TRANS()는 NDB$EPOCH()와 동일한 조건과 제한을 따르며, 추가로 secondary의 binary log에 모든 transaction ID가 기록되도록 --ndb-log-transaction-id를 ON으로 설정해야 합니다. 이는 row당 최대 13byte의 가변적인 overhead를 더합니다.
자세한 내용은 NDB$EPOCH()를 참조하십시오.
NDB$EPOCH2() 함수는 NDB$EPOCH()와 유사하지만, NDB$EPOCH2()는 bidirectional replication topology에서 delete-delete 처리 기능을 제공합니다. 이 시나리오에서, 두 source의 primary 및 secondary 역할은 각 source에서 ndb_conflict_role system variable을 적절한 값(보통 PRIMARY, SECONDARY 중 하나)으로 설정하여 지정합니다. 이렇게 하면, secondary에서 이루어진 변경은 primary에 의해 secondary로 다시 반영되며, 이후 secondary에서 조건에 따라 적용됩니다.
NDB$EPOCH2_TRANS()는 NDB$EPOCH2() 함수를 확장한 것입니다. conflict는 동일한 방식으로 탐지 및 처리되며, replication cluster에 primary 및 secondary 역할을 부여하지만, conflict가 발생한 transaction에서 update된 다른 row도 conflict로 간주됩니다. 즉, NDB$EPOCH2()가 secondary에서 개별 conflict row를 재정렬하는 반면, NDB$EPOCH_TRANS()는 conflict transaction 전체를 재정렬합니다.
NDB$EPOCH() 및 NDB$EPOCH_TRANS()는 primary에서, secondary에서 복제된 incoming row 변경이 local에서 commit된 변경과 concurrent인지 여부를 결정하기 위해, row당 마지막으로 변경된 epoch 단위로 지정된 metadata를 사용합니다. concurrent 변경은 conflict로 간주되며, 이어서 exceptions table update 및 secondary 재정렬이 발생합니다. 그러나 row가 primary에서 delete되어 더 이상 last-modified epoch가 존재하지 않는 경우 이후 복제된 작업이 conflict인지 판단할 수 없게 되어, conflict delete 작업이 탐지되지 않는 문제가 발생합니다. 이는 예를 들어 한 cluster에서의 delete가, 다른 cluster에서의 delete와 insert와 concurrent한 경우 divergence를 초래할 수 있으며, 이런 이유로 NDB$EPOCH() 및 NDB$EPOCH_TRANS()를 사용할 때 delete 작업은 한 cluster에만 라우팅할 수 있습니다.
NDB$EPOCH2()는 위에서 설명한 문제를 피하기 위해 PRIMARY에서 delete된 row에 대한 정보를 저장하지 않고도 delete-delete conflict를 무시하고, 이로 인한 잠재적인 divergence를 방지합니다. 이는 secondary에서 성공적으로 적용되고 복제된 모든 작업을 secondary로 다시 반영(reflect)함으로써 달성됩니다. 이렇게 secondary로 돌아온 작업은, primary에서 발생한 작업으로 인해 delete된 secondary의 작업을 재적용하는 데 사용할 수 있습니다.
NDB$EPOCH2()를 사용할 때는, secondary가 primary에서 온 delete를 적용하여 새로운 row를 제거한 뒤, 반영된 작업에 의해 다시 복구한다는 점을 명심해야 합니다. 이론적으로는 이후 secondary에서의 insert 또는 update가 primary의 delete와 conflict하지만, 여기서는 cluster 간 divergence를 방지하기 위해 이를 무시하고 secondary가 “승리”하도록 허용합니다. 즉, delete 이후 primary는 conflict를 탐지하지 않고, 대신 secondary의 이후 변경을 즉시 수용합니다. 이 때문에 secondary의 state는 최종(안정) 상태로 수렴하는 과정에서 여러 이전 commit 상태를 반복해서 거칠 수 있으며, 이들 중 일부는 외부에 보일 수 있습니다.
또한 secondary에서의 모든 작업을 primary로 되돌려 반영하면, primary의 binary log 크기뿐 아니라 대역폭, CPU 사용량, disk I/O 요구도 증가한다는 점을 유의해야 합니다.
secondary에서 반영된 작업의 적용 여부는 secondary의 대상 row state에 따라 달라집니다. 반영된 변경이 secondary에서 적용되었는지 여부는 Ndb_conflict_reflected_op_prepare_count 및 Ndb_conflict_reflected_op_discard_count status variable이나, ndb_replication_applier_status Performance Schema table의 CONFLICT_REFLECTED_OP_PREPARE_COUNT, CONFLICT_REFLECTED_OP_DISCARD_COUNT column을 확인하여 추적할 수 있습니다. 적용된 변경 수는 단순히 이 두 값의 차이이며(항상 Ndb_conflict_reflected_op_prepare_count가 Ndb_conflict_reflected_op_discard_count보다 크거나 같음에 유의하십시오), 이 차이가 적용된 변경 수입니다.
event는 다음 두 조건이 모두 true인 경우에만 적용됩니다.
row의 존재 여부, 즉 row가 존재하는지 여부가 event type과 일치해야 합니다. delete 및 update 작업의 경우 row가 이미 존재해야 하며, insert 작업의 경우 row가 존재하지 않아야 합니다.
row가 마지막으로 primary에 의해 변경된 상태여야 합니다. 이 변경은 반영된 작업의 실행을 통해 수행되었을 수도 있습니다.
이 두 조건을 모두 만족하지 못하면, secondary에서 반영된 작업은 폐기(discard)됩니다.
NDB$OLD() conflict resolution 함수를 사용하려면, 이 유형의 conflict resolution이 사용될 각 NDB table에 대응하는 exceptions table을 생성해야 합니다. NDB$EPOCH() 또는 NDB$EPOCH_TRANS()를 사용할 때도 마찬가지입니다. 이 table의 이름은 conflict resolution을 적용할 table 이름에 문자열 $EX를 덧붙인 것입니다(예를 들어, 원래 table 이름이 mytable이면, 대응하는 exceptions table 이름은 mytable$EX가 되어야 합니다). exceptions table을 생성하는 syntax는 다음과 같습니다.
1CREATE TABLE original_table$EX ( 2 [NDB$]server_id INT UNSIGNED, 3 [NDB$]source_server_id INT UNSIGNED, 4 [NDB$]source_epoch BIGINT UNSIGNED, 5 [NDB$]count INT UNSIGNED, 6 7 [NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',\ 8 'REFRESH_ROW', 'READ_ROW') NOT NULL,] 9 [NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',\ 10 'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,] 11 [NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL,] 12 13 original_table_pk_columns, 14 15 [orig_table_column|orig_table_column$OLD|orig_table_column$NEW,] 16 17 [additional_columns,] 18 19 PRIMARY KEY([NDB$]server_id, [NDB$]source_server_id, [NDB$]source_epoch, [NDB$]count) 20) ENGINE=NDB;
처음 네 개의 column은 필수입니다. 처음 네 개의 column과 원래 table의 primary key column에 대응하는 column의 이름은 엄격히 정해져 있지는 않지만, 명확성과 일관성을 위해 server_id, source_server_id, source_epoch, count column에는 위의 이름을 사용하고, 원래 table의 primary key column에 대응하는 column에는 원래 table에서와 동일한 이름을 사용할 것을 권장합니다.
exceptions table이 이 절의 뒷부분에서 설명하는 optional column NDB$OP_TYPE, NDB$CFT_CAUSE, NDB$ORIG_TRANSID 중 하나 이상을 사용하는 경우, 필수 column 각각도 NDB$ prefix를 사용하여 이름을 지정해야 합니다. optional column을 정의하지 않더라도 원하는 경우 필수 column 이름에 NDB$ prefix를 사용할 수 있지만, 이 경우 네 개의 필수 column 모두에 prefix를 사용해야 합니다.
이들 column에 이어, 원래 table의 primary key를 구성하는 column들을, 원래 table의 primary key 정의에 사용된 순서대로 복사해야 합니다. 원래 table의 primary key column을 복제한 column의 data type은 원래 column의 type과 같거나 더 커야 합니다. primary key column의 일부(subset)만 사용할 수도 있습니다.
exceptions table은 반드시 NDB storage engine을 사용해야 합니다(NDB$OLD()와 exceptions table을 함께 사용하는 예는 이 절의 뒷부분에 나옵니다).
추가 column은 복제된 primary key column 이후에 선택적으로 정의할 수 있지만, 그 이전에는 정의할 수 없습니다. 이러한 추가 column은 NOT NULL일 수 없습니다. NDB Cluster는 NDB$OP_TYPE, NDB$CFT_CAUSE, NDB$ORIG_TRANSID라는 세 개의 추가 predefined optional column을 지원하며, 이들은 다음 단락들에서 설명됩니다.
NDB$OP_TYPE: 이 column은 conflict를 유발한 작업 유형을 얻는 데 사용할 수 있습니다. 이 column을 사용하는 경우 다음과 같이 정의해야 합니다.
1NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW', 2 'REFRESH_ROW', 'READ_ROW') NOT NULL
WRITE_ROW, UPDATE_ROW, DELETE_ROW 작업 유형은 사용자가 시작한 작업을 나타냅니다. REFRESH_ROW 작업은 conflict resolution에 의해 생성되어, conflict를 탐지한 cluster에서 originating cluster로 전송되는 보상 transaction에 의해 생성된 작업입니다. READ_ROW 작업은 exclusive row lock으로 정의된 사용자 시작 read tracking 작업입니다.
NDB$CFT_CAUSE: 선택적 column NDB$CFT_CAUSE를 정의하여, 등록된 conflict의 원인을 제공할 수 있습니다. 이 column을 사용하는 경우, 다음과 같이 정의해야 합니다.
1NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS', 2 'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL
ROW_DOES_NOT_EXIST는 UPDATE_ROW 및 WRITE_ROW 작업에 대해 원인으로 보고될 수 있습니다. ROW_ALREADY_EXISTS는 WRITE_ROW event에 대해 보고될 수 있습니다. DATA_IN_CONFLICT는 row 기반 conflict 함수가 conflict를 탐지할 때 보고되며, TRANS_IN_CONFLICT는 transaction 기반 conflict 함수가 전체 transaction에 속하는 모든 작업을 거부할 때 보고됩니다.
NDB$ORIG_TRANSID: NDB$ORIG_TRANSID column은 사용되는 경우 originating transaction의 ID를 포함합니다. 이 column은 다음과 같이 정의해야 합니다.
1NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL
NDB$ORIG_TRANSID는 NDB에서 생성하는 64bit 값입니다. 이 값은 동일한 conflict transaction에 속하는 여러 exceptions table row를, 동일 exceptions table 내에서나 서로 다른 exceptions table 간에 상호 연관(correlate)시키는 데 사용할 수 있습니다.
원래 table의 primary key에 속하지 않는 추가 reference column은 colname$OLD 또는 colname$NEW로 이름을 지정할 수 있습니다. colname$OLD는 update 및 delete 작업(즉 DELETE_ROW event를 포함하는 작업)의 old 값을 참조합니다. colname$NEW는 insert 및 update 작업(즉 WRITE_ROW event, UPDATE_ROW event, 또는 두 event type 모두를 사용하는 작업)의 new 값을 참조하는 데 사용할 수 있습니다. conflict하는 작업이 primary key가 아닌 특정 reference column에 대한 값을 제공하지 않는 경우, exceptions table row에는 해당 column에 NULL 또는 그 column에 대해 정의된 default 값이 들어갑니다.
주의
mysql.ndb_replication table은 data table이 replication 대상이 될 때 읽히므로, replication할 table에 대응하는 row는 replication할 table을 생성하기 전에 mysql.ndb_replication에 삽입되어 있어야 합니다.
여러 status variable을 사용하여 conflict detection을 모니터링할 수 있습니다. 이 replica가 마지막으로 재시작된 이후 NDB$EPOCH()에 의해 conflict로 판정된 row 수는 Ndb_conflict_fn_epoch system status variable의 현재 값이나, Performance Schema ndb_replication_applier_status table의 CONFLICT_FN_EPOCH column을 확인하여 알 수 있습니다.
Ndb_conflict_fn_epoch_trans는 NDB$EPOCH_TRANS()에 의해 직접 conflict로 판정된 row 수를 제공합니다. Ndb_conflict_fn_epoch2 및 Ndb_conflict_fn_epoch2_trans는 각각 NDB$EPOCH2(), NDB$EPOCH2_TRANS()에 의해 conflict로 판정된 row 수를 제공합니다. 다른 conflict row와 동일 transaction에 속하거나 그러한 transaction에 의존하기 때문에 영향을 받은 row를 포함하여, 실제로 realign된 row 수는 Ndb_conflict_trans_row_reject_count에 의해 주어집니다.
또 다른 server status variable인 Ndb_conflict_fn_max는 해당 mysqld가 시작된 이후 현재 SQL node에서 “greatest timestamp wins” conflict resolution 때문에 row가 적용되지 않은 횟수를 제공합니다. Ndb_conflict_fn_max_del_win은 NDB$MAX_DELETE_WIN() 결과에 기반한 conflict resolution이 적용된 횟수를 제공합니다.
Ndb_conflict_fn_max_ins는 write 작업에 대해 “greater timestamp wins” 처리가 적용된 횟수(NDB$MAX_INS() 사용)를 추적합니다. write에 대해 “same timestamp wins” 처리가 적용된 횟수(NDB$MAX_DEL_WIN_INS()가 구현)는 Ndb_conflict_fn_max_del_win_ins status variable에 의해 제공됩니다.
특정 mysqld가 마지막으로 재시작된 이후 “same timestamp wins” conflict resolution의 결과로 row가 적용되지 않은 횟수는 global status variable Ndb_conflict_fn_old에 의해 주어집니다. Ndb_conflict_fn_old는 증가할 뿐 아니라, 사용되지 않은 row의 primary key가 이 절의 다른 부분에서 설명하는 것처럼 exceptions table에 삽입됩니다.
앞 단락에서 언급된 각 status variable은 Performance Schema ndb_replication_applier_status table에서 해당 column을 가지고 있습니다. 자세한 내용은 이 table의 설명을 참조하십시오. 또한 Section 25.4.3.9.3, “NDB Cluster Status Variables”도 참조하십시오.
다음 예제에서는 Section 25.7.5, “Preparing the NDB Cluster for Replication” 및 Section 25.7.6, “Starting NDB Cluster Replication (Single Replication Channel)”에서 설명한 대로, 이미 동작하는 NDB Cluster replication 구성이 있다고 가정합니다.
NDB$MAX() example.
table test.t1에서 column mycol을 “timestamp”로 사용하여 “greatest timestamp wins” conflict resolution을 활성화하려 한다고 가정합니다. 다음 단계로 이를 수행할 수 있습니다.
다음과 같이 source의 mysqld를 --ndb-log-update-as-write=OFF로 시작했는지 확인합니다.
source에서 다음 INSERT 문을 실행합니다.
1INSERT INTO mysql.ndb_replication 2 VALUES ('test', 't1', 0, NULL, 'NDB$MAX(mycol)');
참고
ndb_replication table이 아직 존재하지 않는 경우, 이를 생성해야 합니다. 자세한 내용은 ndb_replication Table을 참조하십시오.
server_id column에 0을 삽입하면 이 table에 접근하는 모든 SQL node가 conflict resolution을 사용해야 함을 나타냅니다. 특정 mysqld에서만 conflict resolution을 사용하려면 실제 server ID를 사용하십시오.
binlog_type column에 NULL을 삽입하는 것은 0 (NBT_DEFAULT)을 삽입하는 것과 동일한 효과를 가지며, server default가 사용됩니다.
test.t1 table을 생성합니다.1CREATE TABLE test.t1 ( 2 columns 3 mycol INT UNSIGNED, 4 columns 5) ENGINE=NDB;
이제 이 table에 대해 update가 수행되면 conflict resolution이 적용되며, mycol 값이 가장 큰 row 버전이 replica에 기록됩니다.
참고
NBT_UPDATED_ONLY_USE_UPDATE(6)와 같은 다른 binlog_type 옵션은 command-line 옵션이 아니라 ndb_replication table을 사용하여 source에서 logging을 제어하는 데 사용해야 합니다.
NDB$OLD() example.
다음과 같이 정의된 NDB table이 복제되고 있으며, 이 table에 대한 update에 “same timestamp wins” conflict resolution을 활성화하려 한다고 가정합니다.
1CREATE TABLE test.t2 ( 2 a INT UNSIGNED NOT NULL, 3 b CHAR(25) NOT NULL, 4 columns, 5 mycol INT UNSIGNED NOT NULL, 6 columns, 7 PRIMARY KEY pk (a, b) 8) ENGINE=NDB;
다음 단계는 표시된 순서대로 수행해야 합니다.
test.t2를 생성하기 전에, 다음과 같이 mysql.ndb_replication table에 row를 삽입해야 합니다.1INSERT INTO mysql.ndb_replication 2 VALUES ('test', 't2', 0, 0, 'NDB$OLD(mycol)');
binlog_type column에 가능한 값은 이 절의 앞부분에 나와 있으며, 여기서는 server 기본 logging 동작을 지정하기 위해 0을 사용합니다. conflict_fn column에는 'NDB$OLD(mycol)' 값을 삽입해야 합니다.
test.t2에 대한 적절한 exceptions table을 생성합니다. 다음 table 생성 문은 필수 column을 모두 포함하며, 추가 column은 이 column들 다음이자 table primary key 정의 이전에 선언해야 합니다.1CREATE TABLE test.t2$EX ( 2 server_id INT UNSIGNED, 3 source_server_id INT UNSIGNED, 4 source_epoch BIGINT UNSIGNED, 5 count INT UNSIGNED, 6 a INT UNSIGNED NOT NULL, 7 b CHAR(25) NOT NULL, 8 9 [additional_columns,] 10 11 PRIMARY KEY(server_id, source_server_id, source_epoch, count) 12) ENGINE=NDB;
원하는 경우 특정 conflict에 대해 작업 유형, 원인, originating transaction ID에 대한 추가 column을 포함할 수 있습니다. 또한 원래 table의 모든 primary key column에 대응하는 column을 제공할 필요는 없습니다. 즉 예외 table을 다음과 같이 생성할 수도 있습니다.
1CREATE TABLE test.t2$EX ( 2 NDB$server_id INT UNSIGNED, 3 NDB$source_server_id INT UNSIGNED, 4 NDB$source_epoch BIGINT UNSIGNED, 5 NDB$count INT UNSIGNED, 6 a INT UNSIGNED NOT NULL, 7 8 NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW', 9 'REFRESH_ROW', 'READ_ROW') NOT NULL, 10 NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS', 11 'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL, 12 NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL, 13 14 [additional_columns,] 15 16 PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count) 17) ENGINE=NDB;
참고
table 정의에 NDB$OP_TYPE, NDB$CFT_CAUSE, NDB$ORIG_TRANSID column 중 하나 이상을 포함했기 때문에, 네 개의 필수 column에는 NDB$ prefix를 사용해야 합니다.
test.t2 table을 생성합니다.NDB$OLD()를 사용하여 conflict resolution을 수행하려는 각 table에 대해 위의 단계가 필요합니다. 각 table에는 mysql.ndb_replication에 해당 row가 있어야 하며, 복제되는 table과 동일한 database에 exceptions table이 있어야 합니다.
Read conflict detection and resolution.
NDB Cluster는 read 작업 추적도 지원하며, 이를 통해 순환 replication 구성에서 한 cluster에서 특정 row에 대한 read와 다른 cluster에서 동일 row에 대한 update 또는 delete 간 conflict를 관리할 수 있습니다. 이 예제에서는 employee 및 department table을 사용하여, source cluster(이후 cluster _A_로 지칭)에서 한 employee를 한 부서에서 다른 부서로 이동하는 동안 replica cluster(이후 cluster B)가 employee의 이전 부서에 대한 employee 수를 interleaved transaction에서 update하는 시나리오를 모델링합니다.
data table은 다음 SQL 문을 사용하여 생성되었습니다.
1# Employee table 2CREATE TABLE employee ( 3 id INT PRIMARY KEY, 4 name VARCHAR(2000), 5 dept INT NOT NULL 6) ENGINE=NDB; 7 8# Department table 9CREATE TABLE department ( 10 id INT PRIMARY KEY, 11 name VARCHAR(2000), 12 members INT 13) ENGINE=NDB;
두 table의 내용은, 다음 SELECT 문 일부 출력에 표시된 row를 포함합니다.
1mysql> SELECT id, name, dept FROM employee; 2+---------------+------+ 3| id | name | dept | 4+------+--------+------+ 5... 6| 998 | Mike | 3 | 7| 999 | Joe | 3 | 8| 1000 | Mary | 3 | 9... 10+------+--------+------+ 11 12mysql> SELECT id, name, members FROM department; 13+-----+-------------+---------+ 14| id | name | members | 15+-----+-------------+---------+ 16... 17| 3 | Old project | 24 | 18... 19+-----+-------------+---------+
이미 네 개의 필수 column(그리고 이들 column이 이 table의 primary key로 사용됨), 작업 유형 및 원인을 위한 optional column, original table의 primary key column을 포함하는 exceptions table을 사용 중이라고 가정하며, 이는 다음 SQL 문으로 생성되었습니다.
1CREATE TABLE employee$EX ( 2 NDB$server_id INT UNSIGNED, 3 NDB$source_server_id INT UNSIGNED, 4 NDB$source_epoch BIGINT UNSIGNED, 5 NDB$count INT UNSIGNED, 6 7 NDB$OP_TYPE ENUM( 'WRITE_ROW','UPDATE_ROW', 'DELETE_ROW', 8 'REFRESH_ROW','READ_ROW') NOT NULL, 9 NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST', 10 'ROW_ALREADY_EXISTS', 11 'DATA_IN_CONFLICT', 12 'TRANS_IN_CONFLICT') NOT NULL, 13 14 id INT NOT NULL, 15 16 PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count) 17) ENGINE=NDB;
이제 두 cluster에서 동시에 두 개의 transaction이 발생한다고 가정합니다. cluster _A_에서는 다음 SQL 문을 사용하여 새 부서를 생성하고, 그 다음 employee 번호 999를 해당 부서로 이동합니다.
1BEGIN; 2 INSERT INTO department VALUES (4, "New project", 1); 3 UPDATE employee SET dept = 4 WHERE id = 999; 4COMMIT;
동시에 cluster _B_에서는 다음과 같이 또 다른 transaction이 employee에서 read를 수행합니다.
1BEGIN; 2 SELECT name FROM employee WHERE id = 999; 3 UPDATE department SET members = members - 1 WHERE id = 3; 4commit;
이 conflict transaction들은 read(SELECT)와 update 작업 간 conflict이므로, 일반적으로 conflict resolution 메커니즘에 의해 탐지되지 않습니다. replica cluster에서 SET ndb_log_exclusive_reads= 1을 실행하면 이 문제를 피할 수 있습니다. 이런 방식으로 exclusive read를 활성화하면, source에서 읽힌 row가 replica cluster에서 conflict resolution 대상으로 표시됩니다. 이러한 transaction이 log되기 전에 이와 같이 exclusive read를 활성화하면, cluster _B_에서의 read는 추적되어 resolution을 위해 cluster _A_로 전송되고, 그 후 employee row에서 conflict가 탐지되며 cluster _B_의 transaction이 abort됩니다.
이 conflict는 exceptions table(cluster A)에 READ_ROW 작업으로 등록됩니다(작업 유형에 대한 설명은 Conflict Resolution Exceptions Table 참조). 예는 다음과 같습니다.
1mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX; 2+-------+-------------+-------------------+ 3| id | NDB$OP_TYPE | NDB$CFT_CAUSE | 4+-------+-------------+-------------------+ 5... 6| 999 | READ_ROW | TRANS_IN_CONFLICT | 7+-------+-------------+-------------------+
read 작업에서 발견된 기존 row는 모두 flag가 설정됩니다. 이는 동일한 conflict에서 발생한 여러 row가 예외 table에 log될 수 있음을 의미하며, 이는 cluster _A_에서의 update와 cluster _B_에서의 동일 table에 대한 여러 row read 간 conflict가 동시에 transaction으로 발생하는 경우를 살펴보면 알 수 있습니다. cluster _A_에서 실행되는 transaction은 다음과 같습니다.
1BEGIN; 2 INSERT INTO department VALUES (4, "New project", 0); 3 UPDATE employee SET dept = 4 WHERE dept = 3; 4 SELECT COUNT(*) INTO @count FROM employee WHERE dept = 4; 5 UPDATE department SET members = @count WHERE id = 4; 6COMMIT;
동시에 cluster _B_에서는 다음 문을 포함하는 transaction이 실행됩니다.
1SET ndb_log_exclusive_reads = 1; # Must be set if not already enabled 2... 3BEGIN; 4 SELECT COUNT(*) INTO @count FROM employee WHERE dept = 3 FOR UPDATE; 5 UPDATE department SET members = @count WHERE id = 3; 6COMMIT;
이 경우 두 번째 transaction의 SELECT에서 WHERE 조건과 일치하는 세 개의 row가 모두 읽히며, 따라서 예외 table에서 다음과 같이 flag가 설정됩니다.
1mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX; 2+-------+-------------+-------------------+ 3| id | NDB$OP_TYPE | NDB$CFT_CAUSE | 4+-------+-------------+-------------------+ 5... 6| 998 | READ_ROW | TRANS_IN_CONFLICT | 7| 999 | READ_ROW | TRANS_IN_CONFLICT | 8| 1000 | READ_ROW | TRANS_IN_CONFLICT | 9... 10+-------+-------------+-------------------+
read tracking은 기존 row만을 기준으로 수행됩니다. 특정 조건을 기준으로 한 read는 찾은 row의 conflict만 추적하며, interleaved transaction에서 insert되는 row의 conflict는 추적하지 않습니다. 이는 단일 NDB Cluster instance에서 exclusive row locking이 수행되는 방식과 유사합니다.
Insert conflict detection and resolution example.
다음 예는 insert conflict detection 함수의 사용을 보여줍니다. database test의 두 table t1, t2를 복제하고 있으며, t1에는 NDB$MAX_INS()를, t2에는 NDB$MAX_DEL_WIN_INS()를 사용하여 insert conflict detection을 사용하려 한다고 가정합니다. 두 data table은 setup 과정의 나중 단계까지 생성되지 않습니다.
insert conflict resolution 설정은 앞 예에서 본 다른 conflict detection 및 resolution 알고리즘 설정과 유사합니다. binary logging 및 conflict resolution 설정에 사용되는 mysql.ndb_replication table이 아직 존재하지 않는 경우, 먼저 다음과 같이 생성해야 합니다.
1CREATE TABLE mysql.ndb_replication ( 2 db VARBINARY(63), 3 table_name VARBINARY(63), 4 server_id INT UNSIGNED, 5 binlog_type INT UNSIGNED, 6 conflict_fn VARBINARY(128), 7 PRIMARY KEY USING HASH (db, table_name, server_id) 8) ENGINE=NDB 9PARTITION BY KEY(db,table_name);
ndb_replication table은 table 단위로 동작합니다. 즉 설정할 각 table에 대해, table 정보, binlog_type 값, 사용할 conflict resolution 함수, timestamp column(X) 이름을 포함하는 row를 다음과 같이 삽입해야 합니다.
1INSERT INTO mysql.ndb_replication VALUES ("test", "t1", 0, 7, "NDB$MAX_INS(X)"); 2INSERT INTO mysql.ndb_replication VALUES ("test", "t2", 0, 7, "NDB$MAX_DEL_WIN_INS(X)");
여기서 우리는 binlog_type을 NBT_FULL_USE_UPDATE(7)로 설정했습니다. 이는 항상 전체 row를 log한다는 의미입니다. 가능한 다른 값은 ndb_replication Table을 참조하십시오.
또한 conflict resolution을 사용할 각 NDB table에 대응하는 exceptions table을 생성할 수 있습니다. exceptions table은 특정 table에 대해 conflict resolution 함수에 의해 거부된 모든 row를 기록합니다. replication conflict detection용 exceptions table t1$EX, t2$EX는 다음 두 SQL 문을 사용하여 생성할 수 있습니다.
1CREATE TABLE `t1$EX` ( 2 NDB$server_id INT UNSIGNED, 3 NDB$source_server_id INT UNSIGNED, 4 NDB$source_epoch BIGINT UNSIGNED, 5 NDB$count INT UNSIGNED, 6 NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW', 7 'REFRESH_ROW', 'READ_ROW') NOT NULL, 8 NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS', 9 'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL, 10 a INT NOT NULL, 11 PRIMARY KEY(NDB$server_id, NDB$source_server_id, 12 NDB$source_epoch, NDB$count) 13) ENGINE=NDB; 14 15CREATE TABLE `t2$EX` ( 16 NDB$server_id INT UNSIGNED, 17 NDB$source_server_id INT UNSIGNED, 18 NDB$source_epoch BIGINT UNSIGNED, 19 NDB$count INT UNSIGNED, 20 NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW', 21 'REFRESH_ROW', 'READ_ROW') NOT NULL, 22 NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS', 23 'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL, 24 a INT NOT NULL, 25 PRIMARY KEY(NDB$server_id, NDB$source_server_id, 26 NDB$source_epoch, NDB$count) 27) ENGINE=NDB;
마지막으로, 방금 생성한 exception table 이후에, 다음 두 SQL 문을 사용하여 replication 및 conflict resolution control 대상인 data table을 생성할 수 있습니다.
1CREATE TABLE t1 ( 2 a INT PRIMARY KEY, 3 b VARCHAR(32), 4 X INT UNSIGNED 5) ENGINE=NDB; 6 7CREATE TABLE t2 ( 8 a INT PRIMARY KEY, 9 b VARCHAR(32), 10 X INT UNSIGNED 11) ENGINE=NDB;
각 table에서 X column은 timestamp column으로 사용됩니다.
source에서 생성된 이후 t1, t2는 복제되며, source와 replica 모두에 존재한다고 가정할 수 있습니다. 예제의 나머지 부분에서는 source에 연결된 mysql client를 mysqlS>, replica에서 실행되는 mysql client를 mysqlR>로 표시합니다.
먼저 source의 table에 각각 하나의 row를 다음과 같이 삽입합니다.
1mysqlS> INSERT INTO t1 VALUES (1, 'Initial X=1', 1); 2Query OK, 1 row affected (0.01 sec) 3 4mysqlS> INSERT INTO t2 VALUES (1, 'Initial X=1', 1); 5Query OK, 1 row affected (0.01 sec)
이 두 row를 삽입하기 전에 replica의 table에는 어떤 row도 없었으므로, 이 두 row는 conflict 없이 복제되었음을 확신할 수 있습니다. 이는 replica에서 다음과 같이 table을 조회하여 확인할 수 있습니다.
1mysqlR> TABLE t1 ORDER BY a; 2+---+-------------+------+ 3| a | b | X | 4+---+-------------+------+ 5| 1 | Initial X=1 | 1 | 6+---+-------------+------+ 71 row in set (0.00 sec) 8 9mysqlR> TABLE t2 ORDER BY a; 10+---+-------------+------+ 11| a | b | X | 12+---+-------------+------+ 13| 1 | Initial X=1 | 1 | 14+---+-------------+------+ 151 row in set (0.00 sec)
다음으로 replica에서 다음과 같이 새 row를 table에 삽입합니다.
1mysqlR> INSERT INTO t1 VALUES (2, 'Replica X=2', 2); 2Query OK, 1 row affected (0.01 sec) 3 4mysqlR> INSERT INTO t2 VALUES (2, 'Replica X=2', 2); 5Query OK, 1 row affected (0.01 sec)
이제 source에서 더 큰 timestamp(X) 값을 가진 conflict row를 다음과 같이 table에 삽입합니다.
1mysqlS> INSERT INTO t1 VALUES (2, 'Replica X=20', 20); 2Query OK, 1 row affected (0.01 sec) 3 4mysqlS> INSERT INTO t2 VALUES (2, 'Replica X=20', 20); 5Query OK, 1 row affected (0.01 sec)
이제 replica의 두 table을 다시 조회하여 결과를 관찰합니다.
1mysqlR> TABLE t1 ORDER BY a; 2+---+-------------+-------+ 3| a | b | X | 4+---+-------------+-------+ 5| 1 | Initial X=1 | 1 | 6+---+-------------+-------+ 7| 2 | Source X=20 | 20 | 8+---+-------------+-------+ 92 rows in set (0.00 sec) 10 11mysqlR> TABLE t2 ORDER BY a; 12+---+-------------+-------+ 13| a | b | X | 14+---+-------------+-------+ 15| 1 | Initial X=1 | 1 | 16+---+-------------+-------+ 17| 1 | Source X=20 | 20 | 18+---+-------------+-------+ 192 rows in set (0.00 sec)
source에서 삽입된 row는 replica의 conflict row보다 더 큰 timestamp를 가지므로, conflict row를 대체했습니다. 다음으로 replica의 t1, t2에서 기존 row와 conflict하지 않는 두 개의 새 row를 삽입합니다.
1mysqlR> INSERT INTO t1 VALUES (3, 'Replica X=30', 30); 2Query OK, 1 row affected (0.01 sec) 3 4mysqlR> INSERT INTO t2 VALUES (3, 'Replica X=30', 30); 5Query OK, 1 row affected (0.01 sec)
source에서 동일한 primary key 값(3)을 가진 row를 다시 삽입하면 이전과 마찬가지로 conflict가 발생하지만, 이번에는 replica의 conflict row에 있는 timestamp 값보다 작은 timestamp 값을 사용합니다.
1mysqlS> INSERT INTO t1 VALUES (3, 'Source X=3', 3); 2Query OK, 1 row affected (0.01 sec) 3 4mysqlS> INSERT INTO t2 VALUES (3, 'Source X=3', 3); 5Query OK, 1 row affected (0.01 sec)
replica의 mysql client에서 table을 조회해 보면, source에서의 두 insert가 replica에 의해 거부되었고, 이전에 replica에 삽입된 row가 덮어쓰여지지 않았음을 알 수 있습니다.
1mysqlR> TABLE t1 ORDER BY a; 2+---+--------------+-------+ 3| a | b | X | 4+---+--------------+-------+ 5| 1 | Initial X=1 | 1 | 6+---+--------------+-------+ 7| 2 | Source X=20 | 20 | 8+---+--------------+-------+ 9| 3 | Replica X=30 | 30 | 10+---+--------------+-------+ 113 rows in set (0.00 sec) 12 13mysqlR> TABLE t2 ORDER BY a; 14+---+--------------+-------+ 15| a | b | X | 16+---+--------------+-------+ 17| 1 | Initial X=1 | 1 | 18+---+--------------+-------+ 19| 2 | Source X=20 | 20 | 20+---+--------------+-------+ 21| 3 | Replica X=30 | 30 | 22+---+--------------+-------+ 233 rows in set (0.00 sec)
exception table을 조회하면, replica에서 거부된 row에 대한 정보를 다음과 같이 확인할 수 있습니다.
1mysqlR> SELECT NDB$server_id, NDB$source_server_id, NDB$count, 2 > NDB$OP_TYPE, NDB$CFT_CAUSE, a 3 > FROM t1$EX 4 > ORDER BY NDB$count\G 5*************************** 1. row *************************** 6NDB$server_id : 2 7NDB$source_server_id: 1 8NDB$count : 1 9NDB$OP_TYPE : WRITE_ROW 10NDB$CFT_CAUSE : DATA_IN_CONFLICT 11a : 3 121 row in set (0.00 sec) 13 14mysqlR> SELECT NDB$server_id, NDB$source_server_id, NDB$count, 15 > NDB$OP_TYPE, NDB$CFT_CAUSE, a 16 > FROM t2$EX 17 > ORDER BY NDB$count\G 18*************************** 1. row *************************** 19NDB$server_id : 2 20NDB$source_server_id: 1 21NDB$count : 1 22NDB$OP_TYPE : WRITE_ROW 23NDB$CFT_CAUSE : DATA_IN_CONFLICT 24a : 3 251 row in set (0.00 sec)
앞에서 보았듯이, replica에서 거부된 row는 source에서 삽입된 row 중 replica의 conflict row보다 작은 timestamp 값을 가진 row뿐이며, 다른 row는 replica에서 거부되지 않았습니다.
25.7.11 NDB Cluster Replication Using the Multithreaded Applier
25.8 NDB Cluster Release Notes