go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/buildid/buildid.go (about)

     1  // Copyright 2020 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 buildid provides the build id computation related functions.
    16  //
    17  // Build key:
    18  // Build keys are autogenerated, monotonically decreasing integers.
    19  // That is, when sorted by key, new builds are first.
    20  // Build has no parent.
    21  //
    22  // Build id is a 64 bits integer.
    23  //   - 1 highest order bit is set to 0 to keep value positive.
    24  //   - 43 bits are 43 lower bits of bitwise-inverted time since
    25  //     beginningOfTheWorld at 1ms resolution.
    26  //     It is good for 2**43 / 365.3 / 24 / 60 / 60 / 1000 = 278 years
    27  //     or 2010 + 278 = year 2288.
    28  //   - 16 bits are set to a random value. Assuming an instance is internally
    29  //     consistent with itself, it can ensure to not reuse the same 16 bits in two
    30  //     consecutive requests and/or throttle itself to one request per
    31  //     millisecond. Using random value reduces to 2**-15 the probability of
    32  //     collision on exact same timestamp at 1ms resolution, so a maximum
    33  //     theoretical rate of 65536000 requests/sec but an effective rate in the
    34  //     range of ~64k qps without much transaction conflicts. We should be fine.
    35  //   - 4 bits are 0. This is to represent the 'version' of the entity
    36  //     schema.
    37  //
    38  // The idea is taken from Swarming TaskRequest entity:
    39  // https://source.chromium.org/chromium/_/chromium/infra/luci/luci-py/+/a4a91d5e1e14b8b866b68b68bc1055b0b8ffef3b:appengine/swarming/server/task_request.py;l=1380-1404
    40  package buildid
    41  
    42  import (
    43  	"context"
    44  	"time"
    45  
    46  	"go.chromium.org/luci/common/data/rand/mathrand"
    47  )
    48  
    49  const (
    50  	timeResolution = time.Millisecond
    51  
    52  	// BuildIDMax is the maximum theoretical build ID.
    53  	// Based on time = beginningOfTheWorld, and max values for random and version bits.
    54  	BuildIDMax = int64(0x7FFFFFFFFFFFFFFF)
    55  
    56  	// buildIDTimeSuffixLen is the number of bits following the time in a build ID.
    57  	buildIDTimeSuffixLen = 20
    58  
    59  	// buildIDVersion is the version number of the build ID scheme.
    60  	// Must not exceed 15 in the current build ID scheme since it's stored in four bits.
    61  	buildIDVersion = 1
    62  )
    63  
    64  var (
    65  	// beginningOfTheWorld is the earliest valid time encoded by build IDs.
    66  	beginningOfTheWorld = time.Date(2010, 01, 01, 0, 0, 0, 0, time.UTC)
    67  )
    68  
    69  // NewBuildIDs generates the given number of build IDs in descending order.
    70  func NewBuildIDs(ctx context.Context, t time.Time, n int) []int64 {
    71  	// Build ID format [idTimeSegment] [random number] [buildIDVersion]
    72  	// Generate n build IDs by getting a random number R, then using the n
    73  	// integers in the range (R-n, R] as the random component while fixing
    74  	// the time and version components (see package-level comment). Ensure
    75  	// R is in [n-1, 2^16) so that the range (R-n, R] only contains
    76  	// non-negative integers.
    77  	r := mathrand.Intn(ctx, 65536-(n-1)) + (n - 1)
    78  	base := idTimeSegment(t) | (int64(r) << 4) | buildIDVersion
    79  	ids := make([]int64, n)
    80  	for i := range ids {
    81  		// Returned build IDs are in descending order:
    82  		// [time] [R] [version]
    83  		// [time] [R-1] [version]
    84  		// [time] [R-2] [version]
    85  		// ...
    86  		// [time] [R-(n-1)] [version]
    87  		ids[i] = base - int64(i*(1<<4))
    88  	}
    89  	return ids
    90  }
    91  
    92  // MayContainBuilds returns true if the time range can possibly contain builds.
    93  // Zero low/high value means no boundary for low/high.
    94  func MayContainBuilds(low, high time.Time) bool {
    95  	switch {
    96  	case !high.IsZero() && (high.Before(beginningOfTheWorld) || high.Equal(beginningOfTheWorld)):
    97  		return false
    98  	case !low.IsZero() && !high.IsZero() && high.Before(low):
    99  		return false
   100  	default:
   101  		return true
   102  	}
   103  }
   104  
   105  // IDRange converts a creation time range to the build id range.
   106  // Low/high bounds are inclusive/exclusive respectively
   107  // for both time and id ranges.
   108  func IDRange(low, high time.Time) (int64, int64) {
   109  	// Convert the inclusive low time bound to the exclusive high id bound.
   110  	idHigh := idTimeSegment(low.Add(-timeResolution))
   111  	// Convert the exclusive high time bound to the inclusive low id bound.
   112  	idLow := idTimeSegment(high.Add(-timeResolution))
   113  	return idLow, idHigh
   114  }
   115  
   116  // idTimeSegment constructs the build id bits: "0N{43}0{20}",
   117  // where N is the bits converted from time.
   118  // If time equals to beginningOfTheWorld, the id is 0x7FFFFFFFFFF00000.
   119  // If the returned id is 0, it means its time is less than beginningOfTheWorld.
   120  func idTimeSegment(t time.Time) int64 {
   121  	delta := t.Sub(beginningOfTheWorld).Milliseconds()
   122  	if delta < 0 {
   123  		return 0
   124  	}
   125  	// Use bitwise negation to make sure build id is monotonically decreasing.
   126  	// Thus the larger of the time, the smaller of the id.
   127  	return (^delta & ((int64(1) << 43) - 1)) << buildIDTimeSuffixLen
   128  }