コンフリクト解決の無効化
このページでは、AppSync API のコンフリクト解決を無効化するために必要なバックエンド変更を説明します。すべてのフロントエンドクライアントが DataStore から移行された後、コンフリクト解決を無効化するとシンクインフラストラクチャ(バージョン追跡、論理削除、差分シンク)が削除され、より簡潔で標準的な GraphQL API が実現します。
問題点
DataStore が有効な場合、Amplify は AppSync をコンフリクト解決で設定します。これにより、移行に影響を与える 3 つの動作が発生します:
-
論理削除。 DataStore を通じて削除されたアイテムは DynamoDB から削除されません。代わりに、DataStore はアイテムに
_deleted: trueを設定します。アイテムはテーブルに無期限に残るか、DynamoDB TTL が期限切れになるまで残ります。 -
メタデータフィールド。 コンフリクト解決は、生成された GraphQL スキーマの各モデルに
_deleted、_version、_lastChangedAtフィールドを追加します。DataStore はシンクプロトコル用にこれらのフィールドを内部で使用します。 -
スキーマの結合。 コンフリクト解決を無効化した場合、Amplify は GraphQL スキーマから
_deleted、_version、_lastChangedAtを削除します。ただし、DynamoDB テーブルには引き続き_deleted: trueの論理削除されたアイテムを含む、これらの属性を持つアイテムが含まれます。
DataStore から標準 GraphQL クライアント(Apollo Client など)にフロントエンドを切り替える際に _deleted フィールドに対処しない場合、アプリは隠すべき論理削除されたアイテムを表示します。
移行ステップの概要
| ステップ | アクション | 目的 |
|---|---|---|
| 1 | コンフリクト解決を無効化 | AppSync が _version と論理削除を管理するのを停止 |
| 2 | スキーマにメタデータフィールドを再追加 | クライアントが _deleted、_version、_lastChangedAt を使用できるように維持 |
| 3 | 変更をプッシュ | 更新された設定をバックエンドに適用 |
| 4 | クリーンアップ(オプション) | DynamoDB から論理削除されたアイテムをパージし、クライアント側の _deleted フィルタリングを削除 |
ステップ 1: コンフリクト解決を無効化
Amplify CLI を実行して GraphQL API のコンフリクト解決を無効化します:
amplify update apiプロンプトが表示されたら:
- GraphQL を選択します
- コンフリクト解決設定が表示されるまでオプションを実行します
- Disable DataStore for entire API(または CLI バージョンに応じて Disable conflict resolution)を選択します
ステップ 2: GraphQL スキーマにメタデータフィールドを再追加
コンフリクト解決を無効化した後、Amplify はスキーマから _deleted、_version、_lastChangedAt を削除します。ただし、DynamoDB テーブルはすべてのアイテムにこれらの属性を引き続き含んでいます。GraphQL スキーマファイル(amplify/backend/api/<apiname>/schema.graphql)の各モデルに 3 つのフィールドすべてを再追加します:
type Todo @model @auth(rules: [{ allow: owner }]) { id: ID! name: String! description: String _deleted: Boolean _version: Int _lastChangedAt: AWSTimestamp}type Post @model @auth(rules: [{ allow: owner }]) { id: ID! title: String! content: String status: String _deleted: Boolean _version: Int _lastChangedAt: AWSTimestamp}以前 DataStore で管理されていたすべてのモデルに _deleted: Boolean、_version: Int、_lastChangedAt: AWSTimestamp を追加します。
これが機能する理由
これらのフィールドをスキーマの通常フィールドとして追加すると:
- AppSync は各フィールドを既存の DynamoDB 属性にマップします。 DynamoDB はスキーマレスなので、
_deleted、_version、_lastChangedAtはすべてのアイテムに既に存在しています。AppSync の自動生成リゾルバーはこれらの属性を直接読み書きします。 - フィールドは生成されたフィルターおよび入力型に表示されます。 これらをモデルに追加すると、
ModelTodoFilterInput、ModelPostFilterInputなどの生成型で利用可能になります。クライアントは_deletedのサーバー側フィルタリングを使用したり、移行中に必要に応じて_versionまたは_lastChangedAtを読み取ることができます。 - 下位互換性があります。 既存のアイテムはこれらの属性の値を保持します。コンフリクト解決を無効化した後に作成された新規アイテムは、明示的に設定されない限り
nullになります。
ステップ 3: 変更をプッシュ
更新された設定をバックエンドに適用します:
amplify pushこれによって以下が行われます:
- AppSync でコンフリクト解決を無効化(シンクテーブル設定と差分シンクリゾルバーを削除)
_deleted、_version、_lastChangedAtを GraphQL スキーマの通常フィールドとして維持- 各フィールドをすべてのアイテムの既存 DynamoDB 属性にマップ(データ移行は不要)
ステップ 4(オプション): 論理削除されたアイテムをクリーンアップ
すべてのクライアントが移行され、すべてが正しく機能していることを確認した後、DynamoDB から論理削除されたアイテムをパージし、コード内のクライアント側 _deleted フィルタリングを削除できます。
4a. DynamoDB から論理削除されたアイテムをハード削除
各 DynamoDB テーブルをスキャンし、_deleted が true であるすべてのアイテムを削除するスクリプトを書きます:
import { DynamoDBClient, ScanCommand, DeleteItemCommand,} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({ region: 'us-east-1' });
async function purgeSoftDeletedItems(tableName: string) { let lastEvaluatedKey: Record<string, any> | undefined;
do { const scanResult = await client.send( new ScanCommand({ TableName: tableName, FilterExpression: '#d = :true', ExpressionAttributeNames: { '#d': '_deleted' }, ExpressionAttributeValues: { ':true': { BOOL: true } }, ExclusiveStartKey: lastEvaluatedKey, }) );
for (const item of scanResult.Items ?? []) { await client.send( new DeleteItemCommand({ TableName: tableName, Key: { id: item.id }, }) ); console.log(`Deleted item ${item.id.S} from ${tableName}`); }
lastEvaluatedKey = scanResult.LastEvaluatedKey; } while (lastEvaluatedKey);}
// 各テーブルに対して実行await purgeSoftDeletedItems('Todo-<apiId>-<env>');await purgeSoftDeletedItems('Post-<apiId>-<env>');4b. クライアントコードを更新
論理削除されたアイテムをパージした後、コードからクライアント側の _deleted フィルタリングを削除します。フロントエンド移行中に、リストクエリ結果に .filter(item => !item._deleted) を追加しました。DynamoDB からすべての論理削除されたアイテムが削除されたら、これは不要になります。
// クリーンアップ前: 論理削除されたアイテムをフィルタリングconst posts = data.listPosts.items.filter((post) => !post._deleted);
// クリーンアップ後: フィルターは不要const posts = data.listPosts.items;重要な考慮事項
論理削除されたアイテムを最初にパージしない理由
すべてのクライアントが同時に DataStore を停止することは保証できません。ユーザーはアプリのキャッシュされたバージョンを実行している可能性があります。また、デプロイメントが段階的にロールアウトされる可能性があります。一部のクライアントがまだスキーマで _deleted を期待している状態でコンフリクト解決を無効化すると、ValidationError 例外が発生します。
このガイドのアプローチは下位互換性があります。DataStore クライアント(移行中)と標準 GraphQL クライアントが共存できます。これは _deleted が通常フィールドとしてスキーマに残るためです。
DynamoDB TTL と論理削除されたアイテム
DataStore が有効な場合、Amplify は DynamoDB の _ttl 属性に TTL を設定することがあります。TTL を持つ論理削除されたアイテムは、TTL の期限切れ後に DynamoDB によって自動的にハード削除されます。
コンフリクト解決を無効化した後:
- DynamoDB テーブルの TTL 設定は引き続きアクティブ(テーブルレベル設定であり、AppSync で管理されないため)
_ttl値を持つ既存の論理削除されたアイテムは引き続き自動的に削除されます- 新規削除はハード削除になります(
_ttlは設定されません)
AWS コンソール(テーブル → テーブル → 追加設定 → Time to Live)で DynamoDB テーブルの TTL 設定を確認します。
メタデータフィールドは自動管理されなくなります
コンフリクト解決を無効化した後、AppSync はミューテーション時に _version を自動インクリメントしたり、_lastChangedAt を自動更新しなくなります。フィールドは DynamoDB の最後の値を保持でき、クライアントが読み取ることができますが、アプリケーションコードが明示的に設定しない限り更新されません。クリーンアップステップ(ステップ 4)中に、論理削除されたアイテムをパージし、クライアント側の _deleted フィルタリングを削除できます。
移行後の監視
移行を完了した後、アプリケーションで以下を監視します:
- AppSync CloudWatch ログの
ValidationError。 これはクライアントがスキーマに存在しなくなったフィールドで問い合わせまたはミューテーションを送信していることを示します。 - リストクエリの予期しないアイテム。 論理削除されたアイテムが表示される場合は、すべてのリストクエリ結果がクライアント側で
.filter(item => !item._deleted)でフィルタリングされていることを確認します。 - DynamoDB の
ConditionalCheckFailedException。 コンフリクト解決を無効化した後、_versionチェックが強制されなくなるため、これは発生しなくなります。