リソースを効率的に同期する

このガイドでは、カレンダー データの「増分同期」を実装する方法について説明します。この方法を使用すると、帯域幅を節約しながら、すべてのカレンダー コレクションのデータを同期できます。

目次

概要

増分同期は次の 2 つのステージで構成されています。

  1. 最初の完全同期は、クライアントの状態とサーバーの状態を完全に同期するために、最初に 1 回だけ実行されます。クライアントは、保持する必要がある同期トークンを取得します。

  2. 増分同期は繰り返し実行され、前回の同期以降に発生したすべての変更でクライアントを更新します。クライアントは、サーバーから取得した以前の同期トークンを指定し、レスポンスから新しい同期トークンを保存します。

最初の完全同期

最初の完全同期は、同期するコレクションのすべてのリソースに対する元のリクエストです。リソースの特定のサブセットのみを同期する場合は、必要に応じてリクエスト パラメータを使用してリスト リクエストを制限できます。

リスト オペレーションのレスポンスには、同期トークンを表す nextSyncToken というフィールドがあります。nextSyncToken の値を保存する必要があります。結果セットが大きすぎてレスポンスがページ分けされている場合、nextSyncToken フィールドは最後のページにのみ存在します。

増分同期

増分同期を使用すると、最後の同期リクエスト以降に変更されたすべてのリソースを取得できます。これを行うには、syncToken フィールドに最新の同期トークンを指定してリスト リクエストを実行する必要があります。結果には常に削除されたエントリが含まれるため、クライアントがストレージから削除できるようになります。

前回の増分同期リクエスト以降に大量のリソースが変更された場合、リスト結果に syncToken ではなく pageToken が表示されることがあります。このような場合は、増分同期の最初のページの取得に使用したものとまったく同じリストクエリ(まったく同じ syncToken を使用)を実行し、pageToken を追加して、最後のページで別の syncToken が見つかるまで、次のすべてのリクエストをページネーションする必要があります。今後の次の同期リクエスト用に、この syncToken を保存してください。

増分ページネーション同期が必要なケースのクエリの例を次に示します。

元のクエリ

GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx

// Result contains the following

"nextPageToken":"CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA",

次のページを取得しています

GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx&pageToken=CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA

サーバーが完全同期を必要としている

トークンの有効期限切れや関連する ACL の変更など、さまざまな理由で、同期トークンがサーバーによって無効になることがあります。このような場合、サーバーは増分リクエストにレスポンス コード 410 で応答します。これにより、クライアントのストアの完全ワイプと新しい完全同期がトリガーされます。

サンプルコード

以下のサンプルコード スニペットは、Java クライアント ライブラリで同期トークンを使用する方法を示しています。run メソッドが初めて呼び出されると、完全な同期が実行され、同期トークンが保存されます。以降の実行では、保存された同期トークンが読み込まれ、増分同期が実行されます。

  private static void run() throws IOException {
    // Construct the {@link Calendar.Events.List} request, but don't execute it yet.
    Calendar.Events.List request = client.events().list("primary");

    // Load the sync token stored from the last execution, if any.
    String syncToken = syncSettingsDataStore.get(SYNC_TOKEN_KEY);
    if (syncToken == null) {
      System.out.println("Performing full sync.");

      // Set the filters you want to use during the full sync. Sync tokens aren't compatible with
      // most filters, but you may want to limit your full sync to only a certain date range.
      // In this example we are only syncing events up to a year old.
      Date oneYearAgo = Utils.getRelativeDate(java.util.Calendar.YEAR, -1);
      request.setTimeMin(new DateTime(oneYearAgo, TimeZone.getTimeZone("UTC")));
    } else {
      System.out.println("Performing incremental sync.");
      request.setSyncToken(syncToken);
    }

    // Retrieve the events, one page at a time.
    String pageToken = null;
    Events events = null;
    do {
      request.setPageToken(pageToken);

      try {
        events = request.execute();
      } catch (GoogleJsonResponseException e) {
        if (e.getStatusCode() == 410) {
          // A 410 status code, "Gone", indicates that the sync token is invalid.
          System.out.println("Invalid sync token, clearing event store and re-syncing.");
          syncSettingsDataStore.delete(SYNC_TOKEN_KEY);
          eventDataStore.clear();
          run();
        } else {
          throw e;
        }
      }

      List<Event> items = events.getItems();
      if (items.size() == 0) {
        System.out.println("No new events to sync.");
      } else {
        for (Event event : items) {
          syncEvent(event);
        }
      }

      pageToken = events.getNextPageToken();
    } while (pageToken != null);

    // Store the sync token from the last request to be used during the next execution.
    syncSettingsDataStore.set(SYNC_TOKEN_KEY, events.getNextSyncToken());

    System.out.println("Sync complete.");
  }

従来の同期

イベント コレクションの場合は、イベントリスト リクエストから更新されたフィールドの値を保持し、modifiedSince フィールドを使用して更新されたイベントを取得することで、従来の方法で同期を行うことができます。このアプローチは、更新の漏れ(クエリ制限が適用されていない場合など)によりエラーが発生しやすくなるため、推奨されなくなりました。また、イベントでのみ使用できます。