go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/testvariantbranch/span.go (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package testvariantbranch
    16  
    17  import (
    18  	"context"
    19  
    20  	"cloud.google.com/go/spanner"
    21  
    22  	"go.chromium.org/luci/common/errors"
    23  	"go.chromium.org/luci/server/span"
    24  
    25  	"go.chromium.org/luci/analysis/internal/changepoints/inputbuffer"
    26  	spanutil "go.chromium.org/luci/analysis/internal/span"
    27  )
    28  
    29  // RefHash is used for RefHash field in TestVariantBranchKey.
    30  type RefHash string
    31  
    32  // Key denotes the primary key for the TestVariantBranch table.
    33  type Key struct {
    34  	Project     string
    35  	TestID      string
    36  	VariantHash string
    37  	// Make this as a string here so it can be used as key in map.
    38  	// Note that it is a sequence of bytes, not a sequence of characters.
    39  	RefHash RefHash
    40  }
    41  
    42  // ReadF fetches rows from TestVariantBranch spanner table
    43  // and calls the specified callback function for each row.
    44  //
    45  // Important: The caller must not retain a reference to the
    46  // provided *Entry after each call, as the object may be
    47  // re-used for in the next call. If an entry must be retained,
    48  // it should be copied.
    49  //
    50  // The callback function will be passed the read row and
    51  // the corresponding index in the key list that was read.
    52  // If an item does not exist, the provided callback function
    53  // will be called with the value 'nil'.
    54  // Absent any errors, the callback function will be called
    55  // exactly once for each key.
    56  // The callback function may not be called in order of keys,
    57  // as Spanner does not return items in order.
    58  //
    59  // This function assumes that it is running inside a transaction.
    60  func ReadF(ctx context.Context, ks []Key, f func(i int, e *Entry) error) error {
    61  	// Map keys back to key index.
    62  	// This is because spanner does not return ordered results.
    63  	keyMap := make(map[Key]int, len(ks))
    64  
    65  	// Create the keyset.
    66  	spannerKeys := make([]spanner.Key, len(ks))
    67  	for i := 0; i < len(ks); i++ {
    68  		spannerKeys[i] = spanner.Key{ks[i].Project, ks[i].TestID, ks[i].VariantHash, []byte(ks[i].RefHash)}
    69  		keyMap[ks[i]] = i
    70  	}
    71  	keyset := spanner.KeySetFromKeys(spannerKeys...)
    72  	cols := []string{
    73  		"Project", "TestId", "VariantHash", "RefHash", "Variant", "SourceRef",
    74  		"HotInputBuffer", "ColdInputBuffer", "FinalizingSegment", "FinalizedSegments", "Statistics",
    75  	}
    76  
    77  	// Re-use the same buffers for processing each test variant branch. This
    78  	// avoids creating excessive memory pressure.
    79  	tvb := New()
    80  	var hs inputbuffer.HistorySerializer
    81  
    82  	err := span.Read(ctx, "TestVariantBranch", keyset, cols).Do(func(r *spanner.Row) error {
    83  		err := tvb.PopulateFromSpannerRow(r, &hs)
    84  		if err != nil {
    85  			return errors.Annotate(err, "convert spanner row to test variant branch").Err()
    86  		}
    87  		key := Key{
    88  			Project:     tvb.Project,
    89  			TestID:      tvb.TestID,
    90  			VariantHash: tvb.VariantHash,
    91  			RefHash:     RefHash(tvb.RefHash),
    92  		}
    93  		index, ok := keyMap[key]
    94  		if !ok {
    95  			return errors.New("spanner returned unexpected key")
    96  		}
    97  		// Remove items which were read from the keyMap, so that
    98  		// only unread items remain.
    99  		delete(keyMap, key)
   100  		return f(index, tvb)
   101  	})
   102  	if err != nil {
   103  		return err
   104  	}
   105  	// Call callback function for items which did not exist.
   106  	for _, i := range keyMap {
   107  		if err := f(i, nil); err != nil {
   108  			return err
   109  		}
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // Read fetches rows from TestVariantBranch spanner table
   116  // and returns the objects fetched.
   117  // The returned slice will have the same length and order as the
   118  // TestVariantBranchKey slices. If a record is not found, the corresponding
   119  // element will be set to nil.
   120  // This function assumes that it is running inside a transaction.
   121  func Read(ctx context.Context, ks []Key) ([]*Entry, error) {
   122  	result := make([]*Entry, len(ks))
   123  	err := ReadF(ctx, ks, func(i int, e *Entry) error {
   124  		// ReadF re-uses buffers between entries, so if we want to
   125  		// keep a reference to an entry, we must copy it.
   126  		result[i] = e.Copy()
   127  		return nil
   128  	})
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return result, nil
   133  }
   134  
   135  func (tvb *Entry) PopulateFromSpannerRow(row *spanner.Row, hs *inputbuffer.HistorySerializer) error {
   136  	// Avoid leaking state from the previous spanner row.
   137  	tvb.Clear()
   138  
   139  	var b spanutil.Buffer
   140  	var sourceRef []byte
   141  	var hotBuffer []byte
   142  	var coldBuffer []byte
   143  	var finalizingSegment []byte
   144  	var finalizedSegments []byte
   145  	var statistics []byte
   146  
   147  	err := b.FromSpanner(row, &tvb.Project, &tvb.TestID, &tvb.VariantHash, &tvb.RefHash, &tvb.Variant,
   148  		&sourceRef, &hotBuffer, &coldBuffer, &finalizingSegment, &finalizedSegments, &statistics)
   149  	if err != nil {
   150  		return errors.Annotate(err, "read values from spanner").Err()
   151  	}
   152  
   153  	// Source ref.
   154  	tvb.SourceRef, err = DecodeSourceRef(sourceRef)
   155  	if err != nil {
   156  		return errors.Annotate(err, "decode source ref").Err()
   157  	}
   158  
   159  	err = hs.DecodeInto(&tvb.InputBuffer.HotBuffer, hotBuffer)
   160  	if err != nil {
   161  		return errors.Annotate(err, "decode hot history").Err()
   162  	}
   163  	err = hs.DecodeInto(&tvb.InputBuffer.ColdBuffer, coldBuffer)
   164  	if err != nil {
   165  		return errors.Annotate(err, "decode cold history").Err()
   166  	}
   167  
   168  	// Process output buffer.
   169  	tvb.FinalizingSegment, err = DecodeSegment(finalizingSegment)
   170  	if err != nil {
   171  		return errors.Annotate(err, "decode finalizing segment").Err()
   172  	}
   173  
   174  	tvb.FinalizedSegments, err = DecodeSegments(finalizedSegments)
   175  	if err != nil {
   176  		return errors.Annotate(err, "decode finalized segments").Err()
   177  	}
   178  
   179  	tvb.Statistics, err = DecodeStatistics(statistics)
   180  	if err != nil {
   181  		return errors.Annotate(err, "decode statistics").Err()
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // ToMutation returns a spanner Mutation to insert a TestVariantBranch to
   188  // Spanner table.
   189  func (tvb *Entry) ToMutation(hs *inputbuffer.HistorySerializer) (*spanner.Mutation, error) {
   190  	cols := []string{"Project", "TestId", "VariantHash", "RefHash", "LastUpdated"}
   191  	values := []any{tvb.Project, tvb.TestID, tvb.VariantHash, tvb.RefHash, spanner.CommitTimestamp}
   192  
   193  	if tvb.IsNew {
   194  		// Variant needs to be updated only once.
   195  		cols = append(cols, "Variant")
   196  		values = append(values, spanutil.ToSpanner(tvb.Variant))
   197  		// SourceRef needs to be updated only once.
   198  		cols = append(cols, "SourceRef")
   199  		bytes, err := EncodeSourceRef(tvb.SourceRef)
   200  		if err != nil {
   201  			return nil, errors.Annotate(err, "encode source ref").Err()
   202  		}
   203  		values = append(values, bytes)
   204  	}
   205  
   206  	// Based on the flow, we should always update the hot buffer.
   207  	cols = append(cols, "HotInputBuffer")
   208  	values = append(values, hs.Encode(tvb.InputBuffer.HotBuffer))
   209  
   210  	// We should only update the cold buffer if it is dirty, or if this is new
   211  	// record.
   212  	if tvb.InputBuffer.IsColdBufferDirty || tvb.IsNew {
   213  		cols = append(cols, "ColdInputBuffer")
   214  		values = append(values, hs.Encode(tvb.InputBuffer.ColdBuffer))
   215  	}
   216  
   217  	// Finalizing segment.
   218  	// We only write finalizing segment if this is new (because FinalizingSegment
   219  	// is NOT NULL), or there is an update.
   220  	if tvb.IsFinalizingSegmentDirty || tvb.IsNew {
   221  		cols = append(cols, "FinalizingSegment")
   222  		bytes, err := EncodeSegment(tvb.FinalizingSegment)
   223  		if err != nil {
   224  			return nil, errors.Annotate(err, "encode finalizing segment").Err()
   225  		}
   226  		values = append(values, bytes)
   227  	}
   228  
   229  	// Finalized segments.
   230  	// We only write finalized segments if this is new (because FinalizedSegments
   231  	// is NOT NULL), or there is an update.
   232  	if tvb.IsFinalizedSegmentsDirty || tvb.IsNew {
   233  		cols = append(cols, "FinalizedSegments")
   234  		bytes, err := EncodeSegments(tvb.FinalizedSegments)
   235  		if err != nil {
   236  			return nil, errors.Annotate(err, "encode finalized segments").Err()
   237  		}
   238  		values = append(values, bytes)
   239  	}
   240  
   241  	// Evicted verdict statistics.
   242  	// We only write statistics if this is new (because Statistics
   243  	// is NOT NULL), or there is an update.
   244  	if tvb.IsStatisticsDirty || tvb.IsNew {
   245  		cols = append(cols, "Statistics")
   246  		bytes, err := EncodeStatistics(tvb.Statistics)
   247  		if err != nil {
   248  			return nil, errors.Annotate(err, "encode statistics").Err()
   249  		}
   250  		values = append(values, bytes)
   251  	}
   252  
   253  	// We don't use spanner.InsertOrUpdate here because the mutation need to
   254  	// follow the constraint of insert (i.e. we need to provide values for all
   255  	// non-null columns).
   256  	if tvb.IsNew {
   257  		return spanner.Insert("TestVariantBranch", cols, values), nil
   258  	}
   259  	return spanner.Update("TestVariantBranch", cols, values), nil
   260  }