carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "math"
5 "time"
6)
7
8// The T11 cut budget (moves spec §7.3 T11, §7.4): pure arithmetic that
9// prices a move's stop-to-open time from measured rates and decides how
10// many probe attempts fit the operator's window. Ported from the model in
11// internal/sim/hostlife/budget_test.go at 9c417aaa; the constants below
12// that duplicate the model's (cutWindowDefault, probeAttemptsMax,
13// probeSeconds, settleSeconds, admitSeconds, mirrorBarMiB) are twins of it
14// and change together. All times are seconds, all sizes and rates MiB.
15
16const (
17 CutWindowDefault = 120.0 // the operator's default cut window
18 OpenBudget = 5.0 // the target's open after the commit (§7.4)
19 ProbeAttemptsMax = 3 // §7.7: never more than three attempts
20 ProbeSeconds = 30.0
21 SettleSeconds = 20.0
22 AdmitSeconds = 2.0 // the admitting CAS, the purge and generation.json
23 MirrorBarMiB = 16.0 // §7.3: the final mirror round is at most this much
24 ArchiveSeconds = 2.0 // the archive phase's floor (ruling T7-budget)
25)
26
27// CutEstimate is a cut priced phase by phase.
28type CutEstimate struct {
29 Stop, Checkpoint, Mirror, Archive, Render, Open float64
30 RestoreStaged, SnapshotBuild, Anchor, Verify float64 // target-side build, whole database
31 RestoreAttempt, Admit, FreshUpload float64 // per probe attempt
32 Probe, Settle float64 // one attempt
33}
34
35// Rates are the measured rates T11 reads, in MiB/s: the cross-region copy
36// path, regional S3 (reads and writes from a box in the bucket's region)
37// and the box's disk. MirrorBar is the final mirror round's size bar in
38// MiB; zero means MirrorBarMiB. It is the one knob Task 11 turns
39// (CARLOS_MOVE_READY_DELTA_BYTES, ruling F28(e)). FsyncRate is the target
40// disk's per-file sync rate in files per second (ruling T7-budget): the
41// landing's extract syncs every file, so the archive's entry count prices
42// the Archive phase.
43type Rates struct {
44 Cross, S3, Disk float64
45 MirrorBar float64
46 FsyncRate float64
47}
48
49// ExampleRates are the rates the spec's worked example uses (§7.3 T11):
50// 10 MiB/s across regions, 250 MiB/s to regional S3, 500 MiB/s on disk,
51// and (not in the spec's example, which has no files) 1600 files a second
52// synced, about what Task 7 measured for its 16-worker pool. Tests and
53// documentation only: T11 reads measured rates.
54var ExampleRates = Rates{Cross: 10, S3: 250, Disk: 500, FsyncRate: 1600}
55
56// Estimate prices a cut for a database of dbMiB at the given rates. The
57// final mirror is within the bar across regions. On the target every
58// build pass moves the whole database once across S3 and once across the
59// disk, bar the verification, which reads the disk alone. Each probe
60// attempt restores the prepared generation again and pays the admission;
61// a retried probe also uploads the whole database as a fresh generation.
62// The archive phase is at least ArchiveSeconds, and longer when the
63// archive's entries take longer than that to sync on the target at
64// r.FsyncRate (ruling T7-budget): max(2, entries/FsyncRate).
65//
66// An input that is not a usable measurement (a rate that is not a positive
67// finite number, a negative, NaN or infinite database size or bar, a
68// negative entry count, or entries with no usable fsync rate) prices the cut
69// at infinity, so it is refused: a negative rate must never price a phase
70// below zero and let a move in. With no entries the fsync rate is not
71// needed, but a negative, NaN or infinite one is still refused.
72func Estimate(dbMiB float64, entries int, r Rates) CutEstimate {
73 if !usable(r.Cross) || !usable(r.S3) || !usable(r.Disk) ||
74 !finite(dbMiB) || dbMiB < 0 || !finite(r.MirrorBar) || r.MirrorBar < 0 ||
75 entries < 0 || !finite(r.FsyncRate) || r.FsyncRate < 0 || (entries > 0 && !usable(r.FsyncRate)) {
76 inf := math.Inf(1)
77 return CutEstimate{Stop: inf, Checkpoint: inf, Mirror: inf, Archive: inf, Render: inf, Open: inf}
78 }
79 bar := r.MirrorBar
80 if bar == 0 {
81 bar = MirrorBarMiB
82 }
83 archive := ArchiveSeconds
84 if entries > 0 {
85 archive = math.Max(ArchiveSeconds, float64(entries)/r.FsyncRate)
86 }
87 return CutEstimate{
88 Stop: 2, Checkpoint: 1, Archive: archive, Render: 1, Open: OpenBudget,
89 Mirror: bar / r.Cross,
90 RestoreStaged: dbMiB/r.S3 + dbMiB/r.Disk,
91 SnapshotBuild: dbMiB/r.Disk + dbMiB/r.S3,
92 Anchor: dbMiB/r.Disk + dbMiB/r.S3,
93 Verify: dbMiB / r.Disk,
94 RestoreAttempt: dbMiB/r.S3 + dbMiB/r.Disk,
95 Admit: AdmitSeconds,
96 FreshUpload: dbMiB/r.Disk + dbMiB/r.S3,
97 Probe: ProbeSeconds, Settle: SettleSeconds,
98 }
99}
100
101// Fixed is the cost of every phase outside the probe attempts.
102func (e CutEstimate) Fixed() float64 {
103 return e.Stop + e.Checkpoint + e.Mirror + e.Archive + e.RestoreStaged + e.SnapshotBuild + e.Anchor + e.Verify + e.Render + e.Open
104}
105
106// Attempt prices the i-th probe attempt (from 1): the restore of the
107// prepared generation and the admission overhead, the probe and its
108// settle; from the second attempt on, the fresh generation's first upload
109// as well.
110func (e CutEstimate) Attempt(i int) float64 {
111 c := e.RestoreAttempt + e.Admit + e.Probe + e.Settle
112 if i > 1 {
113 c += e.FreshUpload
114 }
115 return c
116}
117
118// AttemptsAdmitted is T11: the attempts permitted are those whose summed
119// costs fit the window after the fixed costs, at most ProbeAttemptsMax.
120// The instance is eligible when that is at least one, and the probe loop
121// runs at most that many.
122func (e CutEstimate) AttemptsAdmitted(window float64) int {
123 left := window - e.Fixed()
124 n := 0
125 for n < ProbeAttemptsMax && left >= e.Attempt(n+1) {
126 left -= e.Attempt(n + 1)
127 n++
128 }
129 return n
130}
131
132// Total prices the cut with n attempts.
133func (e CutEstimate) Total(n int) float64 {
134 t := e.Fixed()
135 for i := 1; i <= n; i++ {
136 t += e.Attempt(i)
137 }
138 return t
139}
140
141// CutDeadline is §7.4's one absolute deadline: cut_at + cut_budget -
142// open_budget. Every phase before the commit checks it; the commit CAS is
143// conditional on it.
144func CutDeadline(cutAt time.Time, windowSeconds float64) time.Time {
145 return cutAt.Add(time.Duration((windowSeconds - OpenBudget) * float64(time.Second)))
146}
147
148func finite(x float64) bool { return !math.IsNaN(x) && !math.IsInf(x, 0) }
149
150// usable is a rate that can be divided by: positive and finite.
151func usable(rate float64) bool { return finite(rate) && rate > 0 }
152