パフォーマンス向上のヒント

このドキュメントでは、アプリケーションのパフォーマンスを向上させるための手法について説明します。一部、わかりやすく説明するために他の API や汎用の API を例に使っているところがありますが、Google Pay API for Passes でもコンセプトは同じです。

gzip を使用した圧縮

各リクエストに必要な帯域幅を削減するための簡単で便利な方法として、gzip 圧縮を有効にする方法があります。この方法では、結果を解凍するために CPU 時間が余分に必要になりますが、ネットワーク費用とのバランスを考えると、時間をかける価値は十分にあります。

gzip でエンコードされたレスポンスを受け取るには、2 つの準備作業が必要です。1 つは、Accept-Encoding ヘッダーを設定すること、もう 1 つは、ユーザー エージェントに gzip という文字列を追加することです。以下に、gzip 圧縮を有効にするための正しい形式の HTTP ヘッダーの例を示します。

Accept-Encoding: gzip
User-Agent: my program (gzip)

リソースの部分的な使用

API 呼び出しのパフォーマンスを向上させるためのもう 1 つの方法は、データの必要な部分のみを送受信することです。これにより、アプリケーションが不要なフィールドを転送、解析、格納することがなくなるため、ネットワーク、CPU、メモリなどのリソースをより効率的に使用できます。

部分リクエストには次の 2 種類があります。

  • 部分レスポンス - レスポンスに含めるフィールドを指定するリクエスト(fields リクエスト パラメータを使用)。
  • パッチ - 変更するフィールドだけを送信する更新リクエスト(HTTP の PATCH 動詞を使用)。

以下の各セクションで、部分リクエストの作成方法の詳細について説明します。

部分レスポンス

デフォルトで、サーバーはリクエストを処理した後リソース全体を返します。パフォーマンスを向上させるには、サーバーが必要なフィールドのみを送信し、それによって部分レスポンスを取得するように指定します。

部分レスポンスをリクエストするには、fields リクエスト パラメータを使用して取得するフィールドを指定します。このパラメータは、レスポンス データを返す任意のリクエストに指定できます。

fields パラメータはレスポンス データにのみ影響を与えます。送信する必要があるデータには影響を与えません。リソースを変更するときに送信するデータ量を削減するには、patch リクエストを使用します。

以下の例では、fields パラメータの使い方を汎用的な(架空の)Demo API を用いて示します。

シンプルなリクエスト: 次の HTTP GET リクエストでは fields パラメータを省略し、リソース全体を取得します。

https://www.googleapis.com/demo/v1

リソース全体のレスポンス: リソース全体のデータには次のフィールドが含まれます。簡潔にするため、他の多くのフィールドは省略されています。

{
  "kind": "demo",
  ...
  "items": [
  {
    "title": "First title",
    "comment": "First comment.",
    "characteristics": {
      "length": "short",
      "accuracy": "high",
      "followers": ["Jo", "Will"],
    },
    "status": "active",
    ...
  },
  {
    "title": "Second title",
    "comment": "Second comment.",
    "characteristics": {
      "length": "long",
      "accuracy": "medium"
      "followers": [ ],
    },
    "status": "pending",
    ...
  },
  ...
  ]
}

部分的レスポンスのリクエスト: 同じリソースに対する次のリクエストでは、fields パラメータを使用しているため、返されるデータが大幅に少なくなります。

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

部分レスポンス: 上記のリクエストに対するレスポンスとして、サーバーは、種類情報、各アイテムの HTML タイトルと長さのみを含む items 配列が格納されたレスポンスを返します。

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

レスポンスとして返されるのは、選択したフィールドとそれらのフィールドが属する親オブジェクトのみを含む JSON オブジェクトです。

次のセクションで、fields パラメータの形式の指定方法について説明し、さらにその後に、レスポンスとして返される具体的な内容について詳細に説明します。

fields パラメータの構文の概要

fields リクエスト パラメータの形式は、XPath の構文に基づいています。サポートされている構文を以下にまとめます。その後のセクションで追加の例を挙げて説明します。

  • 複数のフィールドを選択するには、カンマ区切りのリストを使用します。
  • フィールド a 内にネストされているフィールド b を選択するには a/b と指定します。b 内にネストされているフィールド c を選択するには a/b/c と指定します。

    例外: データラッパーを使用する API レスポンスは、data: { ... } のように data オブジェクト内にネストされているので、fields の指定に data を含めません。data/a/b のように、fields の指定に data オブジェクトを含めるとエラーになります。この場合は、fieldsa/b のように指定してください。

  • サブセレクタを使用して特定の配列またはオブジェクトのサブフィールドのセットをリクエストするには、式をかっこ ( ) で囲みます。

    たとえば、fields=items(id,author/email) と指定すると、items 配列内の各要素について、アイテム ID と作者のメールアドレスのみが返されます。サブフィールドは 1 つだけでもかまいません。たとえば、fields=items(id) と指定するのと fields=items/id と指定するのは同じことです。

  • 必要に応じて、フィールドの選択にワイルドカードを使用します。

    たとえば fields=items/pagemap/* とすると、pagemap のすべてのオブジェクトが選択されます。

fields パラメータのその他の使用例

以下にいくつか例を挙げて、fields パラメータ値がレスポンスに与える影響を説明します。

注: 他のすべてのクエリ パラメータの値と同じように、fields パラメータ値には URL エンコードを使用する必要があります。このドキュメントの例では、読みやすさを優先してエンコードを省略しています。

返すフィールドを特定する(フィールド選択を行う)
fields リクエスト パラメータ値はフィールドのカンマ区切りのリストであり、各フィールドは、レスポンスのルートからの相対位置で指定します。したがって、リスト オペレーションを実行する場合、レスポンスは通常、リソースの配列が格納されたコレクションです。単一のリソースを返すオペレーションを実行する場合、各フィールドは当該リソースからの相対位置で指定します。指定するフィールドが配列または配列の一部である場合は、その配列に含まれるすべての要素の指定した部分が返されます。

以下に、コレクション レベルの例を挙げます。
効果
items items 配列内のすべての要素を返します。各要素内のすべてのフィールドが含まれますが、その他のフィールドは含まれません。
etag,items etag フィールドと items 配列内のすべての要素を返します。
items/title items 配列内のすべての要素の title フィールドのみを返します。

ネストされたフィールドが返されると、レスポンスには、そのフィールドを含む親オブジェクトが毎回含まれます。親フィールド内のその他の子フィールドは、明示的に選択されていない限り含まれません。
context/facets/label context オブジェクトにネストされている facets 配列内のすべてのメンバーの label フィールドのみを返します。
items/pagemap/*/title items 配列内の各要素について、pagemap のすべての子オブジェクトの title フィールド(存在する場合)のみを返します。

以下に、リソースレベルの例を挙げます。
効果
title リクエストされたリソースの title フィールドを返します。
author/uri リクエストされたリソースの author オブジェクトの uri サブフィールドを返します。
links/*/href
links のすべての子オブジェクトの href フィールドを返します。
部分選択を使用して個々のフィールドの一部のみをリクエストする
デフォルトでは、リクエストに特定のフィールドを指定すると、サーバーは、オブジェクト全体または配列要素全体を返します。一方で、特定のサブフィールドのみを含むレスポンスを指定することもできます。それには、"( )" 部分選択構文を使用します。以下に例を示します。
効果
items(title,author/uri) items 配列内の各要素について、title と作者の uri の値のみを返します。

部分レスポンスの処理

fields クエリ パラメータを含む有効なリクエストを処理した後、サーバーは HTTP 200 OK ステータス コードとともにリクエストされたデータを返します。fields クエリ パラメータにエラーがある場合、またはパラメータが無効な場合、サーバーは、HTTP 400 Bad Request ステータス コードとフィールド選択の問題箇所を示すメッセージ(例: "Invalid field selection a/b")を返します。

前述の紹介セクションで取り上げた部分レスポンスの例を次に示します。このリクエストでは、どのフィールドを返すかを fields パラメータで指定しています。

https://www.googleapis.com/demo/v1?fields=kind,items(title,characteristics/length)

この場合の部分レスポンスは次のようになります。

200 OK
{
  "kind": "demo",
  "items": [{
    "title": "First title",
    "characteristics": {
      "length": "short"
    }
  }, {
    "title": "Second title",
    "characteristics": {
      "length": "long"
    }
  },
  ...
  ]
}

注: maxResultsnextPageToken など、データのページ設定のためのクエリ パラメータをサポートする API では、こうしたパラメータを使用して各クエリの結果を適切なサイズに制限してください。制限しないと、部分レスポンスによる効果が得られない可能性があります。

パッチ(部分更新)

リソースを変更する際にも不要なデータの送信を回避できます。変更する特定のフィールドについてのみ更新データを送信するには、HTTP の PATCH 動詞を使用します。このドキュメントで説明する PATCH 構文は、部分更新の古い実装 GData の PATCH 構文に比べて簡素化されています。

以下に示す簡単な例は、パッチを使用して、小さな更新を行うために送信する必要があるデータを最小化する方法を示しています。

以下の例では、簡単なパッチ リクエストを使用して、汎用的な(架空の)Demo API リソースのタイトルのみを更新する方法を示します。リソースには、コメント、特徴のセット、ステータスなど、多数のフィールドがありますが、このリクエストで変更するのは title フィールドのみですので、このフィールドだけを送信します。

PATCH https://www.googleapis.com/demo/v1/324
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "title": "New title"
}

レスポンス:

200 OK
{
  "title": "New title",
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "accuracy": "high",
    "followers": ["Jo", "Will"],
  },
  "status": "active",
  ...
}

サーバーからは、200 OK のステータス コードと更新されたリソースが完全な表現で返されます。パッチ リクエストに含まれていたのは title フィールドだけなので、更新前と異なるのはこのフィールドだけです。

注: 部分レスポンスfields パラメータをパッチと組み合わせて使用すれば、更新リクエストの効率をさらに高めることができます。パッチ リクエストはリクエストのサイズのみを小さくし、部分的レスポンスはレスポンスのサイズのみを小さくします。このことから、双方向のデータ送信量を削減するには、パッチ リクエストに fields パラメータを指定します。

パッチ リクエストの構文

パッチ リクエストの本文には、変更するリソース フィールドのみを含めます。フィールドを指定する際には、そのフィールドが存在する親オブジェクトも含める必要があります。これは、部分レスポンスで親オブジェクトが返されるのと同じです。親オブジェクトが存在する場合、送信した変更対象データは親オブジェクトのデータとマージされます。

  • 追加: フィールドを新しく追加するには、新規のフィールドとその値を指定します。
  • 変更: 既存のフィールドの値を変更するには、そのフィールドを指定して、新しい値を設定します。
  • 削除: フィールドを削除するには、そのフィールドを指定し null に設定します。例: "comment": null。オブジェクト全体を削除するには(オブジェクトが変更可能な場合)、オブジェクトに null を設定します。Java API クライアント ライブラリを使用する場合は、代わりに Data.NULL_STRING を使用します。詳細については、JSON null をご覧ください。

配列に関する注意: 配列を含むパッチ リクエストを実行すると、既存の配列が指定した配列で置換されます。配列内の要素を部分的に変更、追加、削除することはできません。

パッチを読み取り - 変更 - 書き込みサイクルで使用する

パッチ リクエストを使用する場合は、まず部分レスポンスで変更が必要なデータを取得することをおすすめします。これは、ETag を使用するリソースでは特に重要になります。このようなリソースは、If-Match HTTP ヘッダーに最新の ETag 値を指定しないと、正常に更新できないからです。データを取得したら、値の変更を設定し、変更部分のみを記述した部分的な表現をパッチ リクエストで送り返します。次の例は、demo リソースで ETag が使用されていることを前提としています。

GET https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token

部分レスポンスは次のようになります。

200 OK
{
  "etag": "ETagString"
  "title": "New title"
  "comment": "First comment.",
  "characteristics": {
    "length": "short",
    "level": "5",
    "followers": ["Jo", "Will"],
  }
}

以下のパッチ リクエストは、上記のレスポンスを使用しています。以下に示すとおり、このリクエストでは fields パラメータを使用して、パッチ レスポンスで返されるデータを制限しています。

PATCH https://www.googleapis.com/demo/v1/324?fields=etag,title,comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json
If-Match: "ETagString"
{
  "etag": "ETagString"
  "title": "",                  /* Clear the value of the title by setting it to the empty string. */
  "comment": null,              /* Delete the comment by replacing its value with null. */
  "characteristics": {
    "length": "short",
    "level": "10",              /* Modify the level value. */
    "followers": ["Jo", "Liz"], /* Replace the followers array to delete Will and add Liz. */
    "accuracy": "high"          /* Add a new characteristic. */
  },
}

サーバーからは、200 OK HTTP ステータス コードと、リソースの更新された部分が返されます。

200 OK
{
  "etag": "newETagString"
  "title": "",                 /* Title is cleared; deleted comment field is missing. */
  "characteristics": {
    "length": "short",
    "level": "10",             /* Value is updated.*/
    "followers": ["Jo" "Liz"], /* New follower Liz is present; deleted Will is missing. */
    "accuracy": "high"         /* New characteristic is present. */
  }
}

パッチ リクエストを直接作成する

一部のパッチ リクエストは、あらかじめ取得しておいたデータに基づいて作成する必要があります。たとえば、配列にアイテムを追加する必要があるが、既存の配列要素はそのまま維持する場合は、最初に既存のデータを取得します。同様に、API で ETag が使用されている場合は、リクエストで変更前の ETag 値を送信してからでないと、リソースを正常に更新できません。

注: HTTP の "If-Match: *" ヘッダーを使用して、ETag の使用中にパッチの実行を強制できます。その場合は、書き込む前に読み取る必要はありません。

上記のようなケースを除けば、事前に既存のデータを取得することなく、パッチ リクエストを直接作成できます。たとえば、フィールドを新しい値に更新するパッチ リクエストや新しいフィールドを追加するパッチ リクエストを簡単にセットアップできます。次に例を示します。

PATCH https://www.googleapis.com/demo/v1/324?fields=comment,characteristics
Authorization: Bearer your_auth_token
Content-Type: application/json

{
  "comment": "A new comment",
  "characteristics": {
    "volume": "loud",
    "accuracy": null
  }
}

このリクエストでは、comment フィールドに既存の値が存在する場合は新しい値で既存の値が上書きされ、そうでない場合は新しい値が設定されます。同様に、volume 特性が存在する場合はその値が上書きされ、存在しない場合は作成されます。accuracy フィールドが設定されている場合は削除されます。

パッチに対するレスポンスを処理する

有効なパッチ リクエストを処理すると、API は、200 OK HTTP レスポンス コードと、変更済みリソース全体を返します。API で ETag が使用されている場合、パッチ リクエストが正常に処理されていれば、サーバーは PUT の場合とまったく同様に ETag の値を更新します。

パッチ リクエストでは、リクエストから返されるデータの量を減らすために fields パラメータを使用しない限り、リソース全体が返されます。

パッチ リクエストの結果として新しいリソースの状態が構文上あるいは意味上無効になった場合、サーバーは 400 Bad Request または 422 Unprocessable Entity の HTTP ステータス コードを返し、リソースの状態は変化しません。たとえば、必須フィールドの値を削除しようとすると、サーバーはエラーを返します。

PATCH HTTP 動詞がサポートされていない場合の代替表記

ファイアウォールで HTTP の PATCH リクエストが許可されていない場合は、下に示すように HTTP の POST リクエストを実行し、オーバーライド ヘッダーを PATCH に設定します。

POST https://www.googleapis.com/...
X-HTTP-Method-Override: PATCH
...

パッチと更新の違い

HTTP の PUT 動詞を使用する更新リクエストのデータを送信する場合は、必須かオプションのどちらかのフィールドを送信するだけで済みます。サーバーによって設定されるフィールドの値を送信しても、無視されます。一見、これは部分更新と同じことをしているように思えますが、このアプローチには制限があります。HTTP 動詞 PUT を使用した更新では、必須パラメータを省略するとリクエストは失敗し、任意指定のパラメータを省略するとすでに設定済みのデータがクリアされてしまいます。

このため、パッチを使用したほうが安全です。パッチを使用すれば、変更するフィールドのデータを指定するだけで済みます。省略したフィールドがクリアされることはありません。このルールの唯一の例外は要素または配列の繰り返しです。繰り返される要素または配列をすべて省略すると、何も変更されません。繰り返される要素または配列のいずれかを指定すると、指定した内容で繰り返し全体が置換されます。

推奨されるリクエスト頻度の上限

API を呼び出せる頻度は制限される場合があります。リクエストは 1 秒あたり 20 回以下に抑えることをおすすめします。

Google Pay for Passes へのリクエストを一括処理する

リクエストを一括処理するで説明されているように、Google Pay for Passes API では API 呼び出しの一括処理をサポートしているため、クライアント側で必要とされる接続数を減らすことができます。

Google Pay for Passes ライブラリを使用してリクエストを一括処理するサンプルコードは次のとおりです。

Java

//Set up client batch
Walletobjects client = new Walletobjects.Builder(httpTransport, jsonFactory, credentials)
        .setApplicationName(config.getApplicationName())
        .build();
BatchRequest batch = client.batch();

//Pre insert class
Services.VerticalType verticalType = Services.VerticalType.OFFER;
String classUid = String.format("%s_CLASS_%s", verticalType.toString(), UUID.randomUUID().toString());
String classId = String.format("%s.%s" , config.getIssuerId(), classUid);
OfferClass theClass = rsc.makeOfferClassResource(classId);
client.offerclass().insert(theClass).execute();

// Batch 10 object inserts
for (int i = 0; i < 10; ++i){

    // your objectUid should be a hash based off of pass metadata, for the demo we will use pass-type_object_uniqueid
    String objectUid = String.format("%s_OBJECT_%s", verticalType.toString(), UUID.randomUUID().toString());

    // check Reference API for format of "id", for example offer:(https://developers.google.com/pay/passes/reference/v1/offerobject/insert).
    // Must be alphanumeric characters, ".", "_", or "-".
    String objectId = String.format("%s.%s", config.getIssuerId(), objectUid);
    OfferObject anObject = rsc.makeOfferObjectResource((classId), objectId);
    client.offerobject().insert(anObject).queue(batch, new JsonBatchCallback(){

        @Override
        public void onSuccess(OfferObject t, HttpHeaders responseHeaders) throws IOException {
            System.out.println("Success!");

        }

        @Override
        public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
            System.out.println("Error Message:" + e.getMessage());

        }
    });
}
//Execute batch
batch.execute();

Python

# Generating Credentials for auth request
credentials = service_account.Credentials.from_service_account_file(
    config.SERVICE_ACCOUNT_FILE,
        scopes = config.SCOPES)
authed_session = AuthorizedSession(credentials)
# Pre insert class
classUid = 'OFFER' + '_CLASS_'+ str(uuid.uuid4())
config.offer_class['id'] = '%s.%s' % (config.ISSUER_ID,classUid)
response = authed_session.post('https://www.googleapis.com/walletobjects/v1/offerClass?strict=true', json=config.offer_class)
# Build a batch of 10 object insert requests
data = ''
for i in range(9):
  objectUid = 'OFFER' + '_OBJECT_'+ str(uuid.uuid4())
  objectId = '%s.%s' % (config.ISSUER_ID,objectUid)
  data += '--batch_mybatch\n'
  data += 'Content-Type: application/json\n\n'
  data += 'POST /walletobjects/v1/offerObject/\n\n'
  config.offer_object['id'] = objectId
  data += json.dumps(config.offer_object) + '\n\n'
data += '--batch_mybatch--'
# execute batch
response = authed_session.post('https://walletobjects.googleapis.com/batch', data=data, headers={'Content-Type': 'multipart/mixed; boundary=batch_mybatch'})
print('Response:')
print(response.content.decode('UTF-8'))

PHP

// Create an Google_Client which facilitates REST call
$client = new Google_Client();
// do OAuth2.0 via service account file.
// See https://developers.google.com/api-client-library/php/auth/service-accounts#authorizingrequests
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . SERVICE_ACCOUNT_FILE); // for Google_Client() initialization for server-to-server
$client->useApplicationDefaultCredentials();
// Set application name.
$client->setApplicationName(APPLICATION_NAME);
// Set Api scopes.
$client->setScopes(array(SCOPES));
// Pre insert class
$vertical= "OFFER";
$service = new Google_Service_Walletobjects($client);
$classUid = $vertical."_CLASS_".uniqid('', true);
$classId = sprintf("%s.%s" , ISSUER_ID, $classUid);
$offerClass = ResourceDefinitions::makeOfferClassResource($classId);
$response = $service->offerclass->insert($offerClass);
//Initialize service
$service = new Google_Service_Walletobjects($client);
$service->getClient()->setUseBatch(true);
//Get batch
$batch = $service->createBatch();
// Batch 10 object inserts
for($i=0; $i<=10; $i++){
   $vertical= "OFFER";
   $objectUid= $vertical."_OBJECT_".uniqid('', true)
   $objectId = sprintf("%s.%s", ISSUER_ID, $objectUid);
   $offerObject = ResourceDefinitions::makeOfferObjectResource($classId, $objectId);
   $batch->add($service->offerobject->insert($offerObject));
}
// Execute batch
$results = $batch->execute();
var_export($results);