Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13177] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の server.go ファイルに対する変更です。server.go は、HTTPサーバーのコアロジック、特にリクエストのルーティングを司る ServeMux の実装を含んでいます。ServeMux は、HTTPリクエストのURLパスに基づいて適切なハンドラをディスパッチするマルチプレクサ(ルーター)として機能します。

コミット

net/http: speed up ServeMux when no patterns contain hostnames

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6248053

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c238031b2de1a83e0441b4696dd7c2754e80688b

元コミット内容

    net/http: speed up ServeMux when no patterns contain hostnames
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6248053

変更の背景

このコミットの主な目的は、net/http パッケージの ServeMux のパフォーマンスを向上させることです。具体的には、「登録されたパターンの中にホスト名を含むものが一つも存在しない場合」に、リクエストのルーティング処理を高速化することを目指しています。

ServeMux は、リクエストを処理する際に、まずホスト名を含むパターン(例: example.com/path)にマッチするかどうかを試み、次にホスト名を含まない一般的なパスパターン(例: /path)にマッチするかどうかを試みます。しかし、アプリケーションがホスト名を含むパターンを一切使用しない場合でも、ServeMux は常にホスト名を含むパターンでのマッチングを試みるため、その分のオーバーヘッドが発生していました。

この変更は、この不要なマッチング処理をスキップすることで、パフォーマンスの最適化を図るものです。これにより、一般的なWebアプリケーション(ホスト名ベースのルーティングをあまり使用しないもの)において、リクエスト処理のレイテンシが削減されることが期待されます。

前提知識の解説

Go言語の net/http パッケージ

net/http パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーション開発において中心的な役割を果たします。

http.ServeMux

http.ServeMux は、GoのHTTPサーバーにおけるリクエストマルチプレクサ(ルーター)です。これは、受信したHTTPリクエストのURLパスに基づいて、適切な http.Handler を選択し、そのハンドラにリクエストの処理を委譲します。

ServeMux には、Handle メソッドや HandleFunc メソッドを使って、特定のURLパターンとそれに対応するハンドラを登録します。パターンには以下の2種類があります。

  1. パスパターン: /path/to/resource のように、URLのパス部分のみを指定するパターン。
  2. ホスト名を含むパターン: example.com/path のように、ホスト名とパスを組み合わせたパターン。これにより、同じパスでも異なるホスト名からのリクエストに対して異なるハンドラを割り当てることができます(バーチャルホストのような機能)。

ServeMux は、リクエストが来た際に、まずホスト名を含むパターンにマッチするかどうかを調べ、次に一般的なパスパターンにマッチするかどうかを調べます。

http.Handler インターフェース

http.Handler は、ServeHTTP(ResponseWriter, *Request) メソッドを持つインターフェースです。このインターフェースを実装する型は、HTTPリクエストを処理するハンドラとして機能します。

sync.RWMutex

sync.RWMutex は、Go言語の sync パッケージで提供される読み書きミューテックスです。これは、共有リソースへのアクセスを制御するために使用されます。

  • RWMutex.RLock(): 読み取りロックを取得します。複数のゴルーチンが同時に読み取りロックを取得できます。
  • RWMutex.RUnlock(): 読み取りロックを解放します。
  • RWMutex.Lock(): 書き込みロックを取得します。書き込みロックが取得されている間は、他の読み取りロックも書き込みロックも取得できません。
  • RWMutex.Unlock(): 書き込みロックを解放します。

ServeMux の内部マップ (m) は、複数のゴルーチンから同時にアクセスされる可能性があるため、sync.RWMutex を使用して並行アクセスを安全に制御しています。Handle メソッドでパターンを登録する際には書き込みロック (Lock) を、handler メソッドでパターンを検索する際には読み取りロック (RLock) を使用します。

技術的詳細

このコミットは、ServeMux の内部状態に hosts という新しいブーリアンフィールドを追加し、このフィールドを使ってルーティングロジックを最適化します。

  1. ServeMux 構造体への hosts フィールドの追加: ServeMux 構造体に hosts bool というフィールドが追加されました。このフィールドは、ServeMux に登録されているパターンの中に、ホスト名を含むパターンが一つでも存在するかどうかを示すフラグとして機能します。初期値は false です。

  2. Handle メソッドでの hosts フィールドの更新: ServeMux.Handle メソッドは、新しいパターンが登録されるたびに呼び出されます。このメソッド内で、登録される pattern がスラッシュ (/) で始まらない場合(これはホスト名を含むパターンであることを意味します)、mux.hosts フィールドが true に設定されます。一度 true に設定されると、ServeMux のライフサイクルを通じて true のまま維持されます。これは、ホスト名を含むパターンが一度でも登録されれば、以降のリクエスト処理でホスト名ベースのマッチングが必要になるためです。

  3. handler メソッドでの条件付きマッチング: ServeMux.handler メソッドは、受信したHTTPリクエストに対して適切なハンドラを見つけるための内部メソッドです。このメソッド内で、mux.hosts フィールドの値がチェックされます。

    • もし mux.hoststrue であれば、これまで通り r.Host + r.URL.Path を使ってホスト名を含むパターンでのマッチングを試みます。
    • もし mux.hostsfalse であれば、ホスト名を含むパターンでのマッチング(mux.match(r.Host + r.URL.Path))は完全にスキップされ、直接 r.URL.Path を使った一般的なパスパターンでのマッチングに移行します。

この最適化により、ホスト名を含むパターンが一つも登録されていない ServeMux の場合、リクエストごとに r.Host + r.URL.Path を構築し、そのパターンでマップを検索するという不要な処理が完全に回避され、パフォーマンスが向上します。特に、大量のリクエストを処理するサーバーにおいて、この小さな最適化が累積的な効果をもたらす可能性があります。

コアとなるコードの変更箇所

src/pkg/net/http/server.go ファイルにおいて、以下の変更が行われました。

  1. ServeMux 構造体へのフィールド追加:

    --- a/src/pkg/net/http/server.go
    +++ b/src/pkg/net/http/server.go
    @@ -840,8 +840,9 @@ func RedirectHandler(url string, code int) Handler {
     // redirecting any request containing . or .. elements to an
     // equivalent .- and ..-free URL.
     type ServeMux struct {
    -	mu sync.RWMutex
    -	m  map[string]muxEntry
    +	mu    sync.RWMutex
    +	m     map[string]muxEntry
    +	hosts bool // whether any patterns contain hostnames
     }
    
  2. handler メソッドの変更:

    --- a/src/pkg/net/http/server.go
    +++ b/src/pkg/net/http/server.go
    @@ -903,12 +904,14 @@ func (mux *ServeMux) match(path string) Handler {\n }\n \n // handler returns the handler to use for the request r.\n-func (mux *ServeMux) handler(r *Request) Handler {\n+func (mux *ServeMux) handler(r *Request) (h Handler) {\n  	mux.mu.RLock()\n  	defer mux.mu.RUnlock()\n  \n  	// Host-specific pattern takes precedence over generic ones\n-\th := mux.match(r.Host + r.URL.Path)\n+\tif mux.hosts {\n+\t\th = mux.match(r.Host + r.URL.Path)\n+\t}\n  	if h == nil {\n  	\th = mux.match(r.URL.Path)\n  	}\
    
  3. Handle メソッドの変更:

    --- a/src/pkg/net/http/server.go
    +++ b/src/pkg/net/http/server.go
    @@ -950,6 +953,10 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) {\n  \n  	mux.m[pattern] = muxEntry{explicit: true, h: handler}\n  \n+\tif pattern[0] != \'/\' {\n+\t\tmux.hosts = true\n+\t}\n+\n  	// Helpful behavior:\n  	// If pattern is /tree/, insert an implicit permanent redirect for /tree.\n  	// It can be overridden by an explicit registration.\
    

コアとなるコードの解説

ServeMux 構造体への hosts フィールド追加

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}

hosts フィールドは、ServeMux インスタンスがホスト名を含むパターン(例: example.com/path)を登録しているかどうかを示すブーリアンフラグです。このフラグは、ルーティング処理の最適化のために導入されました。デフォルト値は false です。

handler メソッドの変更

func (mux *ServeMux) handler(r *Request) (h Handler) {
 	mux.mu.RLock()
 	defer mux.mu.RUnlock()

 	// Host-specific pattern takes precedence over generic ones
 	if mux.hosts { // <-- 追加された条件分岐
 		h = mux.match(r.Host + r.URL.Path)
 	}
 	if h == nil {
 		h = mux.match(r.URL.Path)
 	}
    // ... (後続のコード)
}

handler メソッドは、受信したリクエスト r に基づいて適切な Handler を見つけ出す役割を担います。変更前は、常に mux.match(r.Host + r.URL.Path) を呼び出してホスト名を含むパターンでのマッチングを試みていました。

この変更により、if mux.hosts という条件が追加されました。

  • もし mux.hoststrue であれば、ホスト名を含むパターンが登録されている可能性があるため、r.Host + r.URL.Path を使ったマッチングが実行されます。
  • もし mux.hostsfalse であれば、ホスト名を含むパターンは一つも登録されていないことが保証されるため、この最初のマッチングステップは完全にスキップされます。これにより、不要な文字列結合 (r.Host + r.URL.Path) とマップ検索のオーバーヘッドが削減され、パフォーマンスが向上します。

Handle メソッドの変更

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    // ... (既存のコード)

 	mux.m[pattern] = muxEntry{explicit: true, h: handler}

 	if pattern[0] != '/' { // <-- 追加された条件
 		mux.hosts = true
 	}

    // ... (後続のコード)
}

Handle メソッドは、新しいURLパターンとそれに対応するハンドラを ServeMux に登録するために使用されます。

追加された if pattern[0] != '/' という条件は、登録される pattern がスラッシュ (/) で始まらないかどうかをチェックしています。Goの net/http において、スラッシュで始まらないパターンはホスト名を含むパターン(例: example.com/path)として解釈されます。

  • もし登録されるパターンがホスト名を含むパターンであれば、mux.hosts フィールドが true に設定されます。
  • 一度 mux.hoststrue に設定されると、それは ServeMux のインスタンスの寿命を通じて true のまま維持されます。これは、一度でもホスト名を含むパターンが登録されれば、以降のリクエスト処理でホスト名ベースのマッチングが必要になるためです。

この変更により、ServeMux は自身の内部状態(ホスト名を含むパターンが登録されているか否か)を追跡し、その情報に基づいてルーティングロジックを動的に最適化できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語の net/http パッケージのソースコード
  • sync パッケージのドキュメント
  • 一般的なHTTPルーティングとWebサーバーの概念に関する知識