Decompose: Documented concurrency contract (throughput, per-request bounds, failure semantics) #43

Closed
opened 2026-06-22 16:23:20 +00:00 by Mark · 1 comment
Owner

Documented concurrency contract for the service

Context

The HTTP service (issue #31) needs explicit answers to 7 concurrency
questions. Without documented values, every implementer has to guess:
what's the max concurrent digitize? per-request timeout? memory cap?
what happens on overload? partial failure? cancellation? The guesses
will diverge.

Goal

docs/concurrency.md with concrete, testable values for every
question. A load test that asserts the documented behavior.

Decomposition

The 7 questions cluster into 3 categories. Each phase answers one
category and writes the relevant doc sections + tests.

P1: Throughput answers (questions 1, 5, 6)

Goal: Lock the throughput policy.

Specifically:

  1. Max concurrent digitize requests? Answer: 4 (= N CPU cores on
    the target box; configurable via WORKER_POOL_SIZE).
  2. Queue ordering? Answer: FIFO. No priority by user or by input
    size.
  3. Backpressure signal? Answer: HTTP 503 with Retry-After: N
    header, where N is the p50 queue wait time. Document the exact
    header shape.

Files:

  • New: docs/concurrency.md (initial version with these 3 questions
    answered; the other 4 follow in P2/P3)
  • Modify: docs/service/operations.md (issue #40 P2) to reference
    the concurrency policy

Acceptance:

  • docs/concurrency.md has a section per answered question with the
    value, the rationale, and the source (benchmark, calculation, or
    decision doc).
  • The values are testable: a load test can assert "5 concurrent
    requests get 503 with Retry-After" against the spec.
  • The ThreadCraft team agrees on the values.

Why first: throughput answers constrain the design of the worker
pool and the request queue. They should be locked before any service
code is written.


P2: Per-request bounds (questions 2, 3, 4)

Goal: Lock the per-request resource limits.

Specifically:

  1. Per-request timeout? Answer: 60 seconds for digitize, 30
    seconds for export. Justify: a 4 MP image at 0.4mm density is
    ~600K stitches, generated in < 5 seconds; 60s gives 12× headroom.
  2. Per-request memory cap? Answer: 1024 MB. Justify: the raster
    pipeline at 4 MP peaks at ~800 MB.
  3. Cancellation? Answer: yes — when the client disconnects, the
    worker thread is interrupted within 1 second (set a flag the
    pipeline checks between stages; full thread cancellation is unsafe
    because of numpy/scipy GIL interactions).

Files:

  • Modify: docs/concurrency.md (add these 3 answers)

Acceptance:

  • All 3 questions answered with value, rationale, source.
  • A test asserts that a request exceeding the timeout returns 504
    Gateway Timeout.
  • A test asserts that a request exceeding the memory cap returns
    503 with a clear error.
  • A test asserts that cancelling the client connection causes the
    worker to stop within 1 second.

Depends on: P1 (overall policy).


P3: Failure semantics (questions 7, plus the "what if stage N fails?" case)

Goal: Lock the failure-mode behavior.

Specifically:

  1. Failure mode on partial completion? Answer: if a stage fails,
    the response is 503 with body {"error": "...", "failed_stage": "...", "completed_stages": [...], "request_id": "..."}. The
    pipeline cleans up any partial state (temp files, half-built
    stitch objects).
  • Plus: what happens on invalid input (malformed PNG, oversize
    upload, unsupported format)? Answer: 400 Bad Request with a
    specific error code per failure mode (NOT 500).
  • Plus: what's the log format on errors? Answer: structured JSON
    with request_id, stage, error, stack (configurable).

Files:

  • Modify: docs/concurrency.md (add failure-mode section)
  • Modify: docs/service/operations.md (cross-link from the error
    response shape)

Acceptance:

  • All failure modes documented with response shape and HTTP code.
  • A test for each documented failure mode asserts the response.
  • An integration test simulates a stage failure mid-pipeline and
    asserts the partial-state cleanup happened (no orphan temp files).

Depends on: P1, P2.

Out of scope

  • The actual implementation of the worker pool, queue, timeout, and
    cancellation logic. Those go in the relevant service tickets
    (issue #31 P2 for the handlers, P5 for the worker pool).
  • Operational concerns beyond the request lifecycle (rolling deploys,
    A/B testing, blue-green) — separate doc, separate ticket.
  • Per-user rate limiting — not in scope for v1 (single-user Tailnet).
    If multi-user support is added later, a rate-limit section can be
    added to docs/concurrency.md.
# Documented concurrency contract for the service ## Context The HTTP service (issue #31) needs explicit answers to 7 concurrency questions. Without documented values, every implementer has to guess: what's the max concurrent digitize? per-request timeout? memory cap? what happens on overload? partial failure? cancellation? The guesses will diverge. ## Goal `docs/concurrency.md` with concrete, testable values for every question. A load test that asserts the documented behavior. ## Decomposition The 7 questions cluster into 3 categories. Each phase answers one category and writes the relevant doc sections + tests. ### P1: Throughput answers (questions 1, 5, 6) **Goal:** Lock the throughput policy. **Specifically:** 1. **Max concurrent digitize requests?** Answer: 4 (= N CPU cores on the target box; configurable via `WORKER_POOL_SIZE`). 5. **Queue ordering?** Answer: FIFO. No priority by user or by input size. 6. **Backpressure signal?** Answer: HTTP 503 with `Retry-After: N` header, where N is the p50 queue wait time. Document the exact header shape. **Files:** - New: `docs/concurrency.md` (initial version with these 3 questions answered; the other 4 follow in P2/P3) - Modify: `docs/service/operations.md` (issue #40 P2) to reference the concurrency policy **Acceptance:** - `docs/concurrency.md` has a section per answered question with the value, the rationale, and the source (benchmark, calculation, or decision doc). - The values are testable: a load test can assert "5 concurrent requests get 503 with Retry-After" against the spec. - The ThreadCraft team agrees on the values. **Why first:** throughput answers constrain the design of the worker pool and the request queue. They should be locked before any service code is written. --- ### P2: Per-request bounds (questions 2, 3, 4) **Goal:** Lock the per-request resource limits. **Specifically:** 2. **Per-request timeout?** Answer: 60 seconds for digitize, 30 seconds for export. Justify: a 4 MP image at 0.4mm density is ~600K stitches, generated in < 5 seconds; 60s gives 12× headroom. 3. **Per-request memory cap?** Answer: 1024 MB. Justify: the raster pipeline at 4 MP peaks at ~800 MB. 4. **Cancellation?** Answer: yes — when the client disconnects, the worker thread is interrupted within 1 second (set a flag the pipeline checks between stages; full thread cancellation is unsafe because of numpy/scipy GIL interactions). **Files:** - Modify: `docs/concurrency.md` (add these 3 answers) **Acceptance:** - All 3 questions answered with value, rationale, source. - A test asserts that a request exceeding the timeout returns 504 Gateway Timeout. - A test asserts that a request exceeding the memory cap returns 503 with a clear error. - A test asserts that cancelling the client connection causes the worker to stop within 1 second. **Depends on:** P1 (overall policy). --- ### P3: Failure semantics (questions 7, plus the "what if stage N fails?" case) **Goal:** Lock the failure-mode behavior. **Specifically:** 7. **Failure mode on partial completion?** Answer: if a stage fails, the response is 503 with body `{"error": "...", "failed_stage": "...", "completed_stages": [...], "request_id": "..."}`. The pipeline cleans up any partial state (temp files, half-built stitch objects). - **Plus:** what happens on invalid input (malformed PNG, oversize upload, unsupported format)? Answer: 400 Bad Request with a specific error code per failure mode (NOT 500). - **Plus:** what's the log format on errors? Answer: structured JSON with `request_id`, `stage`, `error`, `stack` (configurable). **Files:** - Modify: `docs/concurrency.md` (add failure-mode section) - Modify: `docs/service/operations.md` (cross-link from the error response shape) **Acceptance:** - All failure modes documented with response shape and HTTP code. - A test for each documented failure mode asserts the response. - An integration test simulates a stage failure mid-pipeline and asserts the partial-state cleanup happened (no orphan temp files). **Depends on:** P1, P2. ## Out of scope - The actual implementation of the worker pool, queue, timeout, and cancellation logic. Those go in the relevant service tickets (issue #31 P2 for the handlers, P5 for the worker pool). - Operational concerns beyond the request lifecycle (rolling deploys, A/B testing, blue-green) — separate doc, separate ticket. - Per-user rate limiting — not in scope for v1 (single-user Tailnet). If multi-user support is added later, a rate-limit section can be added to `docs/concurrency.md`.
Mark changed title from No documented concurrency contract for the service — queue depth, timeouts, memory caps to Decompose: Documented concurrency contract (throughput, per-request bounds, failure semantics) 2026-06-22 16:30:53 +00:00
Author
Owner

This issue has been decomposed into the following sub-issues:

# Title Phase Scope
#69 Decompose: Documented concurrency contract (throughput, per- Goal: Lock the throughput policy. Specifically: 1. **Max concurrent di
#70 Decompose: Documented concurrency contract (throughput, per- Goal: Lock the per-request resource limits. Specifically: 2. **Per-req
#71 Decompose: Documented concurrency contract (throughput, per- Goal: Lock the failure-mode behavior. Specifically: 7. **Failure mode

Each sub-issue has been labeled with triage-approved and is ready for queue routing.

This issue has been decomposed into the following sub-issues: | # | Title | Phase Scope | |---|-------|-------------| | #69 | Decompose: Documented concurrency contract (throughput, per- | **Goal:** Lock the throughput policy. **Specifically:** 1. **Max concurrent di | | #70 | Decompose: Documented concurrency contract (throughput, per- | **Goal:** Lock the per-request resource limits. **Specifically:** 2. **Per-req | | #71 | Decompose: Documented concurrency contract (throughput, per- | **Goal:** Lock the failure-mode behavior. **Specifically:** 7. **Failure mode | Each sub-issue has been labeled with `triage-approved` and is ready for queue routing.
Mark closed this issue 2026-06-22 16:31:53 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/easystitch#43
No description provided.