AppSyncのVersionedデータソースで実現する堅牢なデータ同期基盤 - 2025年最新実装ガイド

AppSyncのVersionedデータソースで実現する堅牢なデータ同期基盤 - 2025年最新実装ガイド

最終更新日:2025年09月01日公開日:2025年09月01日
益子 竜与志
writer:益子 竜与志
XThreads

AWS AppSyncのVersionedデータソース機能は、競合検出や差分同期(Delta Sync)を実現する重要な仕組みです。実はこの機能、ドキュメントの奥深くに記載されているため、見落としがちですが、リアルタイム同期が必要なアプリケーションやオフライン対応が求められるモバイルアプリ開発において、非常に強力な武器となります。

本記事では、2025年最新の仕様に基づいて、実装上の注意点と実践的な活用方法を詳しく解説します。

AppSyncのVersionedデータソースで実現する堅牢なデータ同期基盤 - 2025年最新実装ガイド

なぜ今Versionedデータソースが重要なのか

モバイルファーストの時代において、オフライン対応やリアルタイム同期は避けて通れない要件になりました。特に、複数のクライアントが同時に同じデータを更新する可能性があるシステムでは、「競合検出」と「競合解決」の仕組みが必須です。

AppSyncの「Versionedデータソース」は、こうした要件に対する答えのひとつです。しかし、この機能は現在もDynamoDBのみがサポートしており、その制約を理解した上で活用することが重要になります。実際のプロジェクトで導入する際、設計段階でこの制約を考慮せずに進めると、後々大きな手戻りが発生することを何度も経験してきました。

Versionedデータソースが提供する3つの価値

自動化されるメタデータ管理

Versionedを有効化すると、AppSyncが自動的に以下のメタデータを付与・管理します。

引用:Versioning DynamoDB data sources in AWS AppSync オブジェクトのバージョン管理メタデータ(_version, lastChangedAt, deleted, _ttl)の付与・管理

これらのメタデータは、単なる付加情報ではありません。「_version」はOptimistic Lockingの要となり、「_lastChangedAt」は差分同期の起点となる重要な情報です。また、「_deleted」フラグにより、論理削除と物理削除を使い分けることができます。

メタデータによるアイテムサイズの増加を考慮する必要があり、AWS公式ドキュメントでは「500バイト+最大プライマリキーサイズ」を設計時の予約推奨値としています。これは見落としがちですが、DynamoDBの料金計算に直接影響するため、初期設計で必ず考慮すべきポイントです。

変更履歴の自動記録

「PutItem」「UpdateItem」「DeleteItem」の操作が実行されると、自動的にDeltaテーブルに変更履歴が記録されます。これにより、以下の実装が可能になります。

  • 特定時点からの差分データのみを取得する効率的な同期処理
  • 監査ログとしての活用
  • 変更履歴を基にしたデータ分析

Deltaテーブルへの記録は非同期で行われるため、ベーステーブルへの書き込みパフォーマンスには影響しません。ただし、Deltaテーブル側のキャパシティ設計を忘れると、履歴記録の失敗によって同期処理に影響が出る可能性があります。

Tombstone方式による削除管理

削除された項目は即座に物理削除されるのではなく、「tombstone(墓石)」として一定期間ベーステーブルに保持されます。これにより、削除操作もクライアントに同期できるようになります。

「BaseTableTTL」を0に設定すると即座に削除されますが、オフライン状態のクライアントが存在する場合、削除情報が同期されない可能性があるため、ユースケースに応じた適切な値の設定が必要です。

実装における必須要件と注意点

Deltaテーブルのスキーマ設計

Deltaテーブルには厳格なスキーマ要件があります。以下の属性が必須となり、これらの命名規則は変更できません。

表 Deltaテーブル必須属性一覧

属性名

タイプ

役割

形式例

ds_pk

String

パーティションキー

ベースデータソース名:変更日のISO8601

ds_sk

String

ソートキー

変更時刻ISO8601:項目PK:バージョン

_ttl

Number

TTL(エポック秒)

1735286400

gsi_ds_pk

String

GSI用パーティションキー(オプション)

VTL設定時に自動生成

gsi_ds_sk

String

GSI用ソートキー(オプション)

VTL設定時に自動生成

これらの属性は、AppSyncが内部的に管理するため、開発者が直接操作することはありません。しかし、Deltaテーブルのキャパシティ設計やGSIの設計時には、これらの属性の特性を理解しておく必要があります。

TTL設定の落とし穴

CloudFormationのDynamoDBConfig仕様において、TTL関連の設定値は「分」単位で指定します。これは非常に重要なポイントで、秒単位と勘違いすると想定外の動作を引き起こします。

実装時によくある間違いとして、以下のような設定があります。

  • DeltaSyncTableTTL: 3600(1時間のつもりが60時間になってしまう)
  • BaseTableTTL: 86400(1日のつもりが60日になってしまう)

正しくは以下のように分単位で指定します。

  • DeltaSyncTableTTL: 60(1時間)
  • BaseTableTTL: 1440(1日)

また、Deltaテーブル側でDynamoDBのTTL機能を「_ttl」属性に対して有効化することを忘れてはいけません。これを忘れると、Deltaテーブルにデータが永続的に蓄積され、ストレージコストが増大し続けます。

最新のCloudFormation実装例

2025年現在の最新仕様に基づいた実装例を示します。以下の例では、プロダクション環境での使用を想定し、オンデマンド課金モデルを採用しています。

Resources:
  # AppSyncのVersionedデータソース
  AppSyncVersionedDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId
      Name: ProductionVersionedDataSource
      Type: AMAZON_DYNAMODB
      ServiceRoleArn: !GetAtt AppSyncDynamoDBRole.Arn
      DynamoDBConfig:
        AwsRegion: !Ref AWS::Region
        TableName: !Ref BaseTable
        Versioned: true
        DeltaSyncConfig:
          DeltaSyncTableName: !Ref DeltaSyncTable
          DeltaSyncTableTTL: "10080"  # 7日間(分単位)
          BaseTableTTL: "1440"         # 1日間(分単位)

  # ベーステーブル
  BaseTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${AWS::StackName}-base-table
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

  # Deltaテーブル
  DeltaSyncTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${AWS::StackName}-delta-sync
      AttributeDefinitions:
        - AttributeName: ds_pk
          AttributeType: S
        - AttributeName: ds_sk
          AttributeType: S
      KeySchema:
        - AttributeName: ds_pk
          KeyType: HASH
        - AttributeName: ds_sk
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST
      TimeToLiveSpecification:
        AttributeName: "_ttl"
        Enabled: true
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true

  # IAMロール(必要最小権限)
  AppSyncDynamoDBRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: appsync.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: DynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:DeleteItem
                  - dynamodb:Query
                  - dynamodb:Scan
                Resource:
                  - !GetAtt BaseTable.Arn
                  - !GetAtt DeltaSyncTable.Arn
                  - !Sub ${BaseTable.Arn}/index/*
                  - !Sub ${DeltaSyncTable.Arn}/index/*

この実装例では、ポイントインタイムリカバリを有効化しており、万が一の障害に備えています。また、オンデマンド課金を採用することで、アクセスパターンが不規則な初期フェーズでもコスト効率的に運用できます。

Sync操作の動作原理と最適化

lastSyncパラメータによる挙動の違い

AWS公式ドキュメントによると、Sync操作は「lastSync」パラメータの値によって以下のように動作が変わります。

Sync操作の挙動を決定する3つのパターンがあります。

  • lastSyncを指定しない場合はベーステーブルで全件Scanを実行
  • lastSyncが「現在時刻 - DeltaSyncTableTTL」より前の場合はベーステーブルで全件Scanを実行
  • lastSyncが「現在時刻 - DeltaSyncTableTTL」以降の場合はDeltaテーブルでQueryを実行

この仕組みを理解していないと、期待する差分同期が動作せず、毎回全件取得が発生してパフォーマンスが劣化することがあります。特に、DeltaSyncTableTTLを短く設定しすぎると、少し古いlastSyncでも全件Scanが発生してしまうため注意が必要です。

実践的な同期戦略

実際のプロジェクトでは、以下のような同期戦略を採用することが多いです。

初回起動時は全件同期を行い、その後は定期的な差分同期とリアルタイムサブスクリプションを組み合わせます。この際、ネットワークの状態に応じて同期頻度を動的に調整することで、バッテリー消費とデータの鮮度のバランスを取ります。

interface SyncStrategy {
  initialSync: () => Promise<void>;
  deltaSync: (lastSync: number) => Promise<void>;
  subscribeToChanges: () => Subscription;
}

class OptimizedSyncManager implements SyncStrategy {
  private lastSyncTimestamp: number = 0;
  private syncInterval: number = 30000; // 30秒

  async initialSync(): Promise<void> {
    // lastSyncを指定せずに全件取得
    const response = await this.syncOperation({
      lastSync: undefined
    });
    this.lastSyncTimestamp = response.startedAt;
  }

  async deltaSync(lastSync: number): Promise<void> {
    // DeltaSyncTableTTL以内のタイムスタンプを指定
    const response = await this.syncOperation({
      lastSync: lastSync
    });
    this.lastSyncTimestamp = response.startedAt;
  }

  subscribeToChanges(): Subscription {
    // リアルタイム更新の購読
    return this.subscribeToMutations();
  }

  private async syncOperation(params: any): Promise<any> {
    // AppSync Sync操作の実装
    // 実際の実装ではGraphQLクライアントを使用
    return {};
  }

  private subscribeToMutations(): Subscription {
    // GraphQLサブスクリプションの実装
    return new Subscription();
  }
}

競合検出と解決戦略の選択

3つの競合解決戦略

Amplify DataStoreのドキュメントによると、AppSyncは以下の3つの競合解決戦略を提供しています。

それぞれの戦略には適した用途があり、要件に応じて選択する必要があります。

表 競合解決戦略の比較

戦略名

特徴

適用シーン

注意点

Optimistic Concurrency

バージョンチェックによる楽観的ロック

厳密な整合性が必要な金融系アプリ

競合時はクライアント側でリトライ処理が必要

Auto Merge

最後の更新が勝つ(Last Writer Wins)

コラボレーション系アプリ、メモアプリ

データの上書きが発生する可能性

Lambda

カスタムロジックによる解決

複雑なビジネスルールがある場合

Lambda関数の開発・保守コストが発生

実際のプロジェクトでは、データの性質によって戦略を使い分けることもあります。例えば、在庫数のような厳密な管理が必要なデータはOptimistic Concurrencyを使用し、ユーザープロフィールのような上書きが許容されるデータはAuto Mergeを使用するという具合です。

競合エラーへの対処

競合が発生した場合、以下のようなエラーが返されることがあります。

  • ConflictUnhandled:競合が解決できなかった
  • MaxConflicts:最大競合回数を超えた
  • BadRequest:クライアントがメタデータを直接編集しようとした

これらのエラーハンドリングを適切に実装することで、ユーザー体験を損なわない同期処理が実現できます。特に「BadRequest」は開発中によく遭遇するエラーで、クライアント側で「_version」や「_lastChangedAt」を直接操作しようとすると発生します。

JavaScriptリゾルバによる最新実装

APPSYNC_JSランタイムの活用

2022年以降、APPSYNC_JSランタイムでSync操作がネイティブサポートされるようになりました。これにより、VTL(Velocity Template Language)を使わずにJavaScriptで差分同期ロジックを実装できます。

// JavaScriptリゾルバの例(TypeScript表記)
interface SyncRequest {
  lastSync?: number;
  limit?: number;
  nextToken?: string;
}

interface SyncResponse {
  items: any[];
  nextToken?: string;
  startedAt: number;
}

export function request(ctx: any): any {
  const { lastSync, limit = 100, nextToken } = ctx.args as SyncRequest;

  return {
    operation: 'Sync',
    lastSync: lastSync,
    limit: limit,
    nextToken: nextToken
  };
}

export function response(ctx: any): SyncResponse {
  const items = ctx.result.items || [];

  // メタデータのフィルタリング(必要に応じて)
  const filteredItems = items.filter((item: any) => !item._deleted);

  return {
    items: filteredItems,
    nextToken: ctx.result.nextToken,
    startedAt: ctx.result.startedAt
  };
}

JavaScriptリゾルバを使用することで、複雑なビジネスロジックも実装しやすくなり、TypeScriptの型定義を活用した開発も可能になります。また、VTLと比較してデバッグが容易になるという利点もあります。

運用上の考慮事項

コスト最適化のポイント

Versionedデータソースを運用する際は、通常のDynamoDB運用と比較して以下の追加コストを考慮する必要があります。

ストレージコストについては、ベーステーブルのメタデータ分(500B + 最大PK)の増加とDeltaテーブルの変更履歴分が追加されます。特にDeltaテーブルは、更新頻度が高いアプリケーションでは急速にデータが蓄積されるため、適切なTTL設定が重要です。

読み書きコストについては、Deltaテーブルへの書き込みが追加で発生します。ただし、差分同期によってクライアントの読み取り回数が削減される効果もあるため、トータルでは効率化される場合が多いです。

モニタリングとアラート設定

以下のメトリクスを監視することを推奨します。

  • Deltaテーブルのアイテム数とストレージサイズ
  • Sync操作のレイテンシと成功率
  • 競合発生頻度とエラー率
  • TTLによる削除処理の実行状況

特にDeltaテーブルのアイテム数が想定を超えて増加している場合は、TTL設定が正しく機能していない可能性があります。CloudWatch Alarmsを設定して、早期に異常を検知できるようにしておくことが重要です。

移行戦略とロールバック計画

既存のDynamoDBテーブルをVersionedデータソースに移行する場合、以下の手順を推奨します。

  1. 開発環境で十分なテストを実施
  2. Deltaテーブルを事前に作成し、TTL設定を確認
  3. メンテナンスウィンドウを設定して移行を実施
  4. 移行後、Sync操作とメタデータの生成を確認
  5. 問題発生時のロールバック手順を事前に準備

ロールバックが必要になった場合、Versionedを無効化するだけでなく、クライアント側のキャッシュクリアや同期状態のリセットも必要になることがあります。このため、クライアントアプリケーションのバージョン管理と連携した移行計画が重要です。

プロダクション導入時のチェックリスト

実際にプロダクション環境に導入する前に、以下の項目を確認することをお勧めします。

プロダクション環境への導入前に確認すべき重要項目があります。

  • DeltaテーブルのTTL設定(_ttl属性)が有効になっているか
  • DeltaSyncTableTTLとBaseTableTTLの値が要件に適しているか(分単位での指定)
  • IAMロールに必要な権限が付与されているか
  • Deltaテーブルのキャパシティが適切に設計されているか
  • 競合解決戦略が要件に合致しているか
  • クライアント側のエラーハンドリングが実装されているか
  • モニタリングとアラートが設定されているか
  • バックアップとリカバリ計画が策定されているか


これらの項目を事前にチェックすることで、本番環境での予期せぬトラブルを防ぐことができます。

まとめ

AppSyncのVersionedデータソースは、単なるバージョン管理機能ではなく、競合検出・解決、差分同期を含む包括的なデータ同期基盤を提供する強力な機能です。2025年現在でもDynamoDBのみのサポートという制約はありますが、その制約の中で最大限の価値を引き出すことができます。

特に重要なのは、TTL設定の単位(分)を正しく理解することと、Deltaテーブルの設計を適切に行うことです。これらを誤ると、想定外のコスト増加やパフォーマンス劣化を引き起こす可能性があります。

また、JavaScriptリゾルバのサポートにより、より柔軟な実装が可能になったことも見逃せません。VTLからの移行を検討している場合は、この機会にJavaScriptリゾルバへの移行も合わせて検討する価値があるでしょう。

リアルタイム同期やオフライン対応が求められる現代のアプリケーション開発において、AppSyncのVersionedデータソースは確実に検討すべき選択肢のひとつです。適切に設計・実装することで、堅牢で拡張性の高いデータ同期基盤を構築できることでしょう。

Careerバナーconsultingバナー