Skip to content

feat(pii): env-driven uvicorn worker count (PII_WORKERS)#5457

Merged
TheodoreSpeaks merged 2 commits into
stagingfrom
feat/pii-workers-env
Jul 7, 2026
Merged

feat(pii): env-driven uvicorn worker count (PII_WORKERS)#5457
TheodoreSpeaks merged 2 commits into
stagingfrom
feat/pii-workers-env

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Launch the Presidio service with --workers from a PII_WORKERS env var, so one image scales per task size (set PII_WORKERS = the task's vCPU count) with no rebuild.
  • sh -c exec expands the var while keeping uvicorn as PID 1 for clean SIGTERM. Defaults to 1, so local/self-hosted behavior is unchanged.

Why

Each Presidio task currently runs a single uvicorn worker → uses 1 of its 2 vCPUs, so the fleet processes very few NER requests in parallel while the app fans out many → queueing → ALB 504s under load. Making worker count env-driven lets us run one worker per vCPU without per-env images.

Coupling (infra PR)

Each worker loads the spaCy models independently (~3.3 GB measured), so task memory must be sized to PII_WORKERS × ~3.3 GB + overhead. The infra PR sets PII_WORKERS (derived from vCPU) and bumps task cpu/memory accordingly, plus raises the ALB idle timeout and caps the app fan-out.

Testing

  • Rebuilt the image and ran locally: PII_WORKERS=2 → uvicorn multi-worker mode (Started parent process); PII_WORKERS=1 → single worker, /health + /redact_batch OK. Measured 3.28 GB RSS per worker.

Type of Change

  • Performance improvement

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

🤖 Generated with Claude Code

Launch the Presidio service with --workers from the PII_WORKERS env var so one
image scales per task size (set PII_WORKERS = the task's vCPU count) without a
rebuild. `sh -c exec` expands the var while keeping uvicorn as PID 1 for clean
SIGTERM. Defaults to 1, so local/self-hosted is unchanged. Each worker loads the
spaCy models independently (~3.3GB measured), so task memory must be sized to
PII_WORKERS x ~3.3GB + overhead (set in infra alongside PII_WORKERS).
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 7, 2026 1:19am

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Multi-worker mode multiplies per-worker memory (~3 GB each) and changes process lifecycle under load; misconfigured PII_WORKERS or undersized task memory could cause OOM or failed health checks until infra is aligned.

Overview
The Presidio container no longer hard-codes a single uvicorn process. Startup uses sh -c exec so PII_WORKERS (default 1) sets --workers, keeping uvicorn as PID 1 for SIGTERM while letting one image scale workers to match task vCPUs without a rebuild.

Health check start-period increases from 180s → 300s because multiple workers each load the five spaCy models in parallel, stretching cold-start time beyond the single-worker case.

Comments document sizing guidance: ~3 GB RAM per worker and quoting the env expansion so bad values fail uvicorn parsing instead of shell misinterpretation.

Reviewed by Cursor Bugbot for commit 044eb3d. Bugbot is set up for automated code reviews on this repo. Configure here.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds env-driven uvicorn worker scaling to the Presidio PII service: PII_WORKERS (default 1) controls the number of worker processes at runtime without rebuilding the image. The healthcheck --start-period is also bumped from 180s to 300s to account for parallel model loading by multiple workers.

  • CMD is changed from exec-form uvicorn to sh -c exec uvicorn ... --workers \"${PII_WORKERS:-1}\", keeping uvicorn as PID 1 for clean SIGTERM handling while allowing env-var expansion.
  • Healthcheck cold-start window increased from 180s to 300s to cover the case where N workers simultaneously load the five spaCy models on container startup.

Confidence Score: 5/5

The change is a focused one-file edit — a new shell CMD form and a larger HEALTHCHECK window — with no application logic touched and a safe default of 1 worker when the env var is absent.

Both the env-var expansion and the healthcheck timing were carefully considered by the author: double-quoting the expansion limits misparse risks, the exec prefix preserves PID 1 semantics for SIGTERM, and the 300s window gives meaningful headroom for multi-worker cold starts. No new code paths are introduced beyond the subprocess launch form.

No files require special attention; the single changed file is a Dockerfile with a minimal, well-documented modification.

Important Files Changed

Filename Overview
docker/pii.Dockerfile Adds env-driven worker count via PII_WORKERS, switches CMD to sh -c exec form for variable expansion while retaining PID 1 ownership, and bumps HEALTHCHECK start-period from 180s to 300s for multi-worker cold starts.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Docker starts container] --> B["sh -c exec uvicorn ... --workers ${PII_WORKERS:-1}"]
    B --> C{PII_WORKERS set?}
    C -- No --> D["Default: --workers 1\n(single-process mode)"]
    C -- Yes --> E["--workers N\n(multi-process mode)"]
    D --> F[uvicorn master becomes PID 1]
    E --> F
    F --> G[Fork N worker processes]
    G --> H[Each worker imports server.py\n~3.3 GB spaCy models each]
    H --> I{All workers healthy?}
    I -- Yes, within 300s start-period --> J[HEALTHCHECK passes\nGET /health → 200]
    I -- Timeout > 300s --> K[ECS marks task unhealthy\ncontainer recycled]
    J --> L[Serve traffic on :5001]
    M[SIGTERM to PID 1] --> N[uvicorn master\ngracefully shuts workers]
    N --> O[Workers finish in-flight requests]
    O --> P[Container exits cleanly]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Docker starts container] --> B["sh -c exec uvicorn ... --workers ${PII_WORKERS:-1}"]
    B --> C{PII_WORKERS set?}
    C -- No --> D["Default: --workers 1\n(single-process mode)"]
    C -- Yes --> E["--workers N\n(multi-process mode)"]
    D --> F[uvicorn master becomes PID 1]
    E --> F
    F --> G[Fork N worker processes]
    G --> H[Each worker imports server.py\n~3.3 GB spaCy models each]
    H --> I{All workers healthy?}
    I -- Yes, within 300s start-period --> J[HEALTHCHECK passes\nGET /health → 200]
    I -- Timeout > 300s --> K[ECS marks task unhealthy\ncontainer recycled]
    J --> L[Serve traffic on :5001]
    M[SIGTERM to PID 1] --> N[uvicorn master\ngracefully shuts workers]
    N --> O[Workers finish in-flight requests]
    O --> P[Container exits cleanly]
Loading

Reviews (3): Last reviewed commit: "harden(pii): quote PII_WORKERS expansion..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds env-driven uvicorn worker scaling to the Presidio PII service by replacing the static CMD with a sh -c exec pattern that reads PII_WORKERS at container start, defaulting to 1. This allows a single image to scale across task sizes without a rebuild, and the exec ensures uvicorn remains PID 1 for clean signal handling.

  • docker/pii.Dockerfile: The old exec-form CMD is replaced with a shell-form that expands ${PII_WORKERS:-1} before handing off to uvicorn via exec. The base image is python:3.12-slim-bookworm (Debian dash as /bin/sh), which fully supports the ${VAR:-default} substitution syntax.
  • No input validation on PII_WORKERS: a non-integer or shell-metacharacter value will cause an opaque startup failure or (in the worst case) arbitrary command execution — low practical risk given infrastructure-controlled env vars, but worth hardening.

Confidence Score: 4/5

Safe to merge for the core use case; the only open questions are input validation on the worker count and whether the 180s health-check window holds under a cold multi-worker start.

The change is minimal and well-reasoned: sh -c exec correctly promotes uvicorn to PID 1, the default of 1 preserves existing local/single-worker behavior, and Debian dash fully supports ${VAR:-default}. The two gaps — no guard against a malformed PII_WORKERS value causing a cryptic startup failure, and the hardcoded 180s start period that was never validated against a multi-worker cold boot — are non-blocking but worth addressing before scaling to higher worker counts in production.

docker/pii.Dockerfile — the CMD line and the HEALTHCHECK start-period both warrant a second look when PII_WORKERS will be set to values greater than 2.

Important Files Changed

Filename Overview
docker/pii.Dockerfile CMD updated to use sh -c exec pattern with ${PII_WORKERS:-1} env var to allow env-driven uvicorn worker scaling without rebuilding the image; shell injection risk exists if the env var is ever exposed to untrusted input

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Container starts] --> B{PII_WORKERS set?}
    B -- No --> C[Default: --workers 1]
    B -- Yes --> D[Use PII_WORKERS value]
    C --> E[sh -c exec uvicorn ...]
    D --> E
    E --> F[exec replaces sh - uvicorn is PID 1]
    F --> G[uvicorn master binds :5001]
    G --> H[Spawns N worker processes]
    H --> I[Each worker loads spaCy models independently ~3.3 GB/worker]
    I --> J[Workers ready - /health responds 200]
    J --> K[HEALTHCHECK passes within 180s start-period]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Container starts] --> B{PII_WORKERS set?}
    B -- No --> C[Default: --workers 1]
    B -- Yes --> D[Use PII_WORKERS value]
    C --> E[sh -c exec uvicorn ...]
    D --> E
    E --> F[exec replaces sh - uvicorn is PID 1]
    F --> G[uvicorn master binds :5001]
    G --> H[Spawns N worker processes]
    H --> I[Each worker loads spaCy models independently ~3.3 GB/worker]
    I --> J[Workers ready - /health responds 200]
    J --> K[HEALTHCHECK passes within 180s start-period]
Loading

Reviews (2): Last reviewed commit: "feat(pii): env-driven uvicorn worker cou..." | Re-trigger Greptile

Comment thread docker/pii.Dockerfile Outdated
Comment thread docker/pii.Dockerfile Outdated
…riod for multi-worker

- Quote ${PII_WORKERS} so a malformed value fails uvicorn arg-parsing instead of
  being shell-interpreted (verified: '1; echo X' rejected as non-integer, X not run)
- Bump HEALTHCHECK start-period 180s -> 300s: N workers load the spaCy models in
  parallel, stretching cold start beyond the single-worker case
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 044eb3d. Configure here.

Comment thread docker/pii.Dockerfile
# `sh -c exec` expands the env var while keeping uvicorn as PID 1 for clean SIGTERM.
# Quote the expansion so a malformed PII_WORKERS fails uvicorn arg-parsing rather
# than being interpreted by the shell.
CMD ["sh", "-c", "exec uvicorn server:app --host 0.0.0.0 --port 5001 --workers \"${PII_WORKERS:-1}\""]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supervisor duplicates spaCy model RAM

High Severity

With PII_WORKERS greater than one, the new --workers flag turns on uvicorn’s multiprocess supervisor. The image’s pinned uvicorn 0.49.0 eagerly imports server:app in that supervisor before workers start, so the five spaCy models load once in the parent and again in each worker. Task memory sized as PII_WORKERS × ~3 GB can OOM or thrash during rollout.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 044eb3d. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine. I can tune this manually.

@TheodoreSpeaks TheodoreSpeaks merged commit 07a1224 into staging Jul 7, 2026
18 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/pii-workers-env branch July 7, 2026 01:23
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