Every image request re-encodes: the artwork path is 240x slower than upstream #185
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Measured for #117, over the anchor — 56k items, a real household's artwork —
with both servers native, pinned to their own cores, one request set, five
repetitions, and the gate agreeing on 8 of the 10 requests before anything was
timed.
Two hundred and forty times. And the response is the same picture — the
byte counts agree to within a percent — so nothing is being done differently
except how often it is done.
The shape says which: ours is flat at ~76 rps from c=8 to c=128 while its
latency grows linearly with concurrency, which is a queue in front of a
CPU-bound step. Upstream is flat at ~18,500 rps with a sub-millisecond median,
which is a file being sent.
Upstream resizes once and keeps it —
ImageProcessor.ResizedImageCachePathis<cache>/images/resized-images(upstream/src/Jellyfin.Drawing/ImageProcessor.cs:77,379),and
GetCachePathcreates the directory on the way. This server has no suchcache: every request re-encodes. That is also why
<cache>/imagesnever existshere, which is what #179 was about before it turned out the storage report was
answering correctly.
The read-path numbers in
report.mdare all in our favour. This is the onethat is not, and it is larger than any of them.
Where to start
crates/ephyra-http/src/routes/library/images/serves these, and the resizehappens per request. Upstream's key is the source path, its date, the requested
dimensions and quality, and the output format — a file per combination under
<cache>/images/resized-images, swept by its own scheduled task.Worth settling first: whether the cache is written on the request that missed
(upstream's shape) or filled ahead by a task, and what happens to a stale entry
when a picture is replaced —
BaseItemImageInfoscarries the date the tag isbuilt from, so a changed picture changes the tag and therefore the key.
How to know it is done
The same run:
cargo xtask bench --fixture <anchor> --workload artwork-anchorover a request set naming rows the anchor holds. A second request for one
picture is served without re-encoding it, the throughput ratio stops being
0.004, and
<cache>/imagesis a directory with files in it.Two of the ten requests in that run drop for an unrelated reason — a
Personimage where upstream answers
404and we answer with a picture — which wantsits own issue.
House rules
Tests run under
cargo nextest; upstream's source is the checkout inupstream/at the pin.docs/benchmarks.mdis the method, and a resultnames the fixture it came from.
Fixed in
365cd2c, measured over the anchor with 8 of 8 requests agreeingbefore anything was timed.
147× the throughput at concurrency 128, and 144× off the median latency.
The ratio this issue was opened over — a flat 0.004 at every concurrency — is
now 0.50 to 0.63, and the flatness is gone: throughput climbs with concurrency
the way upstream's does, because the work is a file read rather than a decode.
At the tail we are now faster than upstream: p99 14.24 ms against 25.32 at
c=128, and p90 12.69 against 19.47. Ours is slower at the median and quicker
under load, which is the shape of a cheaper per-request path behind a slightly
slower one.
<cache>/images/resized-images/<first character>/<digest>.jpgholds entries onboth sides after the run — five of ours, ten of upstream's, from the same eight
requests. Ours are fewer because upstream keys on the request's own spelling of
a size and we key on the size actually served, so two spellings of one size
share an entry.
The bytes still differ by a percent, which is the resampler rather than the
cache —
crates/ephyra-images/src/scaled.rsmeasures which half of thatdisagrees with upstream.
One thing this does not do: upstream sweeps that directory from a scheduled
task and ours grows without bound. Filed separately as #188.
The two requests that used to drop — a
Personimage where upstream answers404and we answer with a picture — are not in this request set; thatdifference is recorded on #117 and is unrelated.