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

[インデックス 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コストが高く、多数のリクエストが集中するとパフォーマンスのボトルネックとなる可能性があります。

このコミットの背景には、以下の目的があります。

  1. パフォーマンスの向上: 頻繁にリクエストされる「次に実行すべきビルドタスク」の情報をMemcacheにキャッシュすることで、データストアへのアクセス回数を減らし、応答時間を短縮します。
  2. データストア負荷の軽減: キャッシュヒット率を高めることで、データストアへの読み込み操作を減らし、バックエンドの負荷を軽減します。
  3. スケーラビリティの向上: キャッシュはアプリケーションのスケーラビリティを向上させ、より多くの同時リクエストを効率的に処理できるようにします。

特に、ビルドタスクの情報は一定期間は変化しないことが多いため、キャッシュによる効果が大きいと判断されたと考えられます。

前提知識の解説

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.Errorerrorインターフェース

このコミットが作成された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からの応答をキャッシュするメカニズムを実装しています。

  1. キャッシュの構造:

    • todoCacheKeyuiCacheKeyという定数で、Memcacheに保存するデータのキーを定義しています。
    • todoCacheExpiry (3600秒 = 1時間) とuiCacheExpiry (600秒 = 10分) で、キャッシュの有効期限を設定しています。
    • todoデータは、map[string]*Todoの形式でJSONエンコードされ、Memcacheに保存されます。キーはリクエストのクエリパラメータをエンコードした文字列(r.Form.Encode())です。これにより、同じリクエストパラメータを持つTodo応答がキャッシュから提供されます。
  2. キャッシュの操作:

    • invalidateCache(c appengine.Context): この関数は、データストアの変更があった際に、関連するMemcacheエントリ(uiCacheKeytodoCacheKey)を削除するために使用されます。これにより、古いキャッシュデータが提供されるのを防ぎ、キャッシュの一貫性を保ちます。memcache.DeleteMultiを使用して複数のキーを一度に削除します。
    • cachedTodo(c appengine.Context, todoKey string) (todo *Todo, hit bool): 指定されたtodoKeyに対応するTodoオブジェクトをMemcacheから取得します。キャッシュヒットした場合はhittrueとなり、Todoオブジェクトが返されます。
    • cacheTodo(c appengine.Context, todoKey string, todo *Todo): 指定されたtodoKeyTodoオブジェクトを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.ItemValue(バイト列)をmap[string]*TodoにJSONデコードするヘルパー関数です。デコードに失敗した場合は、不正なキャッシュエントリを削除し、エラーをログに記録します。
  3. todoHandlerの変更:

    • todoHandlerの冒頭で、まずリクエストのクエリパラメータからtodoKeyを生成し、cachedTodoを呼び出してキャッシュをチェックします。
    • キャッシュヒットした場合は、すぐにキャッシュされたTodoオブジェクトを返します。これにより、データストアへのアクセスや複雑なロジックの実行がスキップされ、高速な応答が可能になります。
    • キャッシュミスの場合、従来のロジックに従ってTodoオブジェクトを生成します。
    • Todoオブジェクトが正常に生成された場合(エラーがない場合)、その結果をcacheTodo関数を使ってMemcacheに保存します。これにより、次回の同じリクエストではキャッシュが利用されるようになります。

この実装により、ダッシュボードのtodoHandlerは、初回リクエスト時またはキャッシュが期限切れになった場合にのみ計算処理とデータストアアクセスを行い、それ以降の同じリクエストに対しては高速なキャッシュ応答を提供できるようになります。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. misc/dashboard/app/build/cache.go (新規ファイル)

    • Memcacheを利用したキャッシュ操作に関するすべてのロジックがこのファイルに集約されています。
    • invalidateCache, cachedTodo, cacheTodo, todoCache, unmarshalTodoといった関数が定義されています。
    • キャッシュキーと有効期限の定数 (todoCacheKey, todoCacheExpiry, uiCacheKey, uiCacheExpiry) もここに定義されています。
  2. misc/dashboard/app/build/handler.go

    • appengine/memcacheのインポートが削除されました。キャッシュ関連のロジックがcache.goに移動したためです。
    • todoHandler関数が大幅に変更されました。
      • 関数の冒頭でcachedTodoを呼び出し、キャッシュヒットをチェックするロジックが追加されました。
      • キャッシュミスの場合にTodoを生成した後、cacheTodoを呼び出して結果をキャッシュに保存するロジックが追加されました。
      • 以前handler.go内にあったinvalidateCache関数が削除されました(cache.goに移動)。
  3. misc/dashboard/app/build/ui.go

    • uiCacheKeyuiCacheExpiryの定数が削除されました。これらは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):
    • uiCacheKeytodoCacheKeyの両方をMemcacheから削除します。
    • データストアの変更など、キャッシュを無効化する必要がある場合に呼び出されます。
    • memcache.DeleteMultiを使用することで、複数のキーを効率的に削除できます。
  • cachedTodo(c appengine.Context, todoKey string) (todo *Todo, hit bool):
    • todoCacheを呼び出して、生のMemcacheアイテムを取得します。
    • 取得したアイテムをunmarshalTodomap[string]*Todoにデコードします。
    • todoKeyに対応するTodoが存在すればそれを返し、hittrueにします。
  • cacheTodo(c appengine.Context, todoKey string, todo *Todo):
    • todoCacheを呼び出して既存のキャッシュエントリを取得します。
    • キャッシュが存在しない場合は、空のJSONオブジェクト"{}"で新しいmemcache.Itemを作成します。
    • unmarshalTodoで既存のキャッシュデータをmap[string]*Todoにデコードし、新しいtodoを追加します。
    • 更新されたmapjson.MarshalでJSONバイト列にエンコードし直します。
    • newItemtrue(新しいエントリ)の場合は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.ItemValueフィールド(バイト列)をjson.Unmarshalmap[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

このファイルは、ダッシュボードのユーザーインターフェース関連のロジックを含んでいます。

  • 定数の削除:
    • uiCacheKeyuiCacheExpiryの定数が削除されました。これらはcache.goに移動され、キャッシュ関連の定数が一元管理されるようになりました。

これらの変更により、GoダッシュボードのtodoHandlerは、キャッシュを介してより効率的にビルドタスクの応答を提供できるようになり、全体的なパフォーマンスとスケーラビリティが向上します。

関連リンク

参考にした情報源リンク