충돌 감지 및 동기화 - AWS AppSync

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

충돌 감지 및 동기화

버전이 지정된 데이터 원본

AWS AppSync는 현재 DynamoDB 데이터 원본의 버전 관리를 지원합니다. 충돌 감지, 충돌 해결 및 동기화 작업에는 Versioned 데이터 원본이 필요합니다. 데이터 원본에 버전 관리를 활성화하면 AWS AppSync에서 자동으로 다음을 수행합니다.

  • 객체 버전 관리 메타데이터로 항목을 향상시킵니다.

  • AWS AppSync 변형을 통한 항목의 변경 사항을 델타 테이블에 기록합니다.

  • 구성 가능한 시간 동안 기본 테이블에서 삭제된 항목의 “삭제 표시”를 유지합니다.

버전이 지정된 데이터 원본 구성

DynamoDB 데이터 원본의 버전 관리를 활성화할 때 다음 필드를 지정합니다.

BaseTableTTL

기본 테이블에서 삭제된 항목의 “삭제 표시”(항목이 삭제되었음을 나타내는 메타데이터 필드)를 유지하는 시간(분)입니다. 항목이 삭제되는 즉시 제거하려면 이 값을 0으로 설정하면 됩니다. 이 필드는 필수 항목입니다.

DeltaSyncTableName

AWS AppSync 변형이 있는 항목에 대한 변경 사항이 저장되는 테이블의 이름입니다. 이 필드는 필수 항목입니다.

DeltaSyncTableTTL

델타 테이블의 항목을 유지할 시간(분)입니다. 이 필드는 필수 항목입니다.

델타 동기화 테이블

AWS AppSync는 현재 PutItem, UpdateItemDeleteItem DynamoDB 작업을 사용하는 변형에 대해 델타 동기화 로깅을 지원합니다.

AWS AppSync 변형이 버전 지정된 데이터 원본의 항목을 변경하면 해당 변경의 레코드는 증분 업데이트에 최적화된 델타 테이블에 저장됩니다. 버전이 지정된 다른 데이터 원본에 대해 서로 다른 델타 테이블(예: 유형당 하나, 도메인 영역당 하나)을 사용하거나 API에 대해 단일 델타 테이블을 사용하도록 선택할 수 있습니다. AWS AppSync에서는 기본 키의 충돌을 피하기 위해 여러 API에 단일 델타 테이블을 사용하지 않을 것을 권장합니다.

이 테이블에 필요한 스키마는 다음과 같습니다.

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 형식, 항목의 프라이머리 키, 항목의 버전을 연결하여 구성됩니다. 이 필드의 조합은 델타 테이블에 있는 모든 항목의 고유성을 보장합니다(예: 시간이 09:30:00, ID가 1a, 버전이 2인 경우 09:30:00:1a:2).

VTL 매핑 템플릿의 customPartitionKey 플래그가 파티션 키의 열 이름으로 설정되면(AWS AppSync 개발자 안내서DynamoDB에 대한 해석기 매핑 템플릿 참조에서 확인) ds_sk의 형식이 변경되고 기본 테이블에서 조합 키 값을 정렬 키 값으로 대체하여 문자열이 구성됩니다. 위의 예제를 사용하자면, 기본 테이블의 레코드에 1a의 파티션 키 값과 2b의 정렬 키 값이 있는 경우 문자열의 새 값은 09:30:00:2b:3가 됩니다.

_ttl

델타 테이블에서 항목을 제거해야 할 때 타임스탬프를 Epoch 초 단위로 저장하는 숫자 값입니다. 이 값은 데이터 원본에 구성된 DeltaSyncTableTTL 값을 변경이 발생한 순간에 추가하여 결정됩니다. 이 필드는 DynamoDB TTL 속성으로 구성해야 합니다.

기본 테이블과 함께 사용하도록 구성된 IAM 역할에는 델타 테이블에서 작업할 수 있는 권한도 포함되어야 합니다. 이 예제에서는 Comments라는 기본 테이블과 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

항목이 삭제되었음을 나타내는 부울 “삭제 표시” 값입니다. 로컬 데이터 스토어에서 삭제된 항목을 제거하기 위해 애플리케이션에서 사용할 수 있습니다.

_ttl

기본 데이터 원본에서 항목을 제거해야 할 때 타임스탬프를 Epoch 초 단위로 저장하는 숫자 값입니다.

ds_pk

델타 테이블의 파티션 키로 사용되는 문자열 값입니다.

ds_sk

델타 테이블의 정렬 키로 사용되는 문자열 값입니다.

gsi_ds_pk

글로벌 보조 인덱스를 파티션 키로 지원하기 위해 생성된 문자열 값 특성입니다. VTL 매핑 템플릿에서 customPartitionKeypopulateIndexFields 플래그가 모두 활성화된 경우에만 포함됩니다(AWS AppSync 개발자 안내서DynamoDB에 대한 해석기 매핑 템플릿 참조에서 확인). 활성화된 경우 기본 데이터 원본 이름과 변경이 발생한 날짜의 ISO 8601 형식을 연결하여 값이 구성됩니다(예: 기본 테이블의 이름이 Comments인 경우 이 레코드는 Comments:2019-01-01로 설정됨).

gsi_ds_sk

글로벌 보조 인덱스를 정렬 키로 지원하기 위해 생성된 문자열 값 특성입니다. VTL 매핑 템플릿에서 customPartitionKeypopulateIndexFields 플래그가 모두 활성화된 경우에만 포함됩니다(AWS AppSync 개발자 안내서DynamoDB에 대한 해석기 매핑 템플릿 참조에서 확인). 활성화된 경우 변경이 발생한 시간의 ISO 8601 형식, 기본 테이블에 있는 항목의 파티션 키, 기본 테이블에 있는 항목의 정렬 키, 항목의 버전을 연결하여 값이 구성됩니다(예: 시간이 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 응답 내에, 최신 버전 서버의 기존 항목이 제공됩니다. 그런 다음 클라이언트는 이 충돌을 로컬로 처리하고, 항목 버전이 업데이트된 변형을 다시 시도해야 합니다.

Automerge

Automerge를 사용하면 개발자가 다른 전략으로는 처리할 수 없었던 충돌을 수동으로 병합하는 클라이언트 측 로직을 작성하지 않고도 충돌 해결 전략을 쉽게 구성할 수 있습니다. Automerge는 충돌을 해결하기 위해 데이터를 병합할 때 엄격한 규칙 세트를 준수합니다. Automerge의 원칙은 GraphQL 필드의 기본 데이터 유형을 중심으로 합니다. 내용은 다음과 같습니다.

  • 스칼라 필드의 충돌: GraphQL 스칼라 또는 컬렉션이 아닌 필드(목록, 집합, 맵). 스칼라 필드의 수신 값을 거부하고 서버에 있는 값을 선택합니다.

  • 목록의 충돌: GraphQL 유형과 데이터베이스 유형은 목록입니다. 수신 목록을 서버의 기존 목록과 연결합니다. 수신 중인 변형의 목록 값이 서버의 목록 끝에 추가됩니다. 중복 값은 유지됩니다.

  • 집합의 충돌: GraphQL 유형은 목록이고 데이터베이스 유형은 집합입니다. 서버의 기존 집합과 수신 집합을 사용하여 집합 공용 구조체를 적용합니다. 이는 중복 항목이 없음을 의미하는 집합의 속성을 준수합니다.

  • 수신 중인 변형이 항목에 새 필드를 추가하거나 null 값이 있는 필드에 대해 만들어진 경우, 이를 기존 항목에 병합합니다.

  • 맵의 충돌: 데이터베이스의 기본 데이터 유형이 맵(키-값 문서)인 경우, 맵의 각 속성을 구문 분석하고 처리할 때 위의 규칙을 적용합니다.

Automerge는 업데이트된 버전의 요청을 자동으로 검색, 병합 및 재시도하여 클라이언트가 충돌하는 데이터를 수동으로 병합할 필요가 없도록 합니다.

Automerge가 스칼라 유형에서 충돌을 처리하는 방법의 예를 보여줍니다. 우선, 다음 레코드를 사용하겠습니다.

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

현재 수신 중인 변형이 항목을 업데이트하려고 시도할 수 있으나, 클라이언트가 아직 서버와 동기화되지 않았기 때문에 이전 버전으로 업데이트됩니다. 다음과 같은 형태입니다.

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

수신 중인 요청이 이전 버전인 2임을 알 수 있습니다. 이 흐름 동안 Automerge는 '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” 집합과 “points” 목록이라는 두 개의 새로운 필드가 추가되었습니다. 이 시나리오에서는 버전 불일치로 인해 충돌이 감지됩니다. Automerge는 속성을 준수하고, 스칼라이기 때문에 이름 변경을 거부하고 충돌하지 않는 필드에 추가합니다. 그러면 서버에 저장된 항목이 다음과 같이 표시됩니다.

{ "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”에 대해 수신 중인 필드를 보면, 서버에 하나의 중복 값이 있고 두 개의 새로운 값이 있습니다. 이 경우 기본 데이터 유형이 집합이므로 Automerge는 서버에 있는 값과 수신 중인 요청의 값을 결합하여 중복을 제거합니다. 마찬가지로 하나의 중복 값과 하나의 새로운 값이 있는 “points” 필드에는 충돌이 있습니다. 그러나 여기에서는 기본 데이터 유형이 목록이므로, Automerge는 수신 중인 요청의 모든 값을 서버에 이미 있는 값의 끝에 추가하기만 합니다. 그 결과, 서버에 저장된 병합 이미지는 다음과 같이 나타납니다.

{ "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”가 생략되고 있습니다. Automerge는 생략된 필드를 유지하므로(참고: 필드를 제거하려는 경우 일치하는 버전으로 요청을 다시 시도해야 함) 손실되는 법이 없습니다. 또한 맵 내의 필드에 동일한 규칙을 적용하므로 “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 PutItem & UpdateItem에 지원됩니다.

  • REJECT: 변형을 거부하고 GraphQL 응답의 기존 항목에 오류를 반환합니다. 현재 DynamoDB PutItem, UpdateItem & DeleteItem에 지원됩니다.

  • REMOVE: 기존 항목을 제거합니다. 현재 DynamoDB DeleteItem에 지원됩니다.

Lambda 호출 요청

AWS AppSync DynamoDB 해석기가 LambdaConflictHandlerArn에 지정된 Lambda 함수를 호출합니다. 데이터 원본에 대해 구성된 동일한 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 함수는 호출하는 사람과 해석기 이름을 확인합니다. jeffTheAdmin에서 만든 경우 DeletePost 해석기의 객체를 REMOVE하거나, Update/Put 해석기의 새 항목과 충돌을 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

충돌 감지는 버전 불일치를 찾아내고, 충돌 핸들러는 변형을 거부합니다.

예: 낙관적 동시성 충돌 핸들러를 사용한 충돌 해결. 또는 REJECT와 함께 반환된 Lambda 충돌 핸들러.

ConflictError

충돌을 해결하려고 하면 내부 오류가 발생합니다.

예: Lambda 충돌 핸들러가 잘못된 응답을 반환했습니다. 또는 제공된 리소스 LambdaConflictHandlerArn을 찾을 수 없어 Lambda 충돌 핸들러를 호출할 수 없습니다.

MaxConflicts

충돌 해결을 위한 최대 재시도 횟수에 도달했습니다.

예: 동일한 객체에 대한 동시 요청이 너무 많습니다. 충돌이 해결되기 전에 객체가 다른 클라이언트에 의해 새 버전으로 업데이트됩니다.

BadRequest

클라이언트는 메타데이터 필드(_version, _ttl, _lastChangedAt, _deleted)를 업데이트하려고 합니다.

예: 클라이언트는 업데이트 변형으로 객체의 _버전을 업데이트하려고 시도합니다.

DeltaSyncWriteError

델타 동기화 레코드를 쓰지 못했습니다.

예: 변형이 성공했지만 델타 동기화 테이블에 쓰려고 할 때 내부 오류가 발생했습니다.

InternalFailure

내부 오류가 발생했습니다.

CloudWatch Logs

AWS AppSync API가 로깅 설정이 Field-Level Logs enabled로 설정되고 Field-Level Logs의 로그 수준이 ALL로 설정된 CloudWatch Logs를 활성화한 경우, AWS AppSync가 로그 그룹에 충돌 감지 및 해결 정보를 방출합니다. 로그 메시지 형식에 대한 자세한 내용은 충돌 탐지 및 동기화 로깅 설명서를 참조하십시오.

동기화 작업

버전이 지정된 데이터 원본은 DynamoDB 테이블에서 모든 결과를 검색한 다음 마지막 쿼리(델타 업데이트) 이후에 변경된 데이터만 수신할 수 있는 Sync 작업을 지원합니다. AWS AppSync가 Sync 작업에 대한 요청을 수신하면 요청에 지정된 필드를 사용하여 기본 테이블 또는 델타 테이블에 액세스해야 하는지 여부를 결정합니다.

  • lastSync 필드가 지정되지 않으면 기본 테이블에서 Scan이 수행됩니다.

  • lastSync 필드가 지정되었지만 값이 current moment - DeltaSyncTTL 이전인 경우 기본 테이블에서 Scan이 수행됩니다.

  • lastSync 필드가 지정되고 값이 current moment - DeltaSyncTTL 시점 또는 그 이후인 경우 델타 테이블에서 Query가 수행됩니다.

AWS AppSync는 모든 Sync 작업에 대해 응답 매핑 템플릿으로 startedAt 필드를 반환합니다. 이 startedAt 필드는 로컬로 저장하고 다른 요청에 사용할 수 있는 Sync 작업이 시작된 시간(Epoch 밀리초)입니다. 페이지 매김 토큰이 요청에 포함된 경우, 이 값은 결과의 첫 페이지에 대한 요청에 의해 반환된 값과 동일합니다.

Sync 매핑 템플릿 형식에 대한 자세한 내용은 매핑 템플릿 참조를 참조하십시오.