AppEngine の createUploadUrl(), getServingUrl() について調査した記録

メモ書きそのままですが…

1. `BlobStoreService#createUploadUrl(String)` でアップロード用の URL が作られる
  * URL は `https://{id}.appspot.com/_ah/upload/XXXXXXX` みたいな
  * `multipart/form-data` でバイナリを送る。キーは自由だしいくつも送ってもいい。
  * AppEngine は、リクエストがインスタンスに来る前に全てのバイナリを BlobStore にため、バイナリを除いたリクエストを `createUploadUrl(String)` に渡した URL にフォワードする（インスタンスに負荷無い）
  * フォワードされたリクエストからは、`getUploads()` で Map が取得出来る。Map のキーはマルチパートのキーであり、値は `BlobKey` である
    * フォワード先のレスポンスをクライアントにそのまま返す。（Status, Body, Header）
    * 400 番台とかだと失敗してるはずだが別にリトライして叩いたりはしない。
  * **アップロードセッションはそのうち切れる。多分10分くらい。** `400 Bad Request` となる。
  * 同じアップロード URL で何回でもアップロードでき、ファイルも上書きされる事は無い。
    * でも、ローカルだと何故か出来ない。（`404 Session Not Found` となる）
  * createUploadUrl はかなり軽い RPC
    * ![](http://i.gyazo.com/4f3e4daa3b63558bb037b3707acfbb60.png)
2. `ImagesService#getServingUrl()` で取得用の URL を取得出来る
  * URL は `https://lh5.ggpht.com/rCGu26RVMiLkEepYZhhfmxMxsKrb29wUFGfqirbErbNvmLqVlr7mFvXILGQrSZ_u53D4OpMSh_wN3lUoh224RhWWFJlFQA` みたいな
  * http でも https でも
  * https なら QUIC が有効になっている
  * **動画には使えません。getServingUrl 呼び出し時に ImagesServiceFailureException が発生します**
  * URL は充分に長くて、推定不可能
  * Google のリアルタイム画像リサイズインフラでハンドリングしてるみたい
  * `=s500` などと付ける事で動的にリサイズしてくれる
    * `=w300` と付けると width が 300 以内になるように
    * `=s300` と付けると 300x300 に収まるように。（アスペクト比を保って）
    * `=s200-c` などと付けると、200x200 にリサイズ＆クロップされます
      * `=w200-c` とやるとおかしくなる。必ず `s` を使うように。
    * 間違った文法、あるいは許容サイズを越えると 404 となる (変換に指定出来るサイズは {x}1600 まで)
  * もちろんインスタンスに負荷ない
  * **アップロードされた画像を配信するのに非常に便利。**
    * ただし、URL が File Path の構造を保存しないので、固定 Asset を動的配信したかったら [imgix](http://www.imgix.com/) とか使う。普通に CDN のせるだけなら、static serving すれば良い。
3. `BlobStoreService#serve(BlobKey, HttpServletResponse)` で AppEngine の URL でも画像をサーブ出来る
  * 多分リクエストをフォワードするだけだから負荷そんなない？
  * 少なくとも get blob するより億倍まし
4. `UploadOptions` に `gcsBucketName` を指定すると、GCS に上げてくれる
  * `UploadOptions.Builder.withGoogleStorageBucketName("grumblerapi.appspot.com");`
  * ![](http://i.gyazo.com/f64aac992a4775cc1e36bafc2b3cd739.png)
  * GCS のファイル名を取得するには、`BlobStoreService#getFileInfos(HttpRequest)` から `FileInfo.getGsObjectName()` すれば良い。
    * サンプル: `/gs/grumblerapi.appspot.com/L2FwcGhvc3RpbmdfcHJvZC9ibG9icy9BRW5CMlVvQVR5VEVIaHdCTk9SN1JSN21IR3Z4M0VaTFZudWtscTNQQ1RpMi1lWnhWVlJIZTU1RlNma0prUmNGemN5RVpNWGp3UWJEbTkxLXVYUXhaMkVRTmkyclR2Y2Ztdy5vbU1HRjBEemh5VXZoSVZ`
    * `/gs/{bucket_name}/{random_name}` となっている。
    * `/gs/` を `https://storage.googleapis.com/` と置換するだけで URL に出来ます。
    * ACL を適切に設定しておくことを忘れずに。（バケット全体の PUBLIC READ を許可しておきましょう！）
    * `Edit Object Default Permission` で `USER` `allUsers` `READ` と設定する
      * ![](http://i.gyazo.com/da519bb07bf647c22239fbd4a59e7294.png)
  * キーは `BlobKey.getKeyString()` でシリアライズ、`new BlobKey(keyString)` でデシリアライズ出来る。これは `ServingUrlOptions.Builder.withBlobKey(blobKey)` のために利用出来る。（GCS に上げていたら、GCS のキー指定でもいける）
　* createUploadUrl はけっこう重い RPC
　  * ![](http://i.gyazo.com/0160b6dbad22fde95d752e8f95c09367.png)

### アップロードされた画像のサンプル

* http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf
* ![](http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf)

* http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=w200
* ![](http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=w200)

* http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=w30
* ![](http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=w30)

* http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=s200-c
* ![](http://lh3.googleusercontent.com/-F6FaSMPNC2fXGdIB2QG3NWiJtJaA_1emC-Sl-dYVIZ6r4Ji3uOYt1ynAdvYEClgFcChVqymCyIo4p5P85En4Bnf=s200-c)

* http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s1200
* ![](http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s1200)

* http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s200
* ![](http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s200)

**G+で使ってるのと同じ動的リサイズ画像配信システムです**

### パフォーマンス

* createUploadUrl を経由して行なったアップロードは**全くメモリを消費しません**。handlerに渡るリクエストでは`multipart/form-data` に付いてきているファイルだけカットされ、BlobKeyを得る為の軽い情報にすげ変わっているようです。
  * アップロードファイルの保管自体は GAE のインフラ層で行なわれます。（BlobStore or GCS）
  * 大量＆重量級のファイルアップロードを捌くのは至難の技。AppEngine ではこの難問を Google インフラ層で請け負ってくれる createUploadUrl を用意している。メモリ不足を避けるためには、createUploadUrl を経由すると全然メモリ使わない。
* getServingUrl で得た URL は死ぬほどパフォーマンス良いです。早過ぎてウケる。
* 2.4MB のファイルをアップロードしても、AP サーバに来るリクエストは 1,257 bytes
  * ![](http://i.gyazo.com/b0ac7f1f8ea2ae94dd3f1a66da48d9d5.png)

#### getServingUrl（一度アクセスした画像）

```
ab -n 100 -c 10 http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ\=s200
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking lh3.googleusercontent.com (be patient).....done


Server Software:        fife
Server Hostname:        lh3.googleusercontent.com
Server Port:            80

Document Path:          /zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s200
Document Length:        6410 bytes

Concurrency Level:      10
Time taken for tests:   0.289 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      682420 bytes
HTML transferred:       641000 bytes
Requests per second:    345.87 [#/sec] (mean)
Time per request:       28.913 [ms] (mean)
Time per request:       2.891 [ms] (mean, across all concurrent requests)
Transfer rate:          2304.97 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        9   11   0.9     11      13
Processing:    11   17  11.2     14      55
Waiting:       10   17  11.2     13      54
Total:         21   28  11.2     25      67

Percentage of the requests served within a certain time (ms)
  50%     25
  66%     25
  75%     26
  80%     27
  90%     57
  95%     61
  98%     64
  99%     67
 100%     67 (longest request)
```

#### getServingUrl（アクセスしたことのない画像）

```
ab http://lh3.googleusercontent.com/zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ\=s1000
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking lh3.googleusercontent.com (be patient).....done


Server Software:        fife
Server Hostname:        lh3.googleusercontent.com
Server Port:            80

Document Path:          /zT7gKuu2IUrXxUMW6gKDYYyRQyW_8_rsbBuDmUqaUkjiCTSUeSTTmdl-xmzQcH7827Ku-9Mz14KsavJWpDMTAQ=s1000
Document Length:        83772 bytes

Concurrency Level:      1
Time taken for tests:   0.345 seconds
Complete requests:      1
Failed requests:        0
Write errors:           0
Total transferred:      84180 bytes
HTML transferred:       83772 bytes
Requests per second:    2.90 [#/sec] (mean)
Time per request:       344.945 [ms] (mean)
Time per request:       344.945 [ms] (mean, across all concurrent requests)
Transfer rate:          238.32 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       10   10   0.0     10      10
Processing:   335  335   0.0    335     335
Waiting:      308  308   0.0    308     308
```

### 参考 URL

- https://cloud.google.com/appengine/docs/java/images/
- http://blog.a-know.me/entry/20120320/1332211908 (CreateUploadUrl の Expire について)
- そのた色々

gist: https://gist.github.com/kaiinui/8aefcd534ad44cce0933