feat(analysis): gate page leasing on a vision readiness probe

The analysis worker leases an analyze_page job (incrementing attempts in
SQL) before calling vision, so a stopped/loading vision burns all retries
and writes a permanent failed page_analysis row. With the vision-manager
autoscaler idle-stopping the container, that would poison the queue on
every cold start.

Add an optional VisionReadiness seam (HTTP GET /health in production) wired
via the new env-only ANALYSIS_VISION_HEALTH_URL. When set, the worker parks
without leasing until the probe answers 2xx; jobs stay pending with
attempts untouched and resume the instant vision is ready. None preserves
today's behavior for always-on endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-14 14:57:48 +02:00
parent 2b7a11b480
commit b86aa80c87
6 changed files with 258 additions and 1 deletions

View File

@@ -420,6 +420,24 @@ fn spawn_analysis_daemon(
model: cfg.model.clone(),
max_image_bytes: cfg.max_image_bytes,
});
// When a readiness URL is configured, gate leasing on it so an
// autoscaler that idle-stops the vision container never lets a job burn
// its retries. A dedicated short-timeout client keeps the probe snappy
// and independent of the (long) per-request analysis timeout.
let readiness: Option<Arc<dyn crate::analysis::daemon::VisionReadiness>> =
match &cfg.vision_health_url {
Some(url) if !url.is_empty() => {
let probe = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.context("build vision readiness http client")?;
Some(Arc::new(crate::analysis::daemon::HttpVisionReadiness {
http: probe,
health_url: url.clone(),
}))
}
_ => None,
};
let handle = crate::analysis::daemon::spawn(
db,
CancellationToken::new(),
@@ -428,6 +446,7 @@ fn spawn_analysis_daemon(
workers: cfg.workers,
job_timeout: cfg.job_timeout,
events,
readiness,
},
);
tracing::info!(workers = cfg.workers, model = %cfg.model, "analysis worker daemon started");