6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

App Engineで60秒制限を超えそうなリクエストを処理する方法

Last updated at Posted at 2019-04-25

お題

表題について、例えば、ある画面に、ボタン押下時の条件(対象期間等)に応じて、ある程度の容量を含む情報を外部から取得してZIPに固めてクライアント側にダウンロードさせるボタンがある。
App Engineではデフォルトで採用されるスケーリング方式だと1HTTPリクエストの処理期限は「60秒なので、上記のように時間のかかる処理に関してはタイムアウトを起こす可能性がある。
そういう時用にApp Engineには「タスクキュー(最大10分まで可)」というものが用意されていたのだけど、どうやら今は「Google Cloud Tasks」を使うようになっているらしい。
なんだけど、Cloud Tasksはまだ”ベータ版”なのでプロダクションで使うには心もとない。

じゃあ、どうしよう。
とりあえずオーダーだけをRDB等に永続化して、それをApp Engineのcronジョブで毎秒ウォッチしつつ、オーダーがあれば条件に合致する情報を取得してZIPに固めてGoogle Cloud Storageに格納。
元の画面側では、Cloud Storage上にZIPが格納されたかを定期的にポーリング(ないし、cronジョブからZIP格納完了通知を何かしらの媒体を介して取得)して監視し、できていればクライアント側にダウンロードさせる。
といった方法が考えられるけど、使うサービス増えるし、もうちょっと簡易的な方法でシンプルにやりたい。

という時に「Basicスケーリング設定の別サービスを作って特定のURLパスでリクエストが来た時だけ、別サービスにディスパッチする」という方法がとれるらしい。

screenshot-console.cloud.google.com-2019-04-25-08-49-34-337.png

前提

以下は済んだ上での作業。

  • GCPプロジェクトの作成
  • Cloud SDKのインストールと初期化・認証
  • App Engineの有効化

開発環境

# OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"

# Cloud SDK

$ gcloud version
Google Cloud SDK 242.0.0

# Golang

$ go version
go version go1.11.4 linux/amd64

実践

今回のソース全量は下記。
https://github.com/sky0621/try-gae-go111/tree/9da41fc38a0def2f703abaa11ebb2f074447c1d8/t01

必要なファイル群を用意

HTTPリクエスト処理ロジック

[main.go]
package main

import (
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/labstack/echo"
)

func main() {
	e := echo.New()
	g := e.Group("/api/v1")

	// デフォルトスケーリング設定用(ドキュメント上、1HTTPリクエストは「60秒」以内に終える必要があると記載あり。)
	g.GET("/automatic/min10", func(c echo.Context) error {
		time.Sleep(10 * time.Minute)
		return c.JSON(http.StatusOK, "OK")
	})

	// ベーシックスケーリング設定用(こちらは最大 24時間までOK)
	g.GET("/basic/min10", func(c echo.Context) error {
		time.Sleep(10 * time.Minute)
		return c.JSON(http.StatusOK, "OK")
	})

	// Google App Engineではデプロイ時にポートが決まり、環境変数「PORT」に接続ポートがセットされる。
	e.Logger.Fatal(e.Start(fmt.Sprintf(":%s", os.Getenv("PORT"))))
}

デフォルトスケーリング用のサービスデプロイ設定ファイル

Goのランタイム指定さえ合っていれば、どのURLパスも受け付ける。

[app.yaml]
runtime: go111

ベーシックスケーリング用のサービスデプロイ設定ファイル

URLパスとして「/api/v1/basic/」を含む場合用に「basic-service」という名前でサービス起動させる設定。

[app_basic_scaling.yaml]
runtime: go111

service: basic-service

handlers:
  - url: /api/v1/basic/.*
    script: auto

basic_scaling:
  max_instances: 25

指定のURLパスだけサービスをデフォルトから分ける設定ファイル

URLパスとして「/api/v1/basic/」を含む場合は「basic-service」に転送する。

[dispatch.yaml]
dispatch:
  - url: "*/api/v1/basic/*"
    service: basic-service

各種ファイルのデプロイ実行

デプロイ先の確認

$ gcloud config list
[compute]
region = asia-northeast1
zone = asia-northeast1-c
[core]
account = 【自分のアカウント】
disable_usage_reporting = False
project = 【自分のGCPプロジェクトID】

デフォルトサービスのデプロイ

$ gcloud app deploy app.yaml 
Services to deploy:

descriptor:      [/home/sky0621/work/src/go111/src/github.com/sky0621/try-gae-go111/t01/app.yaml]
source:          [/home/sky0621/work/src/go111/src/github.com/sky0621/try-gae-go111/t01]
target project:  [【自分のGCPプロジェクトID】]
target service:  [default]
target version:  [20190425t091940]
target url:      [http://【自分のGCPプロジェクトID】.appspot.com]


Do you want to continue (Y/n)?  y

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 7 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.                                                                                                                                                                           
Setting traffic split for service [default]...done.                                                                                                                                                          
Deployed service [default] to [http://【自分のGCPプロジェクトID】.appspot.com]

ベーシックスケーリングサービスのデプロイ

$ gcloud app deploy app_basic_scaling.yaml 
Services to deploy:

descriptor:      [/home/sky0621/work/src/go111/src/github.com/sky0621/try-gae-go111/t01/app_basic_scaling.yaml]
source:          [/home/sky0621/work/src/go111/src/github.com/sky0621/try-gae-go111/t01]
target project:  [【自分のGCPプロジェクトID】]
target service:  [basic-service]
target version:  [20190425t092233]
target url:      [http://basic-service.【自分のGCPプロジェクトID】.appspot.com]


Do you want to continue (Y/n)?  y

Beginning deployment of service [basic-service]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 0 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [basic-service]...done.                                                                                                                                                                     
Setting traffic split for service [basic-service]...⠹                                                                                                                                                        
Setting traffic split for service [basic-service]...⠼                                                                                                                                                        
Setting traffic split for service [basic-service]...⠶                                                                                                                                                        
Setting traffic split for service [basic-service]...done.                                                                                                                                                    
Deployed service [basic-service] to [http://basic-service.【自分のGCPプロジェクトID】.appspot.com]

ディスパッチ設定のデプロイ

$ gcloud app deploy dispatch.yaml 
Configurations to update:

descriptor:      [/home/sky0621/work/src/go111/src/github.com/sky0621/try-gae-go111/t01/dispatch.yaml]
type:            [routing rules]
target project:  [【自分のGCPプロジェクトID】]


Do you want to continue (Y/n)?  y

Updating config [dispatch]...done.                                                                                                                                                                           

Custom routings have been updated.

デプロイ結果の確認

デフォルトとベーシックそれぞれのサービスが出来てる。

screenshot-console.cloud.google.com-2019-04-25-09-27-14-670.png

動作検証

デフォルトサービスにアクセス

レスポンスがタイムアウトを表しているのかが判然とはしないが、”OK"は返っていない。

$ curl -m 900 -X GET \
>   https://【自分のGCPプロジェクトID】.appspot.com/api/v1/automatic/min10 \
>   -H 'Content-Type: application/json' \
>   -H 'cache-control: no-cache'

<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>500 Server Error</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Server Error</h1>
<h2>The server encountered an error and could not complete your request.<p>Please try again in 30 seconds.</h2>
<h2></h2>
</body></html>

ベーシックスケーリングサービスにアクセス

デフォルトサービスと同じ 10分 のスリープを設定した結果、"OK"が返った。

$ curl -m 900 -X GET \
>   https://【自分のGCPプロジェクトID】.appspot.com/api/v1/basic/min10 \
>   -H 'Content-Type: application/json' \
>   -H 'cache-control: no-cache'
"OK"

まとめ

うまくいった。
ただ、今回のソースは、1ファイルにどちらのサービス用のロジックも書いていて、それを「デフォルトサービス」と「ベーシックスケーリングサービス」それぞれに同じものをデプロイしている。
なので、本来は、デフォルトサービス用とベーシックスケーリングサービス用とで、それぞれに必要なURLパス処理ロジックだけが含まれるようにした方がいい。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?