衝突偵測與同步 - AWS AppSync

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

衝突偵測與同步

已建立版本的資料來源

AWS AppSync 目前支援 DynamoDB 資料來源上的版本控制。衝突偵測、衝突解決和同步作業需要 Versioned 資料來源。當您在資料來源上啟用版本控制時,AWS AppSync 會自動:

  • 使用物件版本控制中繼資料增強項目。

  • 對於具有 AWS AppSync 變動之項目所做的變更,將其記錄到 Delta 資料表。

  • 針對可設定的時間量,在具有「tombstone」的 Base 資料表中,維護已刪除的項目。

已建立版本的資料來源組態

當您在 DynamoDB 資料來源上啟用版本控制時,請指定以下欄位:

BaseTableTTL

在具有「tombstone」的 Base 資料表中,保留已刪除項目的分鐘數 - 指出已刪除項目的中繼資料欄位。如果您想要在刪除項目時立即移除項目,可以將此值設為 0。此欄位為必填。

DeltaSyncTableName

資料表名稱,其中存放對於具有 AWS AppSync 變動之項目所做的變更。此欄位為必填。

DeltaSyncTableTTL

保留 Delta 資料表中項目的分鐘數。此欄位為必填。

Delta Sync 資料表

AWS AppSync 目前支援使用PutItemUpdateItemDeleteItem DynamoDB 作業進行突變的差異同步記錄。

當 AWS AppSync 變動變更已建立版本資料來源中的項目時,該變更的記錄也將存放在針對累加式更新進行最佳化的 Delta 資料表中。您可以選擇為其他版本化資料來源使用不同的 Delta 表格 (例如,每個類型一個,每個網域區域一個),或針對 API 使用單一 Delta 表格。AWS AppSync 建議不要對多個 API 使用單個 Delta 表,以避免主鍵的衝突。

此資料表所需的結構描述如下所示:

ds_pk

用來做為分割區索引鍵的字串值。它是通過連接基礎數據源名稱和發生更改日期的 ISO 8601 格式(例如Comments:2019-01-01)構建的。

將 VTL 對應範本中的customPartitionKey旗標設定為分區索引鍵的欄名稱時 (請參閱AWS AppSync 開發人員指南中的 DynamoDB 的解析程式對應範本參考)、ds_pk變更格式和字串是透過將分區索引鍵的值附加到基底資料表中的新記錄中來建構。例如,如果基底資料表中的記錄的分區索引鍵值為1a且排序索引鍵值為2b,則字串的新值將為:Comments:2019-01-01:1a

ds_sk

用來做為排序索引鍵的字串值。它是透過串連發生變更時間的 ISO 8601 格式、項目的主索引鍵以及項目版本來建構的。這些字段的組合保證了 Delta 表中每個條目的唯一性(例如,對於的09:30:00一段時間1a和一個 ID 和版本2,這將是09:30:00:1a:2)。

當 VTL 對應範本中的customPartitionKey旗標設定為分區索引鍵的欄名稱時 (請參閱AWS AppSync 開發人員指南中 DynamoDB 的解析器對應範本參考)、ds_sk變更格式和字串是透過將組合鍵的值取代為基底資料表中排序鍵的值來建構。使用上述範例,如果「基底」資料表中的記錄具有的分割區索引鍵值1a且排序索引鍵值為2b,則字串的新值將是:09:30:00:2b:3

_ttl

此數值用於存放應該從 Delta 資料表中移除項目時的時間戳記 (以 epoch 秒為單位)。此值是透過將資料來源上設定的 DeltaSyncTableTTL 值加入發生變更的時刻所決定的。此欄位應該設定為 DynamoDB TTL 屬性。

設定為與 Base 資料表搭配使用的 IAM 角色必須也包含在 Delta 資料表上進行操作的許可。在此範例中,會顯示名為的「基底」資料表Comments和名為的 Delta 資料表的權限原ChangeLog則:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:DeleteItem", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem" ], "Resource": [ "arn:aws:dynamodb:us-east-1:000000000000:table/Comments", "arn:aws:dynamodb:us-east-1:000000000000:table/Comments/*", "arn:aws:dynamodb:us-east-1:000000000000:table/ChangeLog", "arn:aws:dynamodb:us-east-1:000000000000:table/ChangeLog/*" ] } ] }

已建立版本的資料來源中繼資料

AWS AppSync 代表您管理Versioned資料來源上的中繼資料欄位。自行修改這些欄位可能會導致應用程式發生錯誤或資料遺失。這些欄位包括:

_version

依序遞增的計數器,每當對項目進行變更時都會更新。

_lastChangedAt

此數值用於存放上次修改項目時的時間戳記 (以 epoch 毫秒為單位)。

_deleted

布林「tombstone」值,表示已刪除項目。應用程式可以使用此值,從本機資料存放區撤出已刪除的項目。

_ttl

此數值用於存放應該從基礎資料來源中移除項目時的時間戳記 (以 epoch 秒為單位)。

ds_pk

用來做為 Delta 資料表之分割區索引鍵的字串值。

ds_sk

用來做為 Delta 資料表之排序索引鍵的字串值。

gsi_ds_pk

為支援全域次要索引做為資料分割索引鍵而產生的字串值屬性。只有在 VTL 對應範本中同時啟用customPartitionKeypopulateIndexFields旗標時,才會包含它 (請參閱AWS AppSync 開發人員指南中的 DynamoDB 的解析程式對應範本參考)。如果啟用,將透過連接基礎資料來源名稱和發生變更日期的 ISO 8601 格式來建構值 (例如,如果「基底」資料表命名為「註解」,則此記錄將設定為Comments:2019-01-01)。

gsi_ds_sk

為支援全域次要索引做為排序索引鍵而產生的字串值屬性。只有在 VTL 對應範本中同時啟用customPartitionKeypopulateIndexFields旗標時,才會包含它 (請參閱AWS AppSync 開發人員指南中的 DynamoDB 的解析程式對應範本參考)。如果啟用,則會將發生變更的時間 ISO 8601 格式、Base 資料表中項目的分割索引鍵、Base 資料表中項目的排序索引鍵,以及項目的版本 (例如,分割區索引鍵值為的09:30:00時間1a、排序索引鍵值2b,以及此項目的3版本09:30:00:1a#2b:3) 來建構。

基礎資料來源中基礎資料來源中這些中繼資料欄位會影響項目的整體大小。AWS AppSync 建議在設計應用程式時,為版本化的資料來源中繼資料保留 500 位元組 + 最大主索引鍵儲存大小。若要在用戶端應用程式中使用此中繼資料,請在 GraphQL 類型和變動選取範圍中包含 _version_lastChangedAt_deleted 欄位。

衝突偵測與解決方案

當 AWS AppSync 發生並行寫入時,您可以設定「衝突偵測」和「衝突解決」策略,以適當地處理更新。「衝突偵測」會判斷變動是否與資料來源中的實際寫入項目衝突。將conflictDetection欄位中的值設定 SyncConfig 為,即可啟用衝突偵測VERSION

「衝突解決」是偵測到衝突時所採取的動作。這是透過在中設定「衝突處理程式」欄位來決定的 SyncConfig。有三種衝突解決策略:

  • OPTIMISTIC_CONCURRENCY

  • AUTOMERGE

  • LAMBDA

每一種衝突解決策略的詳細介紹如下所示。

版本會 AppSync 在寫入作業期間自動遞增,而且不應由用戶端或在已啟用版本功能的資料來源設定的解析程式之外進行修改。這樣做會改變系統的一致性行為,並可能造成資料遺失。

開放式並行存取

開放式並行存取是 AWS AppSync 為已建立版本之資料來源提供的一種衝突解決策略。當衝突解析程式設為「開放式並行存取」時,如果偵測到傳入變動的版本與物件的實際版本不同,衝突處理常式將僅拒絕傳入請求。在 GraphQL 回應中,將提供最新版本伺服器上的現有項目。然後,預期用戶端在本機處理此衝突,並使用該項目的更新版本重試變動。

自動合併

自動合併提供開發人員一種簡單的方法來設定衝突解決策略,而不需撰寫用戶端邏輯以手動合併其他策略無法處理的衝突。自動合併在合併資料來解決衝突時,遵守嚴格的規則集。自動合併的原則是以 GraphQL 欄位的基礎資料類型為主。如下所示:

  • 標量字段上的衝突:GraphQL 標量或任何不是集合的字段(即列表,集合,地圖)。拒絕純量欄位的傳入值,並選取伺服器中的現有值。

  • List 的衝突:GraphQL 類型和資料庫類型為 List。串連傳入清單與伺服器中的現有清單。傳入變動的清單值將附加到伺服器列表的末尾。將保留重複的值。

  • Set 的衝突:GraphQL 類型為 List,資料庫類型為 Set。使用傳入集合和伺服器中的現有集合套用集合聯集。這符合 Set 的屬性,表示沒有重複的項目。

  • 當傳入的變異將新欄位新增至項目或針對具有值的欄位建立時null,請將該欄位合併至現有項目。

  • Map 的衝突:當資料庫中的基礎資料類型是 Map (即索引鍵-值文件) 時,會套用上述規則,因為其剖析和處理 Map 的每個屬性。

自動合併是為了自動偵測、合併和重試具有更新版本的請求而設計,讓用戶端無需手動合併任何衝突的資料。

為了展現自動合併如何處理純量類型衝突的範例。我們將使用以下記錄做為起點。

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "_version" : 4 }

現在,由於用戶端尚未與伺服器同步,傳入變動可能試圖使用舊版本來更新該項目。如下所示:

{ "id" : 1, "name" : "Nadia", "jersey" : 55, "_version" : 2 }

請注意,傳入請求中的過時版本 2。在此流程中,自動合併拒絕將 ‘jersey’ 欄位更新為 ‘55’ ,並保留值 ‘5’,導致以下項目影像儲存在伺服器中。

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "_version" : 5 # version is incremented every time automerge performs a merge that is stored on the server. }

假設如上所示的項目狀態為版本 5,現在假設傳入變動嘗試使用以下影像對項目進行變動:

{ "id" : 1, "name" : "Shaggy", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set "points": [24, 30, 27] # underlying data type is a List "_version" : 3 }

傳入變動中有三個參考資訊。名稱 (純量) 已變更,但增加兩個新欄位,“interests” (Set) 和 “points” (List)。在這種情況下,因為版本不符,會偵測到衝突。自動合併遵循其屬性,並拒絕名稱變更,因為它是純量,而且是非衝突的欄位。這將導致儲存在伺服器中的項目顯示如下。

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set "points": [24, 30, 27] # underlying data type is a List "_version" : 6 }

現在,使用版本 6 的項目更新影像,現在假設傳入變動 (具有另一個不相符的版本) 嘗試將項目轉換為以下內容:

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "brunch"] # underlying data type is a Set "points": [30, 35] # underlying data type is a List "_version" : 5 }

我們在這裡觀察到 “interests” 的傳入欄位具有伺服器中的一個重複值和兩個新值。在這種情況下,由於基礎資料類型是 Set,所以自動合併會將伺服器中的現有值與傳入請求中的值合併,並刪除任何重複項目。同樣地,“points” 欄位也有衝突,有一個重複值和一個新值。但由於這裡的基礎資料類型是 List,自動合併會簡單地將傳入請求中的所有值附加到伺服器中已存在值的結尾。存放在伺服器上的產生合併影像顯示如下:

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "_version" : 7 }

現在,假設存放在伺服器中的項目在版本 8 中如下所示。

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "stats": { "ppg": "35.4", "apg": "6.3" } "_version" : 8 }

但傳入的請求嘗試使用以下影像來更新項目,再次發生版本不相符:

{ "id" : 1, "name" : "Nadia", "stats": { "ppg": "25.7", "rpg": "6.9" } "_version" : 3 }

在這種情況下,我們可以看到伺服器中已存在的欄位遺失 (interests、points、jersey)。此外,已編輯映射 “stats” 中的 “ppg” 值,已新增新值 “rpg”,而且已省略 “apg”。自動合併會保留已省略的欄位 (注意:如果要移除欄位,必須使用相符的版本再次嘗試該請求),以便不會遺失欄位。它也會將相同的規則套用至映射中的欄位,因此將拒絕對 “ppg” 進行的變更,而保留 “apg” ,並新增欄位 “rpg”。存放在伺服器中的產生項目現在顯示如下:

{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "stats": { "ppg": "35.4", "apg": "6.3", "rpg": "6.9" } "_version" : 9 }

Lambda

衝突解決選項:

  • RESOLVE:以回應承載中提供的新項目取代現有項目。您一次只能對單ㄧ項目重試相同的操作。目前支援 DynamoDB PutItemUpdateItem

  • REJECT:拒絕變動,並透過 GraphQL 回應中的現有項目傳回錯誤。目前支援 DynamoDB PutItemUpdateItemDeleteItem

  • REMOVE:移除現有的項目。目前支援 DynamoDB DeleteItem

Lambda 叫用請求

AWS AppSync DynamoDB 解析程式會叫用中指定的 Lambda 函數LambdaConflictHandlerArn。它會使用資料來源上所設定的相同 service-role-arn。叫用承載的結構如下:

{ "newItem": { ... }, "existingItem": {... }, "arguments": { ... }, "resolver": { ... }, "identity": { ... } }

欄位定義如下:

newItem

預覽項目,如果變動成功。

existingItem

目前位於 DynamoDB 資料表中的項目。

arguments

來自 GraphQL 變動的引數。

resolver

AWS AppSync 解析程式的相關資訊。

identity

發起人的相關資訊。如果使用 API 金鑰存取,則此欄位會設為 Null。

承載範例:

{ "newItem": { "id": "1", "author": "Jeff", "title": "Foo Bar", "rating": 5, "comments": ["hello world"], }, "existingItem": { "id": "1", "author": "Foo", "rating": 5, "comments": ["old comment"] }, "arguments": { "id": "1", "author": "Jeff", "title": "Foo Bar", "comments": ["hello world"] }, "resolver": { "tableName": "post-table", "awsRegion": "us-west-2", "parentType": "Mutation", "field": "updatePost" }, "identity": { "accountId": "123456789012", "sourceIp": "x.x.x.x", "username": "AIDAAAAAAAAAAAAAAAAAA", "userArn": "arn:aws:iam::123456789012:user/appsync" } }

Lambda 叫用回應

適用於 PutItemUpdateItem 衝突解決

RESOLVE 變動。回應必須採用下列格式。

{ "action": "RESOLVE", "item": { ... } }

item 欄位代表將用來取代基礎資料來源中現有項目的物件。如果包含在 item 中,則會忽略主索引鍵和同步中繼資料。

REJECT 變動。回應必須採用下列格式。

{ "action": "REJECT" }

適用於 DeleteItem 衝突解決

REMOVE 項目。回應必須採用下列格式。

{ "action": "REMOVE" }

REJECT 變動。回應必須採用下列格式。

{ "action": "REJECT" }

以下 Lambda 函數範例會檢查進行呼叫的對象和解析程式名稱。如果它是由製作REMOVEjeffTheAdmin,則用於 DeletePost解析器的對象或RESOLVE與更新/放置解析器的新項目衝突。如果不是,則變動為 REJECT

exports.handler = async (event, context, callback) => { console.log("Event: "+ JSON.stringify(event)); // Business logic goes here. var response; if ( event.identity.user == "jeffTheAdmin" ) { let resolver = event.resolver.field; switch(resolver) { case "deletePost": response = { "action" : "REMOVE" } break; case "updatePost": case "createPost": response = { "action" : "RESOLVE", "item": event.newItem } break; default: response = { "action" : "REJECT" }; } } else { response = { "action" : "REJECT" }; } console.log("Response: "+ JSON.stringify(response)); return response; }

錯誤

ConflictUnhandled

衝突偵測發現版本不相符,而且衝突處理常式拒絕變動。

範例:具有開放式並行存取衝突處理常式的衝突解決機制。或者,Lambda 衝突處理常式傳回 REJECT

ConflictError

嘗試解決衝突時,發生內部錯誤。

範例:Lambda 衝突處理常式傳回格式錯誤的回應。或者,無法叫用 Lambda 衝突處理常式,因為找不到提供的資源 LambdaConflictHandlerArn

MaxConflicts

已達到衝突解決的重試次數上限。

範例:同一個物件上有太多並行請求。解決衝突之前,另一個用戶端已將物件更新為新版本。

BadRequest

用戶端嘗試更新中繼資料欄位 (_version_ttl_lastChangedAt_deleted)。

範例:用戶端嘗試更新具有更新變動之 _version 的物件。

DeltaSyncWriteError

無法寫入差異同步記錄。

範例:變動成功,但嘗試寫入差異同步資料表時發生內部錯誤。

InternalFailure

發生內部錯誤。

CloudWatch 日誌

如果AWS AppSync API 已啟用記錄 CloudWatch 檔,且將記錄設定設為 [欄位層級記錄檔],enabled且 [欄位層級記錄檔] 的記錄層級設定為ALL,則AWS AppSync 會將衝突偵測和解決方案資訊發送至記錄群組。如需日誌訊息格式的相關資訊,請參閱衝突偵測與同步記錄的文件

同步操作

版本化資料來源支Sync援可讓您從 DynamoDB 表擷取所有結果的作業,然後僅接收自上次查詢 (差異更新) 以來變更的資料。當 AWS AppSync 收到 Sync 操作的請求時,會使用請求中指定的欄位來決定是否應該存取 Base 資料表或 Delta 資料表。

  • 如果未指定lastSync欄位,則會Scan在「基底」資料表上執行。

  • 如果已指定lastSync欄位,但值在之前current moment - DeltaSyncTTL,則會執行「基底」資料表Scan上的 a。

  • 如果已指定lastSync欄位,且值在上或之後current moment - DeltaSyncTTL,則會執行 Delta 資料表Query上的 a。

AWS AppSync 將該startedAt字段返回到所有Sync操作的響應映射模板。startedAt 欄位是開始進行 Sync 操作時,可以在本機存放並在另一個請求中使用的時間 (以 epoch 毫秒為單位)。如果請求中包含分頁字符,則該值將與請求針對第一頁結果傳回的值相同。

如需 Sync 映射範本的相關資訊,請參閱映射範本參考