Skip to content

Bound docling-serve load from public readiness checks - #11

Merged
szweibel merged 1 commit into
mainfrom
fix/readiness-docling-probe
Sep 24, 2026
Merged

szweibel merged 1 commit into
mainfrom
fix/readiness-docling-probe

Conversation

@szweibel

Copy link
Copy Markdown
Contributor

Problem

/health/ready is exempt from identity (_PUBLIC_IDENTITY_PATHS) and is exposed publicly at tools.ailab.gc.cuny.edu/pdf-accessibility/health/ready. On every call, _docling_check opened a new client and sent a bearer-authenticated GET {DOCLING_SERVE_URL}/health with up to 3 connect attempts. The number of outbound requests to docling-serve on the Tailscale host scaled directly with anonymous request volume.

Change

  • The remote probe moves into _probe_docling_serve. _cached_docling_serve_probe shares a single probe task per (URL, token) for 30 seconds. Concurrent callers await the same in-flight task, and later callers in the window reuse its result. Failures are cached too, so an unreachable docling-serve isn't hit repeatedly. asyncio.shield stops one client disconnect from cancelling the shared probe.
  • Outbound load is now at most one probe per 30 s per process, whatever the inbound traffic.
  • The README documents the probe interval.

Why not move the probe off public readiness

  • /health/ready has one consumer: the post-deploy curl -fsS in the README, run against both local and public URLs. The Docker HEALTHCHECK and CI smoke test use /health.
  • The NML deploy uses the slim image, so it can't process PDFs without docling-serve. Dropping reachability from readiness would let that check pass on a deploy that can't do work.
  • No orchestrator routes traffic by readiness, so a cold or slow docling-serve only affects the report, not whether jobs are accepted. Jobs still retry Docling on their own path.
  • The app has no admin-authenticated diagnostics surface. Adding one would be new scope.

The response shape, the 200/503 semantics and the checks.docling.metadata.health_check values are unchanged.

Tests

  • test_concurrent_readiness_requests_share_one_remote_docling_probe: 25 concurrent calls plus one sequential call result in 1 outbound request.
  • test_remote_docling_probe_failure_is_cached_until_window_expires: a failure is reused until the window ends, then re-probed (clock is monkeypatched).
  • An autouse fixture clears the cache between tests.
  • ./scripts/check.sh passes: 535 backend tests, frontend lint and build.

🤖 Generated with Claude Code

/health/ready is public and probed the remote docling-serve (with the
bearer token and connect retries) on every request, so caller volume
translated directly into load on docling-serve. Share one in-flight or
recent probe per (URL, token) for 30 seconds; concurrent and repeated
readiness requests reuse it, including failures.

The readiness contract is unchanged: docling-serve reachability still
gates "ready", which the post-deploy curl relies on for slim images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@szweibel
szweibel merged commit 84333c8 into main Sep 24, 2026
1 check passed
@szweibel
szweibel deleted the fix/readiness-docling-probe branch September 24, 2026 14:30
szweibel added a commit that referenced this pull request Oct 4, 2026
Brings in PR #11 (bound docling-serve load from public readiness checks).
The only conflict was test_readiness.py's imports: the branch builds real
Settings instead of SimpleNamespace, and main added asyncio for its
concurrency test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant