20200319のGoに関する記事は3件です。

Goのwrap結局何がうれしいんや

goのwrap結局何が嬉しいのか

先日go1.13から実装されたwrapについての記事を書いたのですが,自分で例とかも書きながら、これって本当にうれしいんか?というか何が嬉しいんやろという疑問がありました。そこで、自分の社内の先輩に嬉しいところを聞いてみたところ、なぁぁぁぁるほどぉぉぉというお答えを頂けたので、自分なりにまとめて書いてみようと思います。

嬉しいところ

僕が前の記事でエラーをラップすると嬉しいこととして、下位の関数で発生したえらーを上位の関数にシンプルな形でエラーを返せるというような説明をしました。これは本当に嬉しいことでは無かったです。書きながら自分でも疑問だったのですが、結局unwrapするんだしwrapする必要あるのか?ということです。
本当に嬉しかった事としては、もし、下位の関数で定義したエラーを上位の関数に返してしまうと、上位の関数が下位の関数に依存してしまう事になります。ここで、エラーを上位の関数が定義したエラーでwrapする事で依存を防げるのですね。これが本当は嬉しい事だったわけです。

実装してみる

 具体的にどうやって、実装するんや、という話です。
例えば、databaseとの接続部で出たエラーをcontroller層に返す時を考えます。まず、database側でこんなエラーを実装したとします

dababase.go
type DataBaseError struct {
  err error
}

func (e dataBaseError)Error() string {
 return e.err.Error()
}

これをwrapせずにコントローラー層に返してしまうと、controller層が外側のdatabase層のDataBaseErrorを知ることになり、依存の方向が逆向きになってしまいます。(dao→controllerの依存のみ許します)この DataBaseError をWrapしてcontroller層に返すことで、 *fmt.WrrapError 型として返るのでcontroller層はDataBaseErrorのことを知らずにdaoからのエラーを扱えるようになります。
。。。。( ^ω^ )
ということを考えながら、自分で言ってて、これUnWrapできんやん、むしろ、AsとかIsとかをcontroller層でやろうとしたら、 errors.Is(err, dao.DataBaseError) とか書くことになって、結局依存解消できんやんとか自分で突っ込んでしまいました。どうしたもんか

というわけで、自分なりにもうちょっと考えてみる

色々と調べてみて、考えてみたのですが、確かに、unwrapしない限りは下位の層で定義したエラーを渡さなくて済むのですが、一番嬉しいこととしては自分で定義しエラーを扱う時に、複雑なものなどが必要になったら、wrapしてあげると綺麗なわかりやすい形で渡せることなのかな、という結論に行き着きました。また、そう言った使い方をしている例が多かったように思います。(大半はラップの仕方だけで、嬉しいことはあまり書いてなかった、、)。
もしかしたら、うまく実装できればunwrapしても依存しない形を作れるのかなーとか思ったので、あったらコメントとかしていただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GAEでGo1.9からGo1.11へ移行したらストレージでマルチリージョンのバケットが使用されていた

*メモ的な感じで記載しています。

2020年3月2日に2020年2月請求として¥3円が発生。
何事かと思い請求内容を確認したところ、ストレージのマルチリージョンに対して請求があった。
今年の1月までは無料トライアル期間であったため気がついていなかった。

どうやら移行作業を行なった時からストレージのマルチジーリョンのバケットが使用されていることがわかった。

当時(昨年末)、Go1.9からGo1.11へ移行するため試行錯誤し、やっとデプロイできる様になったので、
問題なくデプロイできるか確認するために何度もデプロイを行なった。
(Mac のターミナルで gcloud app deploy を実行)
結果、ビルド?されたファイルが多く作成され、数百MBも使用していた。

調べた結果、次のことがわかった。
・デプロイするとローカルにあるソースがストレージ(標準?のリージョン)へアップロードされる。
・次に、Cloud Build サービスを使用してコンテナイメージを作成される。
・コンテナイメージが App Engine へ適用される。

ん?Cloud Build サービス?

Google Cloud ドキュメントを確認しました。

  • アプリケーションのデプロイ
    デプロイを行うと、Cloud Build サービスが App Engine スタンダード環境で実行するアプリケーションのコンテナイメージを作成します。

  • ビルドイメージの管理
    新しいバージョンをデプロイするたびに、Cloud Build サービスを使用してコンテナイメージが作成され、Container Registry の app-engine フォルダに保存されます。
    自動的に削除されないので、保存容量の上限に達する前に不要なイメージは削除するとよいでしょう。

  • ローカル イメージにレジストリ名でタグ付けする
    イメージを新しいホスト名でレジストリに push すると、Container Registry では、指定されたマルチリージョン内にストレージバケットを作成します。

ここです!
「マルチリージョン内にストレージバケットを作成します。」って書いてある!
ビルドイメージの管理には「自動的に削除されないので」と書いてある!

確認のために何度もデプロイをした結果、コンテナイメージファイルが大量に作成され、数百MBも使用してしまったようです。
リージョンが asia-northeast1 でしたので、us-east1 で新規に作成したけど、マルチリージョンが使用されていました。

とりあえず、古いファイルを削除して料金がどの様になるか様子を見ています。

請求が発生しない方法?など、知っている方がいましたら教えてください。


気になる点

App Engineの標準環境であるPython 3、Java 11、PHP 7、Go 1.11 / 1.12、Ruby、NodeJSランタイム、およびApp Engineフレキシブル環境は変更されていません。これらのランタイムは常にCloud Buildを使用しており、支払い手段が​​必要でした。(翻訳)

と書いてあるので、第二世代のランタイムは全てデプロイ時にマルチリージョンのバケットが使われるのだろうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFrontは同じURL+QueryStringでもキャッシュ応答しないケースがある

はじめに

あるAPIサービスが、CloudFront + API Gateway + Lambda という構成で稼働しており
CloudFrontは、URL+QueryStringごとにキャッシュし、応答速度をあげるために使用しています。

事象

このAPIサービスには、Go/PHPそれぞれで実装されたプログラムがHTTPリクエストを送ります。
CloudFrontの設定は以下の通り。(関連設定のみ)

設定項目 設定値
Cache Based on Selected Request Headers None
Object Caching Customize
MinTTL:0
MaxTTL: 86400
DefTTL: 86400
Forward Cookies None
Query String Forwarding and Caching Forward all, cache based on all
Compress Objects Automatically No

Go/PHPそれぞれから同じ URL+QueryString でリクエストを送信した場合は、2回目のリクエストは
キャッシュHitする想定でしたが、なぜかMissになります。(X-Cacheヘッダで確認)

原因

Go実装のプログラムでは 標準のnet/httpパッケージを使ってリクエストを送信しています。
net/httpのhttpクライアントはレスポンスがgzip圧縮されていても自動的に展開してくれるため
デフォルトでAccept-Encoding: gzipヘッダが付きます。
CloudFrontは、このAccept-Encodingヘッダの有無でキャッシュが別管理になるため、
同じURL+QueryStringであっても、ヘッダ有無が異なれば再度オリジンアクセスする、という動作になります。
これは、レスポンスが圧縮されているかに関わらず上記の動作になりました。

コメントで指摘あったので追記)
Vary: Accept-EncodingがレスポンスヘッダにあればAccept-Encodingヘッダ有無で
キャッシュが別になるのは理解できますが、今回使用しているオリジンは、レスポンスを圧縮しない、かつ
Varyヘッダを返さない仕様です。

DefaultClientでリクエスト送信 実装例

main.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    url := "http://example.com/"
    req, err := http.NewRequest(http.MethodGet, url, nil)
    if err != nil {
        fmt.Errorf("%v", err)
        return
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Errorf("%v", err)
        return
    }
    defer resp.Body.Close()
}

今回CloudFront設定を素直に?以下のように解釈してたので発見が遅れました
* リクエストヘッダでキャッシュ判定しない
* URL+QueryStringごとにキャッシュする
* 自動圧縮は無効化してるのでAccept-Encodingを見ない

↑このAccept-Encodingを見ないというのが勘違いで、オリジン側では通常Accept-Encodingヘッダによって
gzip圧縮するか判定しているはずで、CloudFrontも正しくキャッシュ(非圧縮を期待しているクライアントに圧縮
データを応答しないこと)するためにAccept-Encodingヘッダをキャッシュ判定に使用していると思われます。

Go側でリクエスト送信時にAccept-Encodingヘッダを付けないようにすればキャッシュがうまく使われるように
なります。以下Go実装の修正イメージ

Accept-Encodingヘッダを付加しないリクエスト送信 実装例

DisableCompressionをtrueにしたTransportを生成して、それを渡してclientを生成してclient.Doする形

main.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    url := "http://example.com/"
    req, err := http.NewRequest(http.MethodGet, url, nil)
    if err != nil {
        fmt.Errorf("%v", err)
        return
    }
    client := &http.Client{
        Transport: &http.Transport{DisableCompression: true},
    }
    resp, err := client.Do(req)
    if err != nil {
        fmt.Errorf("%v", err)
        return
    }
    defer resp.Body.Close()
}

おわりに

CloudFrontの公式ドキュメントに明確な記述が見つけられなかったので、今回記事にしました。
誰かの助けになれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む