feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,105 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type expensiveOperation int
|
||||
|
||||
const (
|
||||
lookupOperation expensiveOperation = iota
|
||||
cdnPreviewOperation
|
||||
revisionPreviewOperation
|
||||
)
|
||||
|
||||
type tenantWork struct {
|
||||
start time.Time
|
||||
counts [3]int
|
||||
active int
|
||||
}
|
||||
|
||||
// operationLimiter is deliberately local to one API process. It never queues
|
||||
// waiters or evicts active tenants to admit new identities.
|
||||
type operationLimiter struct {
|
||||
mu sync.Mutex
|
||||
tenants map[string]*tenantWork
|
||||
active, maxActive, maxTenantActive, maxTenants int
|
||||
rates [3]int
|
||||
nextSweep time.Time
|
||||
}
|
||||
|
||||
func newOperationLimiter() *operationLimiter {
|
||||
return &operationLimiter{
|
||||
tenants: make(map[string]*tenantWork), maxTenants: 4096,
|
||||
maxActive: operationEnvLimit("EVOBGP_API_EXPENSIVE_MAX_CONCURRENT", 4, 64),
|
||||
maxTenantActive: operationEnvLimit("EVOBGP_API_EXPENSIVE_TENANT_CONCURRENT", 2, 64),
|
||||
rates: [3]int{operationEnvLimit("EVOBGP_API_LOOKUP_PER_MINUTE", 120, 100000), operationEnvLimit("EVOBGP_API_CDN_PREVIEW_PER_MINUTE", 30, 100000), operationEnvLimit("EVOBGP_API_REVISION_PREVIEW_PER_MINUTE", 30, 100000)},
|
||||
}
|
||||
}
|
||||
|
||||
func operationEnvLimit(name string, fallback, maximum int) int {
|
||||
n, err := strconv.Atoi(os.Getenv(name))
|
||||
if err != nil || n < 1 || n > maximum {
|
||||
return fallback
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *operationLimiter) acquire(tenant string, op expensiveOperation, now time.Time) (func(), int) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if !now.Before(l.nextSweep) {
|
||||
for key, entry := range l.tenants {
|
||||
if entry.active == 0 && !now.Before(entry.start.Add(time.Minute)) {
|
||||
delete(l.tenants, key)
|
||||
}
|
||||
}
|
||||
l.nextSweep = now.Add(time.Second)
|
||||
}
|
||||
if l.active >= l.maxActive {
|
||||
return nil, 1
|
||||
}
|
||||
entry := l.tenants[tenant]
|
||||
if entry == nil {
|
||||
if len(l.tenants) >= l.maxTenants {
|
||||
return nil, 1
|
||||
}
|
||||
entry = &tenantWork{start: now}
|
||||
l.tenants[tenant] = entry
|
||||
}
|
||||
if !now.Before(entry.start.Add(time.Minute)) {
|
||||
entry.start = now
|
||||
entry.counts = [3]int{}
|
||||
}
|
||||
if entry.active >= l.maxTenantActive {
|
||||
return nil, 1
|
||||
}
|
||||
if entry.counts[op] >= l.rates[op] {
|
||||
wait := entry.start.Add(time.Minute).Sub(now)
|
||||
return nil, max(1, int((wait+time.Second-1)/time.Second))
|
||||
}
|
||||
entry.counts[op]++
|
||||
entry.active++
|
||||
l.active++
|
||||
var once sync.Once
|
||||
return func() { once.Do(func() { l.mu.Lock(); defer l.mu.Unlock(); entry.active--; l.active-- }) }, 0
|
||||
}
|
||||
|
||||
func (s *Server) acquireOperation(w http.ResponseWriter, r *http.Request, tenant string, op expensiveOperation) (func(), bool) {
|
||||
if r.Context().Err() != nil {
|
||||
writeProblem(w, http.StatusRequestTimeout, "Request Timeout", "request cancelled")
|
||||
return nil, false
|
||||
}
|
||||
s.operationOnce.Do(func() { s.operationLimiter = newOperationLimiter() })
|
||||
release, retry := s.operationLimiter.acquire(tenant, op, time.Now())
|
||||
if release == nil {
|
||||
w.Header().Set("Retry-After", strconv.Itoa(retry))
|
||||
writeProblem(w, http.StatusTooManyRequests, "Too Many Requests", "preview/lookup quota exceeded; retry later")
|
||||
return nil, false
|
||||
}
|
||||
return release, true
|
||||
}
|
||||
Reference in new issue
Block a user