Implement PID-controlled adaptive rate limiting for database operations
- Add LoadMonitor interface in pkg/interfaces/loadmonitor/ for database load metrics - Implement PIDController with filtered derivative to suppress high-frequency noise - Proportional (P): immediate response to current error - Integral (I): eliminates steady-state offset with anti-windup clamping - Derivative (D): rate-of-change prediction with low-pass filtering - Create BadgerLoadMonitor tracking L0 tables, compaction score, and cache hit ratio - Create Neo4jLoadMonitor tracking query semaphore usage and latencies - Add AdaptiveRateLimiter combining PID controllers for reads and writes - Configure via environment variables: - ORLY_RATE_LIMIT_ENABLED: enable/disable rate limiting - ORLY_RATE_LIMIT_TARGET_MB: target memory limit (default 1500MB) - ORLY_RATE_LIMIT_*_K[PID]: PID gains for reads/writes - ORLY_RATE_LIMIT_MAX_*_MS: maximum delays - ORLY_RATE_LIMIT_*_TARGET: setpoints for reads/writes - Integrate rate limiter into Server struct and lifecycle management - Add comprehensive unit tests for PID controller behavior Files modified: - app/config/config.go: Add rate limiting configuration options - app/main.go: Initialize and start/stop rate limiter - app/server.go: Add rateLimiter field to Server struct - main.go: Create rate limiter with appropriate monitor - pkg/run/run.go: Pass disabled limiter for test instances - pkg/interfaces/loadmonitor/: New LoadMonitor interface - pkg/ratelimit/: New PID controller and limiter implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
34
main.go
34
main.go
@@ -23,6 +23,7 @@ import (
|
||||
"next.orly.dev/pkg/database"
|
||||
_ "next.orly.dev/pkg/neo4j" // Import to register neo4j factory
|
||||
"git.mleku.dev/mleku/nostr/encoders/hex"
|
||||
"next.orly.dev/pkg/ratelimit"
|
||||
"next.orly.dev/pkg/utils/interrupt"
|
||||
"next.orly.dev/pkg/version"
|
||||
)
|
||||
@@ -336,6 +337,37 @@ func main() {
|
||||
}
|
||||
acl.Registry.Syncer()
|
||||
|
||||
// Create rate limiter if enabled
|
||||
var limiter *ratelimit.Limiter
|
||||
rateLimitEnabled, targetMB,
|
||||
writeKp, writeKi, writeKd,
|
||||
readKp, readKi, readKd,
|
||||
maxWriteMs, maxReadMs,
|
||||
writeTarget, readTarget := cfg.GetRateLimitConfigValues()
|
||||
|
||||
if rateLimitEnabled {
|
||||
rlConfig := ratelimit.NewConfigFromValues(
|
||||
rateLimitEnabled, targetMB,
|
||||
writeKp, writeKi, writeKd,
|
||||
readKp, readKi, readKd,
|
||||
maxWriteMs, maxReadMs,
|
||||
writeTarget, readTarget,
|
||||
)
|
||||
|
||||
// Create appropriate monitor based on database type
|
||||
if badgerDB, ok := db.(*database.D); ok {
|
||||
limiter = ratelimit.NewBadgerLimiter(rlConfig, badgerDB.DB)
|
||||
log.I.F("rate limiter configured for Badger backend (target: %dMB)", targetMB)
|
||||
} else {
|
||||
// For Neo4j or other backends, create a disabled limiter for now
|
||||
// Neo4j monitor requires access to the querySem which is internal
|
||||
limiter = ratelimit.NewDisabledLimiter()
|
||||
log.I.F("rate limiter disabled for non-Badger backend")
|
||||
}
|
||||
} else {
|
||||
limiter = ratelimit.NewDisabledLimiter()
|
||||
}
|
||||
|
||||
// Start HTTP pprof server if enabled
|
||||
if cfg.PprofHTTP {
|
||||
pprofAddr := fmt.Sprintf("%s:%d", cfg.Listen, 6060)
|
||||
@@ -413,7 +445,7 @@ func main() {
|
||||
}()
|
||||
}
|
||||
|
||||
quit := app.Run(ctx, cfg, db)
|
||||
quit := app.Run(ctx, cfg, db, limiter)
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
||||
for {
|
||||
|
||||
Reference in New Issue
Block a user