docs(upload): stop claiming a proxy bandwidth control that does not exist

`get_original`'s comment said bandwidth abuse "belongs at the proxy, where
per-connection limits still work", which reads as though the removed per-IP
limiter had been replaced by something. It was not: the Caddyfile sets
timeouts and no rate or concurrency directive, and the tower stack is
TraceLayer alone.

Removing the limiter was right — the venue is one NAT address, so that bucket
throttled the whole party's feed — but the route is now unbounded, and the
comment should say so rather than imply cover. Records the actual cost
(no-store plus the derivative fallback plus the nonce'd retry, against a
15-slot pool that upload commits compete for) and the shape a real fix would
take: a concurrency semaphore over media streaming, not a request-rate bucket.
This commit is contained in:
fabi
2026-08-12 20:51:51 +02:00
parent 010bcc0e3c
commit 19b59d6fee

View File

@@ -1477,9 +1477,21 @@ async fn stream_media_file(
/// ///
/// A per-IP bucket cannot separate "one scraper" from "the entire party" when they share an /// A per-IP bucket cannot separate "one scraper" from "the entire party" when they share an
/// address, and these four media routes are unauthenticated by design (an `<img>` cannot send a /// address, and these four media routes are unauthenticated by design (an `<img>` cannot send a
/// bearer token), so there is no per-user key to move to. Bandwidth abuse belongs at the proxy, /// bearer token), so there is no per-user key to move to. The certain harm outweighed the
/// where per-connection limits still work; the certain harm here outweighed the speculative /// speculative protection.
/// protection. ///
/// BE HONEST ABOUT WHAT REPLACED IT: nothing did. This used to say "bandwidth abuse belongs at the
/// proxy, where per-connection limits still work", which reads as though a control exists there.
/// It does not — the `Caddyfile` sets timeouts and no rate or concurrency directive, and the tower
/// stack is `TraceLayer` alone. So this route is unbounded, deliberately, and the cost is real
/// rather than theoretical: `no-store` below plus the feed's fallback to `/original` for any photo
/// whose derivatives are still compressing plus `VirtualFeed`'s nonce'd retry means a hundred open
/// feeds can re-fetch full-resolution originals off the same disk Postgres writes WAL to, each one
/// also holding a connection from a 15-slot pool that upload commits are competing for.
///
/// If that needs bounding, the shape that fits is a concurrency semaphore over media streaming
/// (like `upload_admission`), NOT a request-rate bucket — the venue is one IP, which is what made
/// the previous attempt a self-inflicted outage.
pub async fn get_original( pub async fn get_original(
State(state): State<AppState>, State(state): State<AppState>,
headers: axum::http::HeaderMap, headers: axum::http::HeaderMap,