Go Modules のダウンロードを高速化する Module Mirror と Module Index, Module Checksum Database について

# はじめに

2018年末に Go の開発チームが 2019年 の Go Modules に関する計画を [Go Modules in 2019 - The Go Blog](https://blog.golang.org/modules2019) で公開しました。

ブログの中では、Go 1.11 から提供された Go Modules[^1] の説明から始まり、go get で行えたことを Go Modules でも実現することの重要性[^2] や、コードの分散管理が許容されることの欠点[^3] に触れた後に、Go チームでの Go Modules に関する計画が書かれていました。[^4]

今回は、当時 Go Modules に関する計画として紹介されていて、最近になり [アルファテストの提供開始がアナウンスされた](https://twitter.com/bradfitz/status/1122966144346759168?s=20) Module Mirror, Module Index, Module Checksum Database についてまとめていきたいと思います。

# Module Mirror（ https://proxy.golang.org ）

Module Mirror は Google から提供される高速かつ高信頼性な Module[^5] のキャッシュプロキシーです。まだキャッシュされていない Module が Module Mirror にリクエストされた際には、公開されているオリジンサーバーから Module を取得してキャッシュしておいてくれる形です。

この Module Mirror を利用することで Module を高速にダウンロードできたり、GitHub などのオリジンサーバーがダウンしていた場合や URL が変更されてしまった場合でも、指定した Module のダウンロードが可能になります。

それでは Module Mirror の使い方について説明していきます。
Go Modules では Module Proxy（GOPROXY）という概念が導入[^6] されており、Module の取得をオリジンサーバーでなく任意のサーバーを指定することが可能になっているので、以下のように Module Proxy として Module Mirror を指定することで、ユーザーはその恩恵を受けることができます。

```bash
GOPROXY=https://proxy.golang.org
```

では、実際に Module をオリジンサーバーから取得する場合と Module Mirror から取得する場合の時間を比較してみたいと思います。[^7]

```bash
# Go Modules を利用しているプロダクトを Clone してくる
$ git clone https://github.com/spiffe/spire.git
$ cd spire

# Module Mirror を使わずに Module をダウンロードする
$ time GO111MODULE=on go mod download
...
real	2m29.631s
user	2m32.240s
sys	1m5.320s

# Module Mirror を使って Module をダウンロードする
$ GOPROXY=https://proxy.golang.org GO111MODULE=on go mod download
...
real	0m36.504s
user	0m21.410s
sys	0m16.240s
```

以下の結果となりました。Mirror Module を利用することで Module のダウンロードが高速化されることがわかります。とても早いですね！

|あり/なし|所要時間|
|:-:|:-:|
|Mirror Module なし|約2分半|
|Mirror Module あり|約30秒|

# Module Checksum Database（ https://sum.golang.org ）

Module を認証するために利用可能な Checksum が格納されているデータベースとなります。[^8] 様々なサーバーで公開されている Module の go.sum が提供されるイメージです。

Module Checksum Database から取得した Checksum と、ダウンロードした Module の Checksum を比較することでコードの信頼性を確認することが可能になります。

少し例をあげると、https://sum.golang.org/latest にリクエストを投げることで、Module Checksum Database のツリーサイズや最新のログのハッシュなどを取得することが出来たり、https://sum.golang.org/lookup/github.com/kubernetes/kubernetes@v1.15.0 のように `https://sum.golang.org/lookup/<Module>@<Version>` と指定することで、対象 Module の情報を取得することが出来ます。これらを利用して Go コマンドの内部でダウンロードした Module の信頼性確認が行われているのだと思います。

Module Checksum Database の使い方ですが、`GOPROXY=https://proxy.golang.org` で Module Mirror を利用する場合には、自動的にこの Module Checksum Database が使われる仕様のようです。明示的に設定したい場合には `GOSUMDB=sum.golang.org` を宣言すれば良いです。

なお、Go 1.12 以前では `gosumcheck` を使って、手動で Module Checksum Database を使ってのチェックが可能なようなので、興味ある方はお試しください。

```bash
go get golang.org/x/mod/gosumcheck
gosumcheck /path/to/go.sum
```

蛇足ですが、元々は `the Go notary` という名前だったらしいですが、CNCF Projects の [Notary](https://github.com/theupdateframework/notary) との混同を避けるために現在の命名となったようです。

# Module Index（ https://index.golang.org ）

新しい Module のバージョンのフィードを提供するもので https://index.golang.org/index からフィードを確認できます。フィードは年代順にソートされており、各 Module は以下の Key を持った JSON として構成されています。

|Key|概要|
|:-:|:-:|
|Path|Module 名|
|Version|Module のバージョン|
|Timestamp|Module Mirror（ proxy.golang.org ）で最初にキャッシュされた時刻|

また、以下のパラメータがサポートされています。

|パラメータ|概要|例|
|:-:|:-:|:-:|
|since|指定した Timestamp 以降のフィードを取得できる|https://index.golang.org/index?since=2019-07-01T17:00:00%2b09:00 （JST で 2019-07-01T17:00:00+09:00 を指定している）|
|limit|指定したサイズのフィードを取得できる|https://index.golang.org/index?limit=10|

これを利用することで新しい Module などを発見することが可能になります。godoc.org は今後こちらを参照するようになる?のようなことも記載されておりました。

# 懸念事項

Module Mirror の個人的な懸念点としては、気をつけて利用しないと Private な Module が Module Mirror にキャッシュされてしまうのでは?ということがあります。なので Private な Module を利用しているプロダクトでの Module Mirror 有効化に少し不安があります。

GOPRIVATE, GONOPROXY, GONOSUMDB 辺りを使えば良いのかな？ぐらいの理解なので、今後調べきれたら追記したいと考えています。

# さいごに

今回は Module Mirror, Module Index, Module Checksum Database について紹介しました。現時点では Stable ではないので、不安定性やバグなどがある可能性もありますが、利用する価値はあるのかなと思います。開発されているプロダクトの要件に応じて採用するかどうかをジャッジ頂ければと思います。

Go 1.13 以降の Go コマンドでは、Module Mirror と Module Checksum Database の利用がデフォルトで有効となる（ https://github.com/golang/go/commit/f8a5ba2a3880f4782d8250fd26dde2baa3990afa ）とのことなので、今後もキャッチアップを続けていきたいと思います。

おしまい。

[^1]: Go Modules 自体の説明は [Go Modulesの概要とGo1.12に含まれるModulesに関する変更 - My External Storage](https://budougumi0617.github.io/2019/02/15/go-modules-on-go112/) がとてもわかりやすいので、そちらを参照ください。
[^2]: `go get` のオリジナルデザインで重要だったことの1つに、Node の NPM や Java の Maven など中央集権型なコード管理ではなく、誰でも自分のコードを好きなサーバーで公開できて、誰もが好きなサーバーからコードをインポートできるという非中央集権型（分散型）なコード管理というものがあったとのこと。
[^3]: Go パッケージの依存関係の分散化の重大な欠点として、公開されている Goパッケージ を見つけることの難しさや、そもそも Goパッケージ の提供が継続される保証がないということが挙げられていました。
[^4]: 自分が読み違えて理解している場合もあるので、詳細が気になる方はオリジナルのブログを参照ください。
[^5]: [公式ドキュメント](https://github.com/golang/go/wiki/Modules#modules) では `A module is a collection of related Go packages that are versioned together as a single unit.` という定義がされています。
[^6]: `go help goproxy` で使い方を参照ください。
[^7]: 今回は Go Modules を利用しているプロダクトを Clone する形を取りましたが、単純に go get での比較でも良いと思います。
[^8]: 詳細は [Proposal: Secure the Public Go Module Ecosystem](https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md) に記載されています。