Add file content-hash dedup, expose file hash, harden upload - #257
Conversation
- Expose `fileHash` (hex SHA-256) on the File response so a client can identify
a file's content. Add a self-validating FileHash newtype in handler/utility
(deserializes a 64-char hex string, rejects bad hex/length at the extractor
boundary, carries its own OpenAPI schema).
- Let clients de-duplicate before uploading: the file list endpoint gains a
`hash` filter (GET /workspaces/{slug}/files/?hash=<sha256>) that returns the
workspace's live files with that exact content. A non-empty result means the
file already exists, so the upload can be skipped. Threads a content-hash facet
through FileFilter and both list queries, hitting the existing
(file_hash_sha256, file_size_bytes) index.
- Fix upload-path connection starvation surfaced under load: upload_file no
longer holds a pooled DB connection across the NATS streaming phase. It does
the quick pre-flight DB work (auth, uploader, settings) under a connection,
drops it, streams every file with no connection held, then re-acquires only for
the final commit transaction.
- Remove the RouteCategory metrics categorizer: its prefix list had drifted out
of sync with the routes (so e.g. /notifications/ logged category="unknown"),
and most routes are workspace-scoped and collapse to one category anyway. The
request-metrics middleware keeps method, uri, status, duration, and body sizes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
💤 Files with no reviewable changes (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds SHA-256 file hash filtering and response fields, adjusts upload database connection scopes, and removes route-category tracking from request metrics. ChangesFile hash support
Upload connection lifetime
Request metrics simplification
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: 🟡 Moderate · up to The upload flow now releases database connections during streaming, but a failure when reacquiring one for the final commit can leave encrypted objects without file records or cleanup coverage. Repeated authorized uploads under database pressure could consume storage and affect availability, so this should be addressed or explicitly accepted before merging. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
With #257 merged, the deferred sixth offender: bulk_delete_files held one pooled connection across N sequential object purges (each a NATS delete). Drop the batch connection after the commit and re-acquire a short-lived connection per purge, so one connection is never pinned across the whole delete sequence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8
…262) * Release DB connections across slow I/O; fail fast on pool exhaustion A stress test with a single client exhausted the connection pool and cascaded to 500s. The root cause was handlers and the detection worker holding a pooled connection across slow non-DB work (LLM inference, NATS object I/O, image processing) — with a 10-connection pool and no acquire timeout, a few concurrent requests pinned every connection and later requests hung until the request timeout killed them. Release the connection across the slow phase everywhere it was held: - redact_detection: pre-flight DB work (auth, find detection/file, resolve the audit file row, resolve policies) under a scoped connection, dropped before the audit load, redaction inference, and object staging; re-acquired only for the commit transaction. - DetectionWorker::detect: manages its own connection per phase — reads under a connection, drops it across build/analyze/stage, re-acquires for the fenced finalize. - Avatar upload (account + workspace): authorize under a scoped connection, release before image processing and the NATS put. - Detection-audit read + redaction-review handlers: resolve the audit file row under a connection, release before the object-store load. To support this, split RunBlobStore's audit loading into a connection-bound resolve step (resolve_audit_file / resolve_review_file) and a connection-free load step (load_audit), deduplicating the object load/decode that was copied across two methods and removing the now-unused load_analyzed_document. Fail fast on pool exhaustion instead of hanging: - Default POSTGRES_CONNECTION_TIMEOUT to 10s (was unset = wait forever), below the request timeout, so a starved request fails promptly. - Map a pool wait timeout to a retryable 503 Service Unavailable (new ErrorKind::ServiceUnavailable) rather than a 500; a backend create/recycle timeout stays a 500. Rate limiting is intentionally left to the edge/infra (see issues); the server's job here is to hold shared resources briefly and degrade gracefully. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8 * Release the connection across the bulk-delete purge loop With #257 merged, the deferred sixth offender: bulk_delete_files held one pooled connection across N sequential object purges (each a NATS delete). Drop the batch connection after the commit and re-acquire a short-lived connection per purge, so one connection is never pinned across the whole delete sequence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8 --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Four related changes to the files domain and request observability.
Expose the file content hash
The
Fileresponse now includesfileHash— the lowercase hex SHA-256 of the file's plaintext (the model already stored it; it was never surfaced). Appears on upload, read, and list responses.Adds a self-validating
FileHashnewtype (handler/utility/): deserializes from a 64-character hex string, rejects bad hex or wrong length at the extractor boundary (a malformed value is a clean 400 before the handler runs), serializes back to hex, and carries its own OpenAPI schema (pattern: ^[0-9a-fA-F]{64}$).Client-side dedup before upload
The file list endpoint gains a
hashfilter:It returns the workspace's live files with that exact content. A non-empty result means the file already exists, so the client can skip the upload (or reference the existing file). No new route —
hashis a fifth facet alongsidesearch/formats/modality, threaded throughFileFilterand both list queries, hitting the existing(file_hash_sha256, file_size_bytes)partial index. Workspace- and document-kind-scoped, so it never leaks across tenants or exposes audit/artifact rows.Harden the upload path against pool starvation
A stress test surfaced connection-pool exhaustion:
upload_fileheld a pooled DB connection across the NATS streaming phase (which needs no DB), so concurrent uploads drained the 10-connection pool and later requests waited ~28s until the request timeout killed them with 500s.upload_filenow does the quick pre-flight DB work (auth, uploader, settings) under a connection, drops it, streams every file with no connection held, then re-acquires only for the final commit transaction.Remove the RouteCategory metrics categorizer
Its hardcoded prefix list had drifted out of sync with the actual routes —
/notifications/(and others) loggedcategory="unknown", while it still listed dead prefixes like/monitors///documents/. Since almost every route is workspace-scoped and collapses into a singleworkspacescategory anyway, the categorization added noise, not signal. The request-metrics middleware keeps method, uri, status, duration, and body sizes.Testing
Full gate green:
cargo check/clippy --all-targets --all-features --workspace -D warnings/fmt --check, unit tests (197 passed), doc build. New tests coverFileHashparse/round-trip and rejection of bad hex/length.Follow-ups (not in this PR)
POSTGRES_CONNECTION_TIMEOUTdefaults toNone(wait forever) — a finite value would fail fast instead of hanging to the request timeout; the default pool size (10) is low for concurrent uploads.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Refactor