openreader/docs-site/docs/deploy/compute-worker.md
Richard R f0e0daae77
feat: improve PDF parsing recovery and reader UI (#98)
* refactor(ui): replace anchor tags with next/link in SidebarNavLink and UserMenu

Update SidebarNavLink to use next/link for navigation instead of anchor tags,
ensuring proper routing and improved accessibility in Next.js. Refactor UserMenu
to remove legacy Link wrappers and directly use SidebarNavLink for signin and
signup links. This streamlines navigation components and aligns with Next.js
best practices.

* fix(pdf): handle non-zero viewport origins and improve layout model ordering

Update normalizeTextItemsForLayout to correctly apply viewport transforms,
including non-zero page origins, ensuring accurate mapping of PDF text items
to top-left coordinates. Refactor runLayoutModel to implement a custom order
sequence builder for layout regions using model order logits, improving
region ordering consistency with model semantics. Update related tests to
cover viewport transform edge cases.

* refactor(pdf): improve text normalization with font ascent and vertical overlap logic

Enhance text normalization by incorporating font ascent and descent data to more accurately position glyphs, especially for decorative initials. Update merge logic to better detect line membership using vertical overlap, ensuring drop caps and overlapping glyphs are merged correctly. Extend tests to cover these layout scenarios.

* style(range): redesign slider with precision gauge and ruler ticks

Revamp the range input to feature a minimalist "precision gauge" style.
Introduce a hairline rail, ruler notches for discrete steps, and a slim
needle thumb. Add CSS variables and logic for per-instance tick sizing
and coloring. Remove bulky inline class-based styling in favor of
centralized CSS for improved maintainability and visual clarity.

* refactor(ui): modularize PDF loader and range slider visuals

Move PDF layout scan visualization and range slider styles into dedicated CSS
modules, isolating their styles from the global scope. Integrate PdfLayoutScan
component into the PDF viewer loader UI for animated parse progress. Refactor
progress bars to use a reusable progress-fill class with animated sheen effect.
Update range input to use CSS module for precision gauge styling.

* style(reader): remove grid overlay from PdfLayoutScan visualization

* refactor(api): add staleness detection for inflight worker operation states

Integrate isWorkerOperationStateStale checks into document parse API endpoints to ensure inflight worker operation states are not reused if stale. Introduce helper for staleness detection and corresponding unit tests. Enhance SSE event streaming with keepalive intervals and improve progress acknowledgment error handling.

* feat(worker): recover and fail stale in-flight pdf ops on startup

Add orphaned operation recovery logic to detect and mark stale in-flight pdf_layout jobs as failed during worker startup. Extend OperationStateStore with listOpStates for state enumeration. Update tests and documentation to cover recovery behavior and new environment variable COMPUTE_PDF_JOB_ATTEMPTS.

* refactor(worker): distinguish staleness thresholds for running and queued pdf ops

Update orphan recovery logic to apply separate timeouts for 'running' and
'queued' pdf_layout operations. Adjust tests to verify that only stale
'running' operations are failed, while stale 'queued' operations remain
untouched.

* feat(worker): extend orphan recovery to handle whisper_align ops and improve logging

Update orphan recovery to detect and fail stale 'running' whisper_align operations
in addition to pdf_layout. Refactor recovery logic to generalize staleness checks
across operation kinds and enhance log output with detailed operation info.
Expand tests to verify correct handling of both whisper_align and pdf_layout
operations in running and queued states.

* refactor(control-plane): introduce revision-based CAS for operation state updates

Add revision tracking and compare-and-set (CAS) semantics to operation state
stores, enabling atomic state transitions and preventing lost updates. Extend
the OperationStateStore interface with getOpStateRecord and compareAndSetOpState
methods. Update orchestrator and worker runtime to utilize CAS for marking
operations as failed only if the state is unchanged. Enhance in-memory,
JetStream, and test control plane implementations to support revision logic.

This change improves concurrency safety and correctness of operation state
management across distributed components.

* feat(ui): add parse failure state to PDF layout scan animation

Display a distinct "parse halted" visual state in the PDF layout scan
component and PDF viewer page when parsing fails. The loader animation
is replaced by a static, dimmed page with an alert glyph and updated
styling, ensuring users are not misled by an active animation after a
failure. CSS and component logic updated to support the new state.

* feat(worker): extract orphaned operation recovery to module with periodic sweep

Move orphaned operation recovery logic into a dedicated orphan-recovery module,
introducing a periodic sweep timer that triggers recovery every 15 seconds while
the worker is connected. Refactor runtime to delegate orphan detection and
handling to the new module, improving modularity and maintainability. Add
unit tests for orphan-recovery to ensure correctness.

* fix(ui): adjust PDF viewer layout and update parse loader description

* refactor(pdf): streamline layout model region extraction and update test coverage

- Replace custom order sequence logic with softmax-based class selection in runLayoutModel
- Remove unused sigmoid and buildOrderSequence functions
- Simplify detection loop to filter and map regions directly
- Add targeted tests for layout model extraction logic
- Update CSS animation naming for consistency
- Clarify test description and add inline comments for orphan recovery scenario

* fix(pdf): add strict validation for layout model output shapes and extend test coverage

Add explicit error handling for invalid or inconsistent pred_boxes and logits array lengths in runLayoutModel to prevent silent failures. Expand test suite to verify correct region filtering and error scenarios, ensuring only labeled regions are returned and malformed outputs are handled robustly.
2026-06-03 04:44:55 -06:00

4.4 KiB

title description
Compute Worker Deploy the standalone worker used for Whisper alignment and PDF layout parsing.

Use this guide when OpenReader runs compute as a separate service. For the default embedded/local flow (pnpm dev or pnpm start without COMPUTE_WORKER_URL), configure the root .env instead and see Local Development.

What the worker does

  • Runs Whisper word alignment jobs
  • Runs PDF layout parsing jobs
  • Stores durable job state in NATS JetStream and NATS KV

The app server submits work to POST /ops and listens for updates on GET /ops/:opId/events.

When to use it

  • Required for Vercel-style deployments where heavy compute must run outside the app server
  • Useful when you want a dedicated compute host
  • Not needed for the default embedded local flow

Container image

  • ghcr.io/richardr1126/openreader-compute-worker:latest

Worker environment

Required worker variables:

COMPUTE_WORKER_TOKEN=...
NATS_URL=nats://...
S3_BUCKET=...
S3_REGION=...
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...

:::important compute/worker/.env* is only for standalone worker deployments.

  • Embedded/local mode: configure the root .env only.
  • External worker mode: set COMPUTE_WORKER_URL and COMPUTE_WORKER_TOKEN on the app, and worker runtime values on the worker service.
  • Keep shared values aligned across app and worker: COMPUTE_WORKER_TOKEN, S3_*, COMPUTE_WHISPER_TIMEOUT_MS, COMPUTE_PDF_TIMEOUT_MS, COMPUTE_PDF_JOB_ATTEMPTS, and COMPUTE_OP_STALE_MS. :::

Common optional variables:

  • NATS_CREDS or NATS_CREDS_FILE
  • S3_ENDPOINT, S3_FORCE_PATH_STYLE=true, S3_PREFIX=openreader
  • COMPUTE_WORKER_HOST=0.0.0.0
  • PORT=8081 for local/manual runs. Platforms like Railway usually inject PORT.
  • LOG_FORMAT=json and COMPUTE_LOG_LEVEL=info
  • COMPUTE_PREWARM_MODELS=false by default. Set it to true to pre-download ONNX models during worker startup.
  • COMPUTE_JOB_CONCURRENCY=1
  • COMPUTE_WHISPER_TIMEOUT_MS=30000
  • COMPUTE_PDF_TIMEOUT_MS=300000
  • COMPUTE_PDF_JOB_ATTEMPTS=1
  • COMPUTE_JOBS_STREAM_MAX_BYTES=268435456
  • COMPUTE_EVENTS_STREAM_MAX_BYTES=134217728
  • COMPUTE_JOB_STATES_MAX_BYTES=67108864
  • COMPUTE_NATS_REPLICAS=1
  • COMPUTE_OP_STALE_MS=1800000
  • WHISPER_MODEL_BASE_URL
  • PDF_LAYOUT_MODEL_BASE_URL

If you need the broader app config reference, see Environment Variables.

App server environment

Set these on the Next.js app server:

COMPUTE_WORKER_URL=https://worker.example.com
COMPUTE_WORKER_TOKEN=<same-token-as-worker>
# Optional shared overrides:
# COMPUTE_WHISPER_TIMEOUT_MS=30000
# COMPUTE_PDF_TIMEOUT_MS=300000
# COMPUTE_PDF_JOB_ATTEMPTS=1
# COMPUTE_OP_STALE_MS=1800000

Notes:

  • Model artifact overrides (WHISPER_MODEL_BASE_URL, PDF_LAYOUT_MODEL_BASE_URL) belong on the worker service, not the app server.
  • There is no app-local compute fallback once COMPUTE_WORKER_URL is set. If the worker is unavailable, worker-backed requests fail.

Deployment notes

  • App and worker must share the same object storage.
  • Embedded weed mini is not supported for external worker mode.
  • Protect COMPUTE_WORKER_TOKEN and do not expose worker routes without auth.
  • The worker connects to NATS lazily and disconnects after 120 seconds of full idle time. That allows platforms like Railway to sleep the service, but the first request after a cold start will be slower.

Health endpoints

  • GET /health/live returns { ok: true }.
  • GET /health/ready returns { ok: true, natsConnected } and reflects the current NATS session without forcing a reconnect.

Railway + Synadia example

Deploy the worker image to Railway and set worker env vars similar to:

COMPUTE_WORKER_HOST=0.0.0.0
COMPUTE_WORKER_TOKEN=<shared-token>
NATS_URL=tls://connect.ngs.global:4222
NATS_CREDS="-----BEGIN NATS USER JWT-----
...
------END USER NKEY SEED------"
S3_BUCKET=<bucket>
S3_REGION=<region>
S3_ACCESS_KEY_ID=<key>
S3_SECRET_ACCESS_KEY=<secret>
# Optional:
# S3_ENDPOINT=https://...
# S3_FORCE_PATH_STYLE=true
# S3_PREFIX=openreader

If your platform supports mounted files, you can use NATS_CREDS_FILE instead of NATS_CREDS.

Set these on the OpenReader app server:

COMPUTE_WORKER_URL=https://<railway-worker-domain>
COMPUTE_WORKER_TOKEN=<same-token-as-worker>

Verify the worker after deploy:

  • GET https://<railway-worker-domain>/health/live
  • GET https://<railway-worker-domain>/health/ready