こんにちは、バックエンドグループの冨永と申します。
弊社のタクシーアプリ『GO』にはGAE Standard 1st Go1.11を使用しているAPIサーバーがあり、このサーバーの2nd Go1.12以上への移行を計画しています。
他サイトでもGAE 2ndへの移行記事が増えてきましたが、実際に取り組んでみると新たに得られた知見がありましたので共有したいと思います。
移行計画
1stのGo1.11は1st用のappengineパッケージと2nd対応のパッケージが併用できるため、少しずつ置き換えていくことができます。タイトルが 移行準備
となっているのはこれが理由です。
現在は一部のAPI endpointだけ移行しており、残りのendpointを動作確認しつつ移行していく予定です。
urlfetchの置き換え
net/httpのClientに置き換えるだけですが、デフォルトのtimeoutが設定されていないためurlfetchのデフォルトtimeoutに依存しているコードがある場合はtimeoutを設定してください。
CloudTasks
基本的には公式のサンプルコードの通りでいいのですが、クライアントのキャッシュなどいくつか必要な実装を追加しました。
GAE versionの指定
google.golang.org/appengine/taskqueue
ではtaskのhostを設定することで、タスクを処理するサーバーversionを指定することができます。
Blue-Green Deploymentでは古いサーバーversionで作成したタスクの処理は新しいサーバーではなく古いサーバーで実行して欲しいため、以下のようにtaskspb.AppEngineRouting
を作成します。
appEngineRouting := &taskspb.AppEngineRouting{ Service: os.Getenv("GAE_SERVICE"), Version: os.Getenv("GAE_VERSION"), } req := &taskspb.CreateTaskRequest{ Parent: queuePath, Task: &taskspb.Task{ MessageType: &taskspb.Task_AppEngineHttpRequest{ AppEngineHttpRequest: &taskspb.AppEngineHttpRequest{ Headers: map[string]string{ "Content-Type": "application/json", }, AppEngineRouting: appEngineRouting, HttpMethod: taskspb.HttpMethod_POST, RelativeUri: "/task_handler", }, }, }, }
レイテンシの悪化への対応
GAE 1stではGAEの内部APIを使用しているためタスク作成処理のレスポンスは数msで受け取れていましたが、GAE 2ndでは外部のCloudTasksのAPIを使用するため50ms~150msほどかかるようになりました。regionはasia-northeast1で作成しており、GCP Supportから想定の範囲内のレイテンシであるとの回答を頂いています。 弊社ではバッチ処理で高頻度にtaskを作成している実装があり、バッチ完了までの時間が大幅に伸びてしまうため、その箇所についてはtaskの数自体を減らす工夫やgoroutineの使用を予定しています。
Datastore
Datastoreのclientライブラリはgoonからboomへ移行中です。冒頭で述べました通り、少しずつ移行するために併用している状態です。 goonのデフォルト機能にクエリ結果の透過的なキャッシュ機能がありますが、弊社の2ndへの移行対象のプロダクトではキャッシュ対象になるクエリはほとんど使われていないため、キャッシュについては考慮せず、そのままboomへの移行を行いました。
具体的なコード変更はBoom replacing goonが参考になります。
Memorystore for Memcached
Auto Discovery
bradfitz/gomemcache
など通常のmemcachedクライアントを使用するとnodeを増減させる度に手動でクライアントに設定したnodeのIPリストの更新が必要になります。そのため 検出endpointから定期的にnodeのIPリストを取得するために google/gomemcache
クライアントを使います。これは bradfitz/gomemcache
のforkなのですが、インストール時に問題が発生するためgo.modのreplaceで回避しました。
replace github.com/bradfitz/gomemcache => github.com/google/gomemcache v0.0.0-20200326162346-94281991662a
間違ってAuto Discoveryに対応していないmemcachedクライアントで検出endpointのIPに接続してもエラーにはならないため注意してください。 nodeへのset,getは成功しますが、正しくkeyが分散しないためキャッシュの不整合が発生します。
.circleci/config.yml
memcachedの起動はテストを実行するコンテナの中で行い、Datastoreエミューレータは以下のように別で起動しています。
docker: - image: gcr.io/google.com/cloudsdktool/cloud-sdk:292.0.0 command: ['gcloud', '--project=test-project-id', 'beta', 'emulators', 'datastore', 'start', '--host-port=localhost:8088', '--no-store-on-disk', '--consistency=1.0']
gcloudコマンドのversionに注意
古いversionのgcloudでdeployするとapp.yaml内の vpc_access_connector
が削除された状態でデプロイされMemorystoreに接続できなくなります。
確認した限りではGoogle Cloud SDK 263.0.0 は対応しておらず、Google Cloud SDK 292.0.0では正しく動作しました。
おわりに
GAE 1stがとても優秀だったため、他社もまだまだ1stのままのところが多いと思います。しかしGoのバージョンが1.11のままだと周辺のエコシステムに置いていかれるため、積極的にupdateしていきたいと思います。