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 }