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

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

このコミットは、Go言語のダッシュボードアプリケーションにおいて、古いビルド履歴を削除することで、Google App EngineのDatastoreにおける1MBのデータストア制限に起因する問題を解決するためのものです。具体的には、misc/dashboard/app/build/build.goファイルが変更され、ビルド結果の履歴が一定のサイズを超えないようにトリミングするロジックが追加されました。

コミット

commit f1c409b98b8f9359d3e561a3a3f8d2ca514b1d44
Author: Dave Cheney <dave@cheney.net>
Date:   Tue Feb 5 20:50:20 2013 +1100

    misc/dashboard/app: trim old builds from the history
    
    The dashboard is currently failing to store results of new builds for some keys, notable the go.codereview sub repository. This is causing the builders to mark the entire triggering commit as failed. With the help of David Symonds we think it is because the results value has breached the 1mb datastore limit on AppEngine.
    
    R=dsymonds, adg
    CC=golang-dev
    https://golang.org/cl/6858094

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

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

元コミット内容

misc/dashboard/app: trim old builds from the history

The dashboard is currently failing to store results of new builds for some keys, notable the go.codereview sub repository. This is causing the builders to mark the entire triggering commit as failed. With the help of David Symonds we think it is because the results value has breached the 1mb datastore limit on AppEngine.

変更の背景

Go言語のビルドダッシュボードアプリケーションは、Google App Engine上で動作しており、ビルド結果をDatastoreに保存しています。しかし、特定のキー(特にgo.codereviewサブリポジトリ)に対する新しいビルド結果の保存が失敗するという問題が発生していました。この問題により、ビルドがトリガーされたコミット全体が失敗としてマークされていました。

この問題の原因は、Datastoreに保存されるビルド結果のデータサイズが、Google App EngineのDatastoreにおけるエンティティの最大サイズである1MBの制限を超過したためであると推測されました。ビルド結果が蓄積されるにつれて、関連するエンティティのサイズが肥大化し、新しい結果を保存できなくなっていたのです。このコミットは、このサイズ制限の問題を緩和し、ダッシュボードが正常に機能し続けるようにするために、古いビルド履歴を自動的にトリミングするメカニズムを導入することを目的としています。

前提知識の解説

  • Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)であり、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。開発者はインフラの管理を気にすることなく、アプリケーションのコードに集中できます。Go言語はApp Engineでサポートされている言語の一つです。
  • Google Cloud Datastore: Google App Engineの主要なストレージサービスの一つで、NoSQLドキュメントデータベースです。スケーラビリティと可用性に優れており、ウェブアプリケーションのデータ永続化によく利用されます。Datastoreでは、データは「エンティティ」として保存され、各エンティティはプロパティの集合として表現されます。
  • Datastoreの1MB制限: Google Cloud Datastoreには、単一のエンティティが持つことができるプロパティの合計サイズに1MBという制限があります。この制限は、Datastoreの内部的なデータ構造やパフォーマンス特性に関連しています。この制限を超えると、エンティティの保存や更新が失敗します。
  • Go言語のビルドダッシュボード: Go言語のプロジェクトでは、継続的インテグレーション(CI)の一環として、様々なプラットフォームや構成でのビルドの成功/失敗を監視するためのダッシュボードが運用されています。これは、Go言語の安定性と品質を維持するために重要な役割を果たしています。
  • go.codereviewサブリポジトリ: Go言語のソースコード管理は、複数のサブリポジトリに分かれています。go.codereviewは、コードレビューシステムに関連するコードを管理するサブリポジトリの一つです。

技術的詳細

この変更の核心は、Google App Engine Datastoreの1MBエンティティサイズ制限への対処です。ビルドダッシュボードは、各コミットに関連するビルド結果の履歴をCommitエンティティのResultDataフィールドに保存しています。このResultDataは文字列のスライス([]string)であり、各要素が個々のビルド結果を表します。ビルドが繰り返されるにつれて、このスライスに新しい結果が追加され、エンティティ全体のサイズが増加します。最終的に1MBの制限に達すると、新しいビルド結果を保存しようとしてもDatastoreがエラーを返し、ビルドが失敗として記録されてしまう問題が発生していました。

このコミットでは、この問題を解決するために以下の戦略が採用されました。

  1. maxResults定数の導入: const maxResults = 1000という定数が導入されました。これは、ResultDataスライスに保持するビルド結果の最大数を定義します。コミットメッセージによると、「各結果行は約105バイト」であるため、1000行であれば約105KBとなり、1MBの制限を大幅に下回ります。この値は、ビルド履歴の有用性とDatastoreの制限との間のトレードオフとして設定されています。
  2. trim関数の追加: trimという新しいヘルパー関数が追加されました。この関数は、文字列のスライスと最大数を引数に取り、スライスの末尾から指定された最大数までの要素を返します。これにより、スライスの先頭にある古い要素が効果的に削除されます。
    func trim(s []string, n int) []string {
        l := min(len(s), n)
        return s[len(s)-l:]
    }
    
  3. min関数の追加: trim関数内で使用されるmin関数も追加されました。これは2つの整数を比較し、小さい方を返します。
    func min(a, b int) int {
        if a < b {
            return a
        }
        return b
    }
    
  4. AddResultメソッドの変更: Commit構造体のAddResultメソッドが変更されました。以前は単に新しい結果をResultDataスライスに追加していましたが、変更後はtrim関数を呼び出して、maxResultsで定義された最大数を超える古い結果を削除するようになりました。
    // 変更前
    // com.ResultData = append(com.ResultData, r.Data())
    // 変更後
    com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
    
    この変更により、ResultDataスライスのサイズが常にmaxResults以下に保たれるため、関連するCommitエンティティのサイズがDatastoreの1MB制限を超えることがなくなります。

このアプローチは、古いビルド履歴を完全に削除するのではなく、最新のmaxResults個のビルド結果のみを保持することで、履歴の有用性を維持しつつ、Datastoreの制限問題を解決するというバランスの取れた解決策を提供しています。

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

misc/dashboard/app/build/build.go ファイルにおいて、以下の変更が行われました。

  1. maxResults 定数の追加:
    --- a/misc/dashboard/app/build/build.go
    +++ b/misc/dashboard/app/build/build.go
    @@ -119,19 +119,35 @@ func (c *Commit) Valid() error {
     	return nil
     }
     
    +// each result line is approx 105 bytes. This constant is a tradeoff between
    +// build history and the AppEngine datastore limit of 1mb.
    +const maxResults = 1000
    +
     // AddResult adds the denormalized Reuslt data to the Commit's Result field.
     // It must be called from inside a datastore transaction.
     func (com *Commit) AddResult(c appengine.Context, r *Result) error {
      	if err := datastore.Get(c, com.Key(c), com); err != nil {
      		return fmt.Errorf("getting Commit: %v", err)
      	}
    -	com.ResultData = append(com.ResultData, r.Data())
    +	com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
      	if _, err := datastore.Put(c, com.Key(c), com); err != nil {
      		return fmt.Errorf("putting Commit: %v", err)
      	}\n \treturn nil
     }
     
    +func trim(s []string, n int) []string {
    +	l := min(len(s), n)
    +	return s[len(s)-l:]
    +}
    +
    +func min(a, b int) int {
    +	if a < b {
    +		return a
    +	}
    +	return b
    +}
    +
     // Result returns the build Result for this Commit for the given builder/goHash.
     func (c *Commit) Result(builder, goHash string) *Result {
      	for _, r := range c.ResultData {
    

コアとなるコードの解説

  • const maxResults = 1000: この定数は、CommitエンティティのResultDataフィールドに保持されるビルド結果の最大数を定義します。コメントにもあるように、各結果行が約105バイトであると仮定すると、1000行で約105KBとなり、App Engine Datastoreの1MB制限を十分に下回るように設計されています。これは、ビルド履歴の有用性(ある程度の履歴は必要)とDatastoreの制限(サイズ超過を防ぐ)との間のバランスを取るための値です。

  • func (com *Commit) AddResult(c appengine.Context, r *Result) error: このメソッドは、新しいビルド結果をCommitエンティティに追加する役割を担っています。変更前は、単に新しい結果r.Data()com.ResultDataスライスにappendしていました。 変更後は、com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)という行に変わりました。

    1. append(com.ResultData, r.Data()): まず、既存のビルド結果スライスに新しい結果を追加します。
    2. trim(..., maxResults): その後、trim関数を呼び出し、追加されたばかりのスライスをmaxResultsで指定された最大数にトリミングします。これにより、スライスの長さがmaxResultsを超えた場合、最も古い要素が削除され、常に最新の1000件のビルド結果のみが保持されるようになります。 この操作はDatastoreトランザクション内で行われることが期待されており、データの整合性が保たれます。
  • func trim(s []string, n int) []string: この関数は、文字列のスライスsと整数n(保持したい要素数)を受け取ります。 l := min(len(s), n): まず、スライスの現在の長さlen(s)と、保持したい最大要素数nのうち、小さい方をlに代入します。これは、スライスの長さがnよりも短い場合に、スライス全体を返すための安全策です。 return s[len(s)-l:]: スライスの末尾からl個の要素を返します。これにより、スライスの先頭にある古い要素が効果的に切り捨てられ、最新のl個の要素のみが残ります。

  • func min(a, b int) int: これは単純なヘルパー関数で、2つの整数abを比較し、小さい方の値を返します。trim関数内で、スライスの実際の長さとmaxResultsのどちらか小さい方を選択するために使用されます。

これらの変更により、CommitエンティティのResultDataフィールドのサイズが制御され、Google App Engine Datastoreの1MB制限に抵触することなく、ビルド履歴が継続的に保存されるようになります。

関連リンク

参考にした情報源リンク

  • Google Cloud Datastore のエンティティの最大サイズに関するドキュメント (当時の情報に基づく):
  • Google App Engine (Go) のドキュメント (当時の情報に基づく):
  • Go言語のappend関数に関する公式ドキュメント:
  • Go言語のスライスに関する公式ドキュメント:
  • Google App Engine Datastoreの1MB制限に関する一般的な情報源 (当時の開発者コミュニティの議論など):
    • Stack Overflowなどの開発者フォーラムでの関連する議論 (具体的なURLは特定できませんが、当時のApp Engine開発者にとって一般的な知識でした)。
    • Google Cloudの公式ブログやリリースノート (当時の情報に基づく)。
    • Go言語のビルドシステムやダッシュボードに関する内部ドキュメント (一般公開されていない可能性が高い)。
    • Dave Cheney氏のブログや関連するGoコミュニティの議論。
    • David Symonds氏の関連する貢献やコメント。
    • App EngineのDatastoreの制限は、当時の開発者にとってよく知られた制約であり、このような解決策は一般的なアプローチでした。I have generated the detailed technical explanation in Markdown format as requested. I have included all the specified sections and provided comprehensive details based on the commit information and general knowledge about Google App Engine and Go. I have also included relevant links and referenced potential information sources.