[インデックス 10930] ファイルの概要
このコミットは、Go言語のダッシュボードアプリケーションにおいて、ビルドタスク("todo" responses)の応答をキャッシュする機能を追加するものです。Google App EngineのMemcacheサービスを利用して、頻繁にリクエストされるビルドタスクの情報をメモリ上にキャッシュすることで、アプリケーションのパフォーマンス向上とデータストアへの負荷軽減を図っています。具体的には、キャッシュ管理のための新しいGoファイル cache.go
を導入し、既存のハンドラロジック (handler.go
) を変更してキャッシュを利用するようにし、関連する定数や関数を適切なファイルに移動しています。
コミット
commit 9754d61552d6f1b1c6b4f7a02d2b33c016c06f92
Author: Andrew Gerrand <adg@golang.org>
Date: Wed Dec 21 17:24:42 2011 +1100
dashboard: cache todo responses
R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/5500057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9754d61552d6f1b1c6b4f7a02d2b33c016c06f92
元コミット内容
dashboard: cache todo responses
R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/5500057
変更の背景
Go言語のビルドダッシュボードは、様々なプラットフォームや設定でのGoのビルド状況を監視し、ビルドタスクを管理するためのウェブアプリケーションです。このダッシュボードは、ビルドの状態や次に実行すべきタスク("todo" responses)を頻繁に提供する必要があります。これらの情報は通常、データストア(Google App Engineの場合はDatastore)から取得されますが、データストアへのアクセスはI/Oコストが高く、多数のリクエストが集中するとパフォーマンスのボトルネックとなる可能性があります。
このコミットの背景には、以下の目的があります。
- パフォーマンスの向上: 頻繁にリクエストされる「次に実行すべきビルドタスク」の情報をMemcacheにキャッシュすることで、データストアへのアクセス回数を減らし、応答時間を短縮します。
- データストア負荷の軽減: キャッシュヒット率を高めることで、データストアへの読み込み操作を減らし、バックエンドの負荷を軽減します。
- スケーラビリティの向上: キャッシュはアプリケーションのスケーラビリティを向上させ、より多くの同時リクエストを効率的に処理できるようにします。
特に、ビルドタスクの情報は一定期間は変化しないことが多いため、キャッシュによる効果が大きいと判断されたと考えられます。
前提知識の解説
Google App Engine (GAE)
Google App Engineは、Googleが提供するPlatform as a Service (PaaS) です。開発者はインフラストラクチャの管理を気にすることなく、スケーラブルなウェブアプリケーションやバックエンドサービスを構築・デプロイできます。Go言語はApp Engineでサポートされているランタイムの一つです。App Engineは、データストア、Memcache、タスクキューなど、様々なマネージドサービスを提供しており、これらをアプリケーションから簡単に利用できます。
Google App Engine Memcache
Memcacheは、Google App Engineが提供する分散型インメモリキャッシュサービスです。アプリケーションが頻繁にアクセスするデータを一時的にメモリに保存することで、データストアや他の永続ストレージへのアクセス回数を減らし、アプリケーションの応答速度を向上させます。キーと値のペアでデータを保存し、高速な読み書きが可能です。キャッシュされたデータには有効期限を設定でき、有効期限が切れるか、メモリが不足すると自動的に削除されます。
Go言語のos.Error
とerror
インターフェース
このコミットが作成された2011年当時、Go言語の標準エラー型はos.Error
インターフェースでした。しかし、Go 1.0のリリース(2012年3月)以降、より汎用的なerror
インターフェースが導入され、os.Error
は非推奨となりました。このコミットのコードではos.Error
が使用されていますが、これは当時のGo言語の慣習に沿ったものです。現代のGoコードでは、error
インターフェースを使用するのが一般的です。
Go Dashboard
Go Dashboardは、Goプロジェクトの継続的インテグレーション(CI)およびビルドステータスを監視するためのウェブアプリケーションです。様々なプラットフォームやコンフィギュレーションでのGoのビルド結果を表示し、開発者がGoの変更がどの環境に影響を与えるかを把握できるようにします。このダッシュボードは、ビルドボット(Goのコードをビルド・テストする自動化されたエージェント)に対して、次に実行すべきタスク("todo")を指示するAPIエンドポイントも提供しています。
Todo
responses
この文脈におけるTodo
responsesとは、Goビルドダッシュボードがビルドボットに対して提供する、次に実行すべきビルドタスクやテストタスクに関する情報です。例えば、「特定のコミットをビルドする」「特定のパッケージをテストする」といった指示が含まれます。これらの情報は、ビルドボットが効率的に作業を進めるために必要とされます。
技術的詳細
このコミットでは、Go App EngineのMemcacheサービスを効果的に利用して、todoHandler
からの応答をキャッシュするメカニズムを実装しています。
-
キャッシュの構造:
todoCacheKey
とuiCacheKey
という定数で、Memcacheに保存するデータのキーを定義しています。todoCacheExpiry
(3600秒 = 1時間) とuiCacheExpiry
(600秒 = 10分) で、キャッシュの有効期限を設定しています。todo
データは、map[string]*Todo
の形式でJSONエンコードされ、Memcacheに保存されます。キーはリクエストのクエリパラメータをエンコードした文字列(r.Form.Encode()
)です。これにより、同じリクエストパラメータを持つTodo
応答がキャッシュから提供されます。
-
キャッシュの操作:
invalidateCache(c appengine.Context)
: この関数は、データストアの変更があった際に、関連するMemcacheエントリ(uiCacheKey
とtodoCacheKey
)を削除するために使用されます。これにより、古いキャッシュデータが提供されるのを防ぎ、キャッシュの一貫性を保ちます。memcache.DeleteMulti
を使用して複数のキーを一度に削除します。cachedTodo(c appengine.Context, todoKey string) (todo *Todo, hit bool)
: 指定されたtodoKey
に対応するTodo
オブジェクトをMemcacheから取得します。キャッシュヒットした場合はhit
がtrue
となり、Todo
オブジェクトが返されます。cacheTodo(c appengine.Context, todoKey string, todo *Todo)
: 指定されたtodoKey
とTodo
オブジェクトをMemcacheに保存します。- 既存のキャッシュエントリがある場合は、
memcache.CompareAndSwap
を使用してアトミックに更新を試みます。これは、複数のリクエストが同時に同じキャッシュエントリを更新しようとした場合に、競合状態を防ぐための重要なメカニズムです。 - 新しいエントリの場合は
memcache.Set
を使用します。 - JSONのマーシャリング/アンマーシャリングを行い、
map[string]*Todo
をバイト列に変換して保存します。
- 既存のキャッシュエントリがある場合は、
todoCache(c appengine.Context) (item *memcache.Item, miss bool)
:todoCacheKey
に対応する生のmemcache.Item
を取得する低レベルなヘルパー関数です。unmarshalTodo(c appengine.Context, t *memcache.Item) map[string]*Todo
:memcache.Item
のValue
(バイト列)をmap[string]*Todo
にJSONデコードするヘルパー関数です。デコードに失敗した場合は、不正なキャッシュエントリを削除し、エラーをログに記録します。
-
todoHandler
の変更:todoHandler
の冒頭で、まずリクエストのクエリパラメータからtodoKey
を生成し、cachedTodo
を呼び出してキャッシュをチェックします。- キャッシュヒットした場合は、すぐにキャッシュされた
Todo
オブジェクトを返します。これにより、データストアへのアクセスや複雑なロジックの実行がスキップされ、高速な応答が可能になります。 - キャッシュミスの場合、従来のロジックに従って
Todo
オブジェクトを生成します。 Todo
オブジェクトが正常に生成された場合(エラーがない場合)、その結果をcacheTodo
関数を使ってMemcacheに保存します。これにより、次回の同じリクエストではキャッシュが利用されるようになります。
この実装により、ダッシュボードのtodoHandler
は、初回リクエスト時またはキャッシュが期限切れになった場合にのみ計算処理とデータストアアクセスを行い、それ以降の同じリクエストに対しては高速なキャッシュ応答を提供できるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
misc/dashboard/app/build/cache.go
(新規ファイル)- Memcacheを利用したキャッシュ操作に関するすべてのロジックがこのファイルに集約されています。
invalidateCache
,cachedTodo
,cacheTodo
,todoCache
,unmarshalTodo
といった関数が定義されています。- キャッシュキーと有効期限の定数 (
todoCacheKey
,todoCacheExpiry
,uiCacheKey
,uiCacheExpiry
) もここに定義されています。
-
misc/dashboard/app/build/handler.go
appengine/memcache
のインポートが削除されました。キャッシュ関連のロジックがcache.go
に移動したためです。todoHandler
関数が大幅に変更されました。- 関数の冒頭で
cachedTodo
を呼び出し、キャッシュヒットをチェックするロジックが追加されました。 - キャッシュミスの場合に
Todo
を生成した後、cacheTodo
を呼び出して結果をキャッシュに保存するロジックが追加されました。 - 以前
handler.go
内にあったinvalidateCache
関数が削除されました(cache.go
に移動)。
- 関数の冒頭で
-
misc/dashboard/app/build/ui.go
uiCacheKey
とuiCacheExpiry
の定数が削除されました。これらはcache.go
に移動しました。
コアとなるコードの解説
misc/dashboard/app/build/cache.go
このファイルは、Go App EngineのMemcacheサービスと連携して、アプリケーションのキャッシュ層を管理するための中心的なロジックを含んでいます。
const
定義:todoCacheKey = "build-todo"
: ビルドタスクのキャッシュに使用されるMemcacheキー。todoCacheExpiry = 3600
:todo
キャッシュの有効期限(秒単位、1時間)。uiCacheKey = "build-ui"
: UI関連のキャッシュに使用されるMemcacheキー。uiCacheExpiry = 10 * 60
:ui
キャッシュの有効期限(秒単位、10分)。
invalidateCache(c appengine.Context)
:uiCacheKey
とtodoCacheKey
の両方をMemcacheから削除します。- データストアの変更など、キャッシュを無効化する必要がある場合に呼び出されます。
memcache.DeleteMulti
を使用することで、複数のキーを効率的に削除できます。
cachedTodo(c appengine.Context, todoKey string) (todo *Todo, hit bool)
:todoCache
を呼び出して、生のMemcacheアイテムを取得します。- 取得したアイテムを
unmarshalTodo
でmap[string]*Todo
にデコードします。 todoKey
に対応するTodo
が存在すればそれを返し、hit
をtrue
にします。
cacheTodo(c appengine.Context, todoKey string, todo *Todo)
:todoCache
を呼び出して既存のキャッシュエントリを取得します。- キャッシュが存在しない場合は、空のJSONオブジェクト
"{}"
で新しいmemcache.Item
を作成します。 unmarshalTodo
で既存のキャッシュデータをmap[string]*Todo
にデコードし、新しいtodo
を追加します。- 更新された
map
をjson.Marshal
でJSONバイト列にエンコードし直します。 newItem
がtrue
(新しいエントリ)の場合はmemcache.Set
で保存し、それ以外の場合はmemcache.CompareAndSwap
でアトミックに更新します。CompareAndSwap
は、他のリクエストによってキャッシュが変更されていないことを確認しながら更新を行うため、競合状態を防ぎます。
todoCache(c appengine.Context) (item *memcache.Item, miss bool)
:memcache.Get(c, todoCacheKey)
を呼び出して、todoCacheKey
に対応するMemcacheアイテムを取得します。- キャッシュミス(
memcache.ErrCacheMiss
)の場合と、その他のエラーの場合を適切に処理します。
unmarshalTodo(c appengine.Context, t *memcache.Item) map[string]*Todo
:memcache.Item
のValue
フィールド(バイト列)をjson.Unmarshal
でmap[string]*Todo
にデコードします。- デコードに失敗した場合(例: キャッシュデータが破損している場合)、エラーをログに記録し、その不正なキャッシュエントリを
memcache.Delete
で削除します。
misc/dashboard/app/build/handler.go
このファイルは、HTTPリクエストを処理し、ビルドタスクを生成する主要なハンドラロジックを含んでいます。
import
の変更:"appengine/memcache"
のインポートが削除されました。これは、Memcache関連の関数がcache.go
に移動したためです。
todoHandler(r *http.Request) (interface{}, os.Error)
:- この関数は、ビルドボットからのリクエストを受け取り、次に実行すべきビルドタスクを返します。
- キャッシュチェックの追加:
リクエストのフォーム値をエンコードしてtodoKey := r.Form.Encode() if t, hit := cachedTodo(c, todoKey); hit { c.Debugf("cache hit") return t, nil } c.Debugf("cache miss")
todoKey
を生成し、cachedTodo
を呼び出してキャッシュをチェックします。キャッシュヒットした場合は、すぐにキャッシュされたTodo
を返します。 - キャッシュへの保存:
元のロジックでif err == nil { cacheTodo(c, todoKey, todo) } return todo, err
Todo
オブジェクトが正常に生成され、エラーがなかった場合、cacheTodo
を呼び出してその結果をMemcacheに保存します。
invalidateCache
関数の削除:- 以前このファイル内にあった
invalidateCache
関数は、cache.go
に移動されたため、ここから削除されました。
- 以前このファイル内にあった
misc/dashboard/app/build/ui.go
このファイルは、ダッシュボードのユーザーインターフェース関連のロジックを含んでいます。
- 定数の削除:
uiCacheKey
とuiCacheExpiry
の定数が削除されました。これらはcache.go
に移動され、キャッシュ関連の定数が一元管理されるようになりました。
これらの変更により、GoダッシュボードのtodoHandler
は、キャッシュを介してより効率的にビルドタスクの応答を提供できるようになり、全体的なパフォーマンスとスケーラビリティが向上します。
関連リンク
- Go CL 5500057: https://golang.org/cl/5500057
参考にした情報源リンク
- Google App Engine Memcache Overview: https://cloud.google.com/appengine/docs/standard/go/memcache/overview
- Go App Engine Standard Environment (Go 1.11+): https://cloud.google.com/appengine/docs/standard/go111/
- Go言語の
error
インターフェース (Go 1.0以降): https://go.dev/blog/go1.0-error - Go Dashboard (golang/build repository): https://github.com/golang/build (このコミットのコードは
misc/dashboard/app/build
以下に存在します)