Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

  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
  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 とか使う。普通に CDN のせるだけなら、static serving すれば良い。
  3. BlobStoreService#serve(BlobKey, HttpServletResponse) で AppEngine の URL でも画像をサーブ出来る
    • 多分リクエストをフォワードするだけだから負荷そんなない?
    • 少なくとも get blob するより億倍まし
  4. UploadOptionsgcsBucketName を指定すると、GCS に上げてくれる
    • UploadOptions.Builder.withGoogleStorageBucketName("grumblerapi.appspot.com");
    • 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 PermissionUSER allUsers READ と設定する
    • キーは BlobKey.getKeyString() でシリアライズ、new BlobKey(keyString) でデシリアライズ出来る。これは ServingUrlOptions.Builder.withBlobKey(blobKey) のために利用出来る。(GCS に上げていたら、GCS のキー指定でもいける)  * createUploadUrl はけっこう重い RPC   *

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

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

パフォーマンス

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

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

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away