本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
了解主動-主動衝突
當您在主動-主動模式下使用 pgactive 時,從多個節點寫入相同的資料表可能會產生資料衝突。雖然某些叢集系統使用分散式鎖定來防止並行存取,但 pgactive 採取了更樂觀的方法,更適合地理分散式應用程式。
有些資料庫叢集系統會使用分散式鎖定來防止並行資料存取。雖然此方法可在伺服器接近時運作,但它不支援地理分散式應用程式,因為它需要極低的延遲才能獲得良好的效能。pgactive 延伸模組使用樂觀的方法,而不是使用分散式鎖定 (悲觀方法)。這表示:
-
協助您盡可能避免衝突。
-
允許發生特定類型的衝突。
-
在發生衝突時提供衝突解決方法。
此方法可讓您在建置分散式應用程式時更具彈性。
衝突的發生方式
如果所有涉及的交易都在相同節點上同時發生,則無法發生的事件序列會產生節點間衝突。由於節點只會在交易遞交之後交換變更,因此每個交易個別對其遞交的節點有效,但如果在同時完成其他工作的另一個節點上執行,則會無效。由於 pgactive 套用基本上會在其他節點上重新執行交易,如果套用的交易與在接收節點上遞交的交易之間發生衝突,則重新執行操作可能會失敗。
當所有交易都在單一節點上執行時,大多數衝突不會發生的原因是 PostgreSQL 具有防止衝突的交易間通訊機制,包括:
-
UNIQUE 索引
-
序列
-
資料列和關聯鎖定
-
SERIALIZABLE 相依性追蹤
這裡的所有機制都是在交易之間進行通訊的方式,以防止不需要的並行問題
pgactive 可實現低延遲並妥善處理網路分割區,因為它不使用分散式交易管理員或鎖定管理員。不過,這表示不同節點上交易的執行彼此完全隔離。雖然隔離通常會改善資料庫一致性,但在這種情況下,您需要減少隔離以防止衝突。
衝突類型
可能發生的衝突包括:
主題
PRIMARY KEY 或 UNIQUE 衝突
當多個操作嘗試以在單一節點上不可行的方式修改相同的資料列索引鍵時,就會發生資料列衝突。這些衝突代表最常見的資料衝突類型。
pgactive 透過 last-update-wins 處理或您的自訂衝突處理常式來解決偵測到的衝突。
資料列衝突包括:
-
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 衝突可能會違反多個 UNIQUE 限制條件,包括 PRIMARY KEY。pgactivelink 只能處理涉及單一 UNIQUE 限制條件的衝突。當衝突違反多個 UNIQUE 限制條件時,套用工作者會失敗並傳回下列錯誤:
multiple unique constraints violated by remotely INSERTed tuple.
在較舊版本中,此情況會產生「發散唯一性衝突」錯誤。
若要解決這些衝突,您必須採取手動動作。針對衝突的本機元組進行 DELETE 操作或進行 UPDATE 操作,以移除與新遠端元組的衝突。請注意,您可能需要處理多個衝突元組。目前,pgactivelink 不提供內建功能來忽略、捨棄或合併違反多個唯一限制條件的元組。
注意
如需詳細資訊,請參閱違反多個 UNIQUE 限制條件的 UPDATE。
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 限制條件的 UPDATE
當傳入 UPDATE 違反多個 UNIQUE 限制條件或 PRIMARY KEY 值時,pgactivelink 無法套用 last-update-wins 衝突解決方法。此行為類似於違反多個限制條件的 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 連結建立排除限制條件,或在將獨立資料庫轉換為 pgactive 連結時未捨棄現有限制條件,複寫可能會中斷。若要還原複寫進度,請移除或更改與傳入遠端元組衝突的本機元組,以便套用遠端交易。
全域資料衝突
使用 pgactivelink 時,如果節點具有不同的全域 PostgreSQL 全系統資料 (例如角色),可能會發生衝突。這些衝突可能會導致操作 (主要是 DDL) 在一個節點上成功並遞交,但無法套用至其他節點。
如果使用者存在於一個節點,但不存在於另一個節點,則可能會發生複寫問題:
-
Node1 有名為
fred的使用者,但 Node2 上不存在此使用者 -
當
fred在 Node1 上建立資料表時,資料表會以具備擁有者身分的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 套用程序等待使用者交易或其他套用程序所保留的鎖定。
下列類型的鎖定可能會影響套用程序:
-
依使用者工作階段的明確資料表層級鎖定 (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 時,才會將衝突記錄到此資料表。當 log_min_messages 設定為 LOG 或 lower 時,無論 pgactive.log_conflicts_to_table 設定為何,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 重建複合類型元組。
注意
使用者定義衝突的支援會規劃為未來的延伸模組功能。