googleapi
GoogleAppsAPI

GoogleAPIを高速化しよう

More than 3 years have passed since last update.

こちらの記事Google APIのBatch Requestの仕組みを理解する

でBatch Requestについて書きましたが、高速化する方法はそれだけではありません。

本記事ではそれを紹介していきます。


TL;DR

APIの高速化手法には主に以下の4つがある。


  • Batch Reuqest

  • gzip圧縮

  • fieldsパラメータを指定

  • GAE・GCE

バッチ処理等で大量データを登録するときは Batch Requestは必須。

画面などでAPIを単発で何度も実行しないといけない場合は gzipfieldsGAE・GCEを組み合わせることでレスポンスの向上に繋がる。


検証用データ

カレンダーの予定で解説します。

こんな感じの普通の予定を用意しときます。

Request

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc > result.json

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

Dload Upload Total Spent Left Speed
100 1373 100 1373 0 0 542 0 0:00:02 0:00:02 --:--:-- 543

Reponse


result.json

{

"kind": "calendar#event",
"etag": "\"2909287956280000\"",
"id": "t5f1vqmq89iu5t3jo6dapb4boc",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=dDVmMXZxbXE4OWl1NXQzam82ZGFwYjRib2MgYWRtaW5AaG93ZHlsaWtlcy5qcA",
"created": "2016-02-05T03:39:43.000Z",
"updated": "2016-02-05T03:46:18.140Z",
"summary": "テスト予定",
"description": "これはテスト用の予定です",
"location": "リソース名AAA",
"creator": {
"email": "admin@howdylikes.jp",
"displayName": "Tatsuya Nakano",
"self": true
},
"organizer": {
"email": "admin@howdylikes.jp",
"displayName": "Tatsuya Nakano",
"self": true
},
"start": {
"dateTime": "2016-02-05T09:00:00+09:00"
},
"end": {
"dateTime": "2016-02-05T11:00:00+09:00"
},
"iCalUID": "t5f1vqmq89iu5t3jo6dapb4boc@google.com",
"sequence": 0,
"attendees": [
{
"email": "admin@howdylikes.jp",
"displayName": "Tatsuya Nakano",
"organizer": true,
"self": true,
"responseStatus": "accepted"
},
{
"email": "howdylikes.jp_37393637323732362d313230@resource.calendar.google.com",
"displayName": "テスト用会議室A",
"resource": true,
"responseStatus": "needsAction"
}
],
"hangoutLink": "https://plus.google.com/hangouts/_/howdylikes.jp/admin?hceid=YWRtaW5AaG93ZHlsaWtlcy5qcA.t5f1vqmq89iu5t3jo6dapb4boc",
"reminders": {
"useDefault": true
}
}


gzip圧縮

HTTPには Accept-Encodingヘッダをつけてリクエストを送ることで圧縮して受け取ることが可能な機能があります。

似たようなことがGoogle APIでも可能です。

※この機能は最近のGoogle提供のライブラリならデフォルトONになっている可能性が高いです。(PHPはなっています、他は未確認)


使い方

Accept-Encoding Header

gzipを指定します

User-Agent Header

適当な文字列 (gzip)を指定します

ここが通常のHTTPと違います。

必ず括弧で囲ってgzipと記載する必要があります。

@リファレンスには書いていないのですが、deflateも指定可能みたいです。


使用例

curl -H "Authorization: Bearer $ACCESS_TOKEN" -H "Accept-Encoding: gzip" -H "User-Agent: my program (gzip)" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc > result.gzip

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

Dload Upload Total Spent Left Speed
100 677 0 677 0 0 644 0 0:00:01 0:00:01 --:--:-- 644

Receivedのところをみてください。

1373 -> 677 と減っているのがわかるかと思います。


fieldsパラメータを指定

検証用データのレスポンスを見てもらえるとわかると思うのですが、とにかく項目がおおいです。

Google APIは原則全ての値を返すため肥大化してしまうのです。

SQLでいうとSELECT *を指定しているようなものですね。

※Drive API v3のようにで標準で全てを返さない特殊なものもあります。

fieldsパラメータを指定し、必要な項目に絞ることでサイズを小さくすることが可能です。

またこのfieldsパラメータは取得時だけではなく登録時や更新時のレスポンスにも有効です。


使い方&使用例

getパラメータにfields=XXXの形で指定します。


通常の指定

fields=creator

普通に項目名を指定するだけです

指定した項目の子階層も含めて全て取得されます

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc?fields=creator > result.json


result.json

{

"creator": {
"email": "admin@howdylikes.jp",
"displayName": "Tatsuya Nakano",
"self": true
}
}


深い階層を指定

スラッシュで繋ぎます

fields=creator/email

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc?fields=creator/email > result.json


result.json

{

"creator": {
"email": "admin@howdylikes.jp"
}
}


複数指定

カンマで区切ります

fields=id,etag,kind

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc?fields=id,etag,kind > result.json


result.json

{

"kind": "calendar#event",
"etag": "\"2909287956280000\"",
"id": "t5f1vqmq89iu5t3jo6dapb4boc"
}


複数指定の省略形

attendees/emailattendees/responseStatusを指定したい場合

fields=attendees/email,attendees/responseStatusとなりますが

このままだと冗長なので括弧で囲むことで以下のように書くことが可能です。

fields=attendees(email,responseStatus)

curl -H "Authorization: Bearer $ACCESS_TOKEN" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events/t5f1vqmq89iu5t3jo6dapb4boc?fields="attendees(email,responseStatus)" > result.json


result.json

{

"attendees": [
{
"email": "admin@howdylikes.jp",
"responseStatus": "accepted"
},
{
"email": "howdylikes.jp_37393637323732362d313230@resource.calendar.google.com",
"responseStatus": "needsAction"
}
]
}



おまけ 全てに一致

アスタリスクを孫階層以下に指定することができます。

使うとしたら予定の一覧取得時にitems/*/dateTimeitems/start/dateTimeitems/end/dateTimeが取れる感じになります。

必要性が感じないので例はだしません。


GAE,GCEを使う

Googleネットワーク内でAPI発行することになるため速度向上になります。


実際の速度比較

では実際にどれくらい変わるのか試してみます。

同じ予定取得を何度も流しても面白みがないので、検証内容はカレンダーの予定登録を200件行います。


通常リクエスト

シェルを作って順次流していきます。


requet.sh

#!bin/sh

date

for i in `seq 1 200`
do
curl -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events --data-binary @databinary.txt >> result.json
done

date



databinary.txt

{

"end": {
"dateTime": "2016-02-02T13:00:00+09:00"
},
"start": {
"dateTime": "2016-02-02T12:00:00+09:00"
},
"summary": "通常のリクエストで登録"
}

sh ./request.sh

Tue Feb  9 06:09:03 JST 2016

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1104 0 938 100 166 1272 225 --:--:-- --:--:-- --:--:-- 1271
...
...
...
Tue Feb 9 06:11:31 JST 2016

148秒かかりました。

1件あたり1秒強ですね。大量データがあると辛い数字です。


Batch Request

curl https://www.googleapis.com/batch -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: multipart/mixed; boundary=BOUNDARY" --data-binary @databinary.txt > result.json


databinary.txt

--BOUNDARY

Content-Type: application/http
Content-ID: 1

POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events
Content-type: application/json

{
"end": {
"dateTime": "2016-02-01T13:00:00+09:00"
},
"start": {
"dateTime": "2016-02-01T12:00:00+09:00"
},
"summary": "BatchRequestで登録"
}

--BOUNDARY
Content-Type: application/http
Content-ID: 2

POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events
Content-type: application/json
...
...
...
--BOUNDARY


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

Dload Upload Total Spent Left Speed
100 307k 0 243k 100 65547 15439 4058 0:00:16 0:00:16 --:--:-- 46163

16秒です。

さすがにBatch Requestは速いですね。

Google内部の処理時間はほとんどかかっておらずHTTP通信を何度も行うのが圧倒的に大きいのがわかりますね。


gzip圧縮

通常のリクエスト同様シェルを作って順次流していきます。


requet.sh

#!bin/sh

date

for i in `seq 1 200`
do
curl -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -H "Accept-Encoding: gzip" -H "User-Agent: my program (gzip)" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events --data-binary @databinary.txt >> result.json
done

date



databinary.txt

{

"end": {
"dateTime": "2016-02-03T13:00:00+09:00"
},
"start": {
"dateTime": "2016-02-03T12:00:00+09:00"
},
"summary": "gzipリクエストで登録"
}

sh ./request.sh

Tue Feb  9 06:05:04 JST 2016

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 667 0 506 100 161 1023 325 --:--:-- --:--:-- --:--:-- 1024
...
...
...
Tue Feb 9 06:07:15 JST 2016

131秒かかりました。

若干早くなっているようです。


fields使用

idだけ取得することとします。


requet.sh

#!bin/sh

date

for i in `seq 1 200`
do
curl -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events?fields=id --data-binary @databinary.txt >> result.json
done

date



databinary.txt

{

"end": {
"dateTime": "2016-02-04T13:00:00+09:00"
},
"start": {
"dateTime": "2016-02-04T12:00:00+09:00"
},
"summary": "fieldsのリクエストで登録"
}

Tue Feb  9 06:21:50 JST 2016

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 206 0 40 100 166 64 268 --:--:-- --:--:-- --:--:-- 268
...
...
...
Tue Feb 9 06:23:56 JST 2016

126秒かかりました。

Receivedが40とgzipを使った時よりはるかに小さくなっていますがそこまで差はないですね。


GCE使用

f1-micro、asia-east1-bのインスタンスで実行しました。

リクエスト内容は通常リクエストと同じため省略。

Mon Feb  8 21:29:27 UTC 2016

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1098 0 935 100 163 2250 392 --:--:-- --:--:-- --:--:-- 2253
...
...
...
Mon Feb 8 21:31:13 UTC 2016

106秒かかりました

なにも考ずに早くなるのはうれしいですね。


全部入り

今までやった高速化を全て入れてやってみます。

curl https://www.googleapis.com/batch -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: multipart/mixed; boundary=BOUNDARY" -H "Accept-Encoding: gzip" -H "User-Agent: my program (gzip)" --data-binary @databinary.txt > result.json


databinary.txt

--BOUNDARY

Content-Type: application/http
Content-ID: 1

POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events?fields=id
Content-type: application/json

{
"end": {
"dateTime": "2016-02-06T13:00:00+09:00"
},
"start": {
"dateTime": "2016-02-06T12:00:00+09:00"
},
"summary": "全部入りで登録"
}

--BOUNDARY
Content-Type: application/http
Content-ID: 2

POST https://www.googleapis.com/calendar/v3/calendars/admin@howdylikes.jp/events?fields=id
Content-type: application/json
...
...
...
--BOUNDARY


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

Dload Upload Total Spent Left Speed
100 72657 0 5107 100 67550 339 4487 0:00:15 0:00:15 --:--:-- 0

15秒かかりました。


まとめ

予定を200件登録する時間

種別
時間

通常
148秒

Batch Request
16秒

gzip
131秒

fields(idのみ)
126秒

GCE
106秒

全部入り
15秒

バッチ処理等で大量データを登録するときは Batch Requestは必須ですね。天と地の差があります。

画面などでAPIを単発で何度も実行しないといけない場合は

gzipfieldsGAE・GCEを組み合わせればレスポンスの向上に大きく繋がりますね。


余談

本件検証していてGoogle APIは実行時間帯によって速度の差がかなり違いました。

負荷の少ない時間帯とかあるんでしょうかね。