GoogleAPIを高速化しよう


こちらの記事[Google APIのBatch Requestの仕組みを理解する](http://qiita.com/howdy39/items/a598f9fcd2fb1d3933f2)
でBatch Requestについて書きましたが、高速化する方法はそれだけではありません。
本記事ではそれを紹介していきます。

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

* Batch Reuqest
* gzip圧縮
* fieldsパラメータを指定
* GAE・GCE

バッチ処理等で大量データを登録するときは **Batch Request**は必須。
画面などでAPIを単発で何度も実行しないといけない場合は **gzip**、 **fields**、 **GAE・GCE**を組み合わせることでレスポンスの向上に繋がる。


## 検証用データ
カレンダーの予定で解説します。
こんな感じの普通の予定を用意しときます。

**Request**

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


```console
  % 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**

```json: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も指定可能みたいです。


## 使用例

```console
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
```

```console
  % 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``
普通に項目名を指定するだけです
指定した項目の子階層も含めて全て取得されます

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

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


###深い階層を指定
スラッシュで繋ぎます
``fields=creator/email``

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

```json:result.json
{
 "creator": {
  "email": "admin@howdylikes.jp"
 }
}
```


###複数指定
カンマで区切ります
``fields=id,etag,kind``

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

```json:result.json
{
 "kind": "calendar#event",
 "etag": "\"2909287956280000\"",
 "id": "t5f1vqmq89iu5t3jo6dapb4boc"
}
```

###複数指定の省略形
``attendees/email``と``attendees/responseStatus``を指定したい場合
``fields=attendees/email,attendees/responseStatus ``となりますが
このままだと冗長なので括弧で囲むことで以下のように書くことが可能です。
``fields=attendees(email,responseStatus)``

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

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

```


###おまけ 全てに一致
アスタリスクを孫階層以下に指定することができます。
使うとしたら予定の一覧取得時に``items/*/dateTime``で``items/start/dateTime``と``items/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": "通常のリクエストで登録"
}
```

```console
sh ./request.sh
```

```console
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

```console
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
```

```console
  % 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         
```

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

```console
sh ./request.sh
```

```console
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         
```


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

```console
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のインスタンスで実行しました。

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

```console
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秒**かかりました
なにも考ずに早くなるのはうれしいですね。



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

```console
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
```

```console
  % 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を単発で何度も実行しないといけない場合は
**gzip**、 **fields**、 **GAE・GCE**を組み合わせればレスポンスの向上に大きく繋がりますね。

## 余談
本件検証していてGoogle APIは実行時間帯によって速度の差がかなり違いました。
負荷の少ない時間帯とかあるんでしょうかね。