本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
了解主動-主動衝突
當您在主動-主動模式下使用 pgactive 時,從多個節點寫入相同的資料表可能會產生資料衝突。雖然某些叢集系統使用分散式鎖定來防止並行存取,但 pgactive 採取了更樂觀的方法,更適合地理分佈的應用程式。
有些資料庫叢集系統會使用分散式鎖定來防止並行資料存取。雖然此方法可在伺服器接近時運作,但它不支援地理分佈的應用程式,因為它需要極低的延遲才能獲得良好的效能。pgactive 擴充功能使用樂觀的方法,而不是使用分散式鎖定 (樂觀方法)。這表示:
-
協助您盡可能避免衝突。
-
允許發生特定類型的衝突。
-
在發生衝突時提供衝突解決方案。
此方法可讓您在建置分散式應用程式時更具彈性。
衝突的發生方式
如果所有涉及的交易在同一個節點上同時發生,則無法發生的事件序列會產生節點間衝突。由於節點只會在交易遞交之後交換變更,因此每個交易對其遞交的節點個別有效,但如果在同時完成其他工作的另一個節點上執行,則無效。由於 pgactive 套用基本上在其他節點上重播交易,如果套用的交易與接收節點上遞交的交易之間發生衝突,則重播操作可能會失敗。
當在單一節點上執行的所有交易都PostgreSQL具有防止衝突的交易間通訊機制時,就無法發生大多數衝突,包括:
-
UNIQUE 索引
-
SEQUENCEs
-
資料列和關聯鎖定
-
SERIALIZABLE 相依性追蹤
所有這些機制都是在交易之間進行通訊的方式,以防止不必要的並行問題
pgactive 可實現低延遲並妥善處理網路分割區,因為它不使用分散式交易管理員或鎖定管理員。不過,這表示不同節點上的交易彼此完全隔離執行。雖然隔離通常會改善資料庫一致性,但在這種情況下,您需要減少隔離以防止衝突。
衝突類型
可能發生的衝突包括:
主題
主要金鑰或 UNIQUE 衝突
當多個操作嘗試以單一節點上不可能的方式修改相同的資料列索引鍵時,就會發生資料列衝突。這些衝突代表最常見的資料衝突類型。
pgactive 透過last-update-wins處理或您的自訂衝突處理常式來解決偵測到的衝突。
資料列衝突包括:
-
INSERT 與 INSERT
-
INSERT 與 UPDATE
-
更新與刪除
-
INSERT 與 DELETE
-
DELETE 與 DELETE
-
INSERT 與 DELETE
INSERT/INSERT 衝突
當兩個不同節點上的 INSERTs建立具有相同 PRIMARY KEY 值 (或不存在 PRIMARY KEY 時相同的 UNIQUE 限制值) 的元組時,就會發生此最常見的衝突。
pgactivelink 使用來自原始主機的時間戳記來保留最新的元組,以解決 INSERT 衝突。您可以使用自訂衝突處理常式覆寫此預設行為。雖然此程序不需要特殊管理員動作,但請注意,pgactivelink 會捨棄所有節點的其中一個 INSERT 操作。除非您的自訂處理常式實作,否則不會自動合併資料。
pgactivelink 只能解決涉及單一限制違規的衝突。如果 INSERT 違反多個 UNIQUE 限制,您必須實作額外的衝突解決策略。
違反多個 UNIQUE 限制條件的 INSERTs
INSERT/INSERT 衝突可能會違反多個 UNIQUE 限制,包括 PRIMARY KEY。pgactivelink 只能處理涉及單一 UNIQUE 限制的衝突。當衝突違反多個 UNIQUE 限制時,套用工作者會失敗並傳回下列錯誤:
multiple unique constraints violated by remotely INSERTed tuple.
在較舊版本中,此情況會產生「分散唯一性衝突」錯誤。
若要解決這些衝突,您必須採取手動動作。刪除衝突的本機元組或更新它們,以移除與新遠端元組的衝突。請注意,您可能需要處理多個衝突元組。目前,pgactivelink 不提供內建功能來忽略、捨棄或合併違反多個唯一限制的元組。
注意
如需詳細資訊,請參閱違反多個 UNIQUE 限制UPDATEs。
UPDATE/UPDATE 衝突
當兩個節點同時修改相同的元組而不變更其 PRIMARY KEY 時,就會發生此衝突。如果已定義,pgactivelink 會使用last-update-wins贏邏輯或您的自訂衝突處理常式來解決這些衝突。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 無法使用這些操作的last-update-wins邏輯自動解決衝突。您必須確保 PRIMARY KEY 更新不會與現有值衝突。如果在 PRIMARY KEY 更新期間發生衝突,它們會成為需要您手動介入的不同衝突。如需處理這些情況的詳細資訊,請參閱 差異衝突。
違反多個 UNIQUE 限制UPDATEs
當傳入的 UPDATE 違反多個 UNIQUE 限制條件或 PRIMARY KEY 值時,pgactivelink 無法套用last-update-wins衝突解決。此行為類似於具有多個限制違規的 INSERT 操作。這些情況會產生需要您手動介入的差異衝突。如需詳細資訊,請參閱差異衝突。
更新/刪除衝突
當一個節點UPDATEs資料列,而另一個節點同時DELETEs資料列時,就會發生此衝突。在此情況下,重播時會發生 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 衝突
此衝突可能發生在多節點環境中。當一個節點 INSERTs 資料列、第二個節點 UPDATEs,以及第三個節點在原始 INSERT 之前收到 UPDATE 時,就會發生這種情況。根據預設,除非您的自訂衝突觸發條件另有指定,否則 pgactivelink 會透過捨棄 UPDATE 來解決這些衝突。請注意,此解析方法可能會導致節點之間的資料不一致。如需類似案例及其處理方式的詳細資訊,請參閱 更新/刪除衝突。
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 上不存在此使用者 -
在 Node1 上
fred
建立資料表時,資料表會以擁有者fred
身分使用 複寫 -
當此 DDL 命令套用至 Node2 時,它會失敗,因為使用者
fred
不存在 -
此失敗會在 Node2 上的 PostgreSQL 日誌中產生錯誤,並遞增
pgactive.pgactive_stats.nr_rollbacks
計數器
解決方案:在 Node2 fred
上建立使用者。使用者不需要相同的許可,但必須存在於兩個節點上。
如果資料表存在於一個節點上,但另一個節點上,資料修改操作將會失敗:
-
Node1 有一個名為
foo
的資料表,不存在於 Node2 -
複寫至 Node2 時,Node1 上
foo
資料表上的任何 DML 操作都會失敗
解決方案:在具有相同結構的 Node2 foo
上建立資料表。
注意
pgactivelink 目前不會複寫 CREATE USER 命令或 DDL 操作。DDL 複寫計劃用於未來的版本。
鎖定衝突和死結中止
由於 pgactive 套用程序的運作方式與一般使用者工作階段相同,因此會遵循標準資料列和資料表鎖定規則。這可能會導致 pgactivelink 套用程序等待使用者交易或其他套用程序所保留的鎖定。
下列類型的鎖定可能會影響套用程序:
-
依使用者工作階段明確鎖定資料表層級 (鎖定資料表...)
-
明確的資料列層級鎖定 (SELECT ... FOR UPDATE/FOR SHARE) 依使用者工作階段
-
從外部金鑰鎖定
-
從本機活動或從其他伺服器套用,因為資料列 UPDATEsINSERTs 或 DELETEs隱含鎖定
死鎖可能發生在:
-
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 時,才會發生此資料表的衝突記錄。當 log_min_messages 設定為 LOG
或 時,無論pgactive.log_conflicts_to_table
設定為何lower
,pgactive 擴充功能也會將衝突記錄到 PostgreSQL 日誌檔案。
使用衝突歷史記錄表來:
-
測量應用程式產生衝突的頻率
-
識別衝突發生的位置
-
改善您的應用程式以降低衝突率
-
偵測衝突解決未產生所需結果的案例
-
判斷您需要使用者定義的衝突觸發條件或應用程式設計變更的位置
對於資料列衝突,您可以選擇記錄資料列值。這是由 pgactive.log_conflicts_to_table
設定所控制。請注意:
-
這是全全球資料庫的選項
-
無法依資料表控制資料列值記錄
-
欄位編號、陣列元素或欄位長度不套用任何限制
-
如果您使用可能觸發衝突的多 MB 資料列,則可能不建議啟用此功能
由於衝突歷史記錄資料表包含資料庫中每個資料表的資料 (每個資料表都有可能不同的結構描述),因此記錄的資料列值會儲存為 JSON 欄位。JSON 是使用 建立row_to_json
,類似於直接從 SQL 呼叫它。PostgreSQL 不提供json_to_row
函數,因此您需要資料表特定的程式碼 (PL/pgSQL、PL/Python、PL/Perl 等),才能從記錄的 JSON 重建複合類型元組。
注意
使用者定義衝突的支援會規劃為未來的延伸功能。