f555f41ada
Quantify the latency the mirror load-balancing strategy adds versus a single-URL remote, and lock it in as a regression guard. - selection_bench_test.go: isolates baseURLAttemptOrder / beginAttempt / endAttempt against a zero-value Engine (no network/DB/redis), covering single-URL, round_robin (3/8), least_conn (3/8) and the gauge inc/dec pair, plus RunParallel variants to surface sync.Map/atomic contention. - BENCHMARKS.md: median-of-6 results on a Ryzen 7 4700U with the raw runs and the cache-miss-only caveat. Headline (median of 6, ns/op): single-URL ~133; round_robin ~462 (3) / ~682 (8); least_conn ~2690 (3) / ~15200 (8). Selection runs only on cache-miss, so cache hits pay zero.
157 lines
4.9 KiB
Go
157 lines
4.9 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"git.unkin.net/unkin/artifactapi/pkg/models"
|
|
)
|
|
|
|
// These benchmarks isolate the mirror-selection overhead only: they construct a
|
|
// zero-value Engine (no DB/S3/redis) and call baseURLAttemptOrder /
|
|
// beginAttempt / endAttempt directly, mirroring leastconn_test.go and
|
|
// multibaseurl_test.go. This quantifies how much latency the load-balancing
|
|
// strategy (round_robin vs least_conn) adds versus a single-URL remote. Note
|
|
// that in the live proxy this selection runs only on the cache-miss/upstream
|
|
// path; a cache hit never calls it.
|
|
|
|
// mirrorPool builds a remote with n upstreams (base_url + n-1 mirrorlist
|
|
// entries) under the given strategy.
|
|
func mirrorPool(name, strategy string, n int) models.Remote {
|
|
r := models.Remote{
|
|
Name: name,
|
|
BaseURL: "https://mirror0.example/repo",
|
|
MirrorStrategy: strategy,
|
|
}
|
|
for i := 1; i < n; i++ {
|
|
r.Mirrorlist = append(r.Mirrorlist, fmt.Sprintf("https://mirror%d.example/repo", i))
|
|
}
|
|
return r
|
|
}
|
|
|
|
// skewInflight sets an ascending in-flight load across the pool so least_conn's
|
|
// stable sort has real work to do (mirror0 busiest, last mirror idle).
|
|
func skewInflight(e *Engine, r models.Remote) {
|
|
pool := r.UpstreamPool()
|
|
for i, u := range pool {
|
|
e.inflightCounter(r.Name, u).Add(int64(len(pool) - i))
|
|
}
|
|
}
|
|
|
|
// BenchmarkBaseURLAttemptOrder_SingleURL measures the early-return no-op path
|
|
// (pool of 1): the branch that preserves original single-attempt behavior and
|
|
// must add effectively zero overhead. This is the same code the cache-miss path
|
|
// takes for every non-mirrored remote.
|
|
func BenchmarkBaseURLAttemptOrder_SingleURL(b *testing.B) {
|
|
e := &Engine{}
|
|
r := models.Remote{Name: "solo", BaseURL: "https://mirror0.example/repo"}
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = e.baseURLAttemptOrder(r)
|
|
}
|
|
}
|
|
|
|
// BenchmarkBaseURLAttemptOrder_RoundRobin measures the default strategy: rotate
|
|
// the starting mirror by an atomic cursor and materialize the ordered slice. No
|
|
// in-flight sort.
|
|
func BenchmarkBaseURLAttemptOrder_RoundRobin(b *testing.B) {
|
|
for _, n := range []int{3, 8} {
|
|
b.Run(fmt.Sprintf("pool%d", n), func(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("rr", models.MirrorStrategyRoundRobin, n)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = e.baseURLAttemptOrder(r)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkBaseURLAttemptOrder_LeastConn measures the least_conn strategy: RR
|
|
// rotation plus a stable sort of the pool by the atomic in-flight gauges. Skew
|
|
// is preloaded so the sort compares distinct loads.
|
|
func BenchmarkBaseURLAttemptOrder_LeastConn(b *testing.B) {
|
|
for _, n := range []int{3, 8} {
|
|
b.Run(fmt.Sprintf("pool%d", n), func(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("lc", models.MirrorStrategyLeastConn, n)
|
|
skewInflight(e, r)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = e.baseURLAttemptOrder(r)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkBeginEndAttempt measures the gauge inc/dec pair that brackets each
|
|
// least_conn upstream attempt (LoadOrStore + atomic add, then atomic add back).
|
|
func BenchmarkBeginEndAttempt(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("g", models.MirrorStrategyLeastConn, 3)
|
|
url := r.UpstreamPool()[0]
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
ctr := e.beginAttempt(r, url)
|
|
endAttempt(ctr)
|
|
}
|
|
}
|
|
|
|
// BenchmarkBaseURLAttemptOrder_RoundRobin_Parallel surfaces atomic-cursor
|
|
// contention on the shared rrCounters entry under concurrent selection.
|
|
func BenchmarkBaseURLAttemptOrder_RoundRobin_Parallel(b *testing.B) {
|
|
for _, n := range []int{3, 8} {
|
|
b.Run(fmt.Sprintf("pool%d", n), func(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("rrp", models.MirrorStrategyRoundRobin, n)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_ = e.baseURLAttemptOrder(r)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkBaseURLAttemptOrder_LeastConn_Parallel surfaces sync.Map read
|
|
// contention on the in-flight gauges plus the per-call sort under concurrency.
|
|
func BenchmarkBaseURLAttemptOrder_LeastConn_Parallel(b *testing.B) {
|
|
for _, n := range []int{3, 8} {
|
|
b.Run(fmt.Sprintf("pool%d", n), func(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("lcp", models.MirrorStrategyLeastConn, n)
|
|
skewInflight(e, r)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_ = e.baseURLAttemptOrder(r)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkBeginEndAttempt_Parallel exercises the gauge inc/dec pair under
|
|
// concurrency: all goroutines hammer the same atomic.Int64, the realistic
|
|
// hot-mirror case, to surface counter contention.
|
|
func BenchmarkBeginEndAttempt_Parallel(b *testing.B) {
|
|
e := &Engine{}
|
|
r := mirrorPool("gp", models.MirrorStrategyLeastConn, 3)
|
|
url := r.UpstreamPool()[0]
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
ctr := e.beginAttempt(r, url)
|
|
endAttempt(ctr)
|
|
}
|
|
})
|
|
}
|