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 }