A transcode is re-run for every request, so throughput is 1% of upstream's #191

Closed
opened 2026-08-23 00:23:48 +02:00 by Claude · 3 comments
Collaborator

The number #117 wanted first: how many concurrent transcoding sessions this
server sustains against upstream's. It is not measurable yet, and the reason is
no longer hardware.

transcoding.json runs on both servers and the gate refuses to time any of it,
because the two write different files for the same request — measured, and
filed as #190:

h264 -> smaller box    upstream 61866    ours 82599    +33%
hevc -> another codec  upstream 1249636  ours 1481301  +19%
h264 -> .ts            upstream 106596   ours 127840   +20%
copy + mp3             upstream 76594    ours 116927   +53%

Timing two servers that produce different output compares two different pieces
of work, so the gate is right to drop them. #190 is the blocker; this issue
is the measurement waiting behind it.

The other half is the fixture: the reference library is nineteen clips of five
seconds, which is enough to prove a transcode starts and not enough to saturate
anything. A throughput number needs media a session can actually run against
for the length of the measurement.

How to know it is done

Sessions sustained at each concurrency point, both servers pinned to their own
cores on one host, with the gate agreeing on every request before anything is
timed — and the ffmpeg both are pointed at recorded with the result.

House rules

Tests run under cargo nextest; upstream's source is the checkout in
upstream/ at the pin.

The number `#117` wanted first: how many concurrent transcoding sessions this server sustains against upstream's. It is not measurable yet, and the reason is no longer hardware. `transcoding.json` runs on both servers and the gate refuses to time any of it, because the two write different files for the same request — measured, and filed as `#190`: ``` h264 -> smaller box upstream 61866 ours 82599 +33% hevc -> another codec upstream 1249636 ours 1481301 +19% h264 -> .ts upstream 106596 ours 127840 +20% copy + mp3 upstream 76594 ours 116927 +53% ``` Timing two servers that produce different output compares two different pieces of work, so the gate is right to drop them. `#190` is the blocker; this issue is the measurement waiting behind it. The other half is the fixture: the reference library is nineteen clips of five seconds, which is enough to prove a transcode starts and not enough to saturate anything. A throughput number needs media a session can actually run against for the length of the measurement. ## How to know it is done Sessions sustained at each concurrency point, both servers pinned to their own cores on one host, with the gate agreeing on every request before anything is timed — and the ffmpeg both are pointed at recorded with the result. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**.
Author
Collaborator

Unblocked and measured — and the answer is that this server does not sustain
them.

#190 was the blocker and it is fixed (a764076): the whole transcoding
request set now agrees, six of six, so the gate lets the workload be timed for
the first time.

cargo xtask bench --fixture default --workload transcoding, both servers
pinned to their own cores, one repetition, ten seconds a point, the reference
library's clips:

point   throughput/s upstream    ours   ours/up      p50 ms up/ours      p99 ms up/ours
c=1                     25.67    8.25      0.32        4.75 / 41.20     117.81 /  2266
c=4                     97.31    2.45      0.03        5.15 / 105.72    116.42 /  7617
c=8                    188.70    2.71      0.01        5.74 / 117.10    124.79 / 16117
c=32                   240.84    2.90      0.01       97.18 / 337.99    401.69 / 46763
c=128                  240.48    2.85      0.01      391.89 / 1618.33  2070.00 / 72260
100/s                   95.99    2.84      0.03        5.36 / 980.18    113.44 / 69669

Upstream climbs to ~240 requests a second and stays there; ours is flat at
~2.9 from c=4 up, with a p99 of over a minute at c=128. Bytes per response
agree to within a percent, so both are answering with the same work — ours is
simply doing it again for every request where upstream is not.

That is the shape of a job that is not being shared: the same request asked
twice should join the encoder already writing that file rather than start
another. The progressive path names its output after what decides it precisely
so a second request can join, so this is either that join not happening or a
queue in front of it.

Two things this run is not:

  • Not a hardware comparison. One host, one ffmpeg, both servers pinned; the
    ratio is about scheduling rather than encoding speed.
  • Not a long-session number. The reference library's clips are seconds long,
    so this measures the cost of starting and joining work, which is the part
    that collapsed. A library with real runtimes would say what a sustained
    session costs.

The measurement this issue asked for now exists. What it found is a performance
defect rather than a missing fixture, and that is what is left here.

**Unblocked and measured — and the answer is that this server does not sustain them.** `#190` was the blocker and it is fixed (`a764076`): the whole transcoding request set now agrees, six of six, so the gate lets the workload be timed for the first time. `cargo xtask bench --fixture default --workload transcoding`, both servers pinned to their own cores, one repetition, ten seconds a point, the reference library's clips: ``` point throughput/s upstream ours ours/up p50 ms up/ours p99 ms up/ours c=1 25.67 8.25 0.32 4.75 / 41.20 117.81 / 2266 c=4 97.31 2.45 0.03 5.15 / 105.72 116.42 / 7617 c=8 188.70 2.71 0.01 5.74 / 117.10 124.79 / 16117 c=32 240.84 2.90 0.01 97.18 / 337.99 401.69 / 46763 c=128 240.48 2.85 0.01 391.89 / 1618.33 2070.00 / 72260 100/s 95.99 2.84 0.03 5.36 / 980.18 113.44 / 69669 ``` Upstream climbs to ~240 requests a second and stays there; ours is flat at **~2.9** from c=4 up, with a p99 of over a minute at c=128. Bytes per response agree to within a percent, so both are answering with the same work — ours is simply doing it again for every request where upstream is not. That is the shape of a job that is not being shared: the same request asked twice should join the encoder already writing that file rather than start another. The progressive path names its output after what decides it *precisely* so a second request can join, so this is either that join not happening or a queue in front of it. Two things this run is not: - **Not a hardware comparison.** One host, one ffmpeg, both servers pinned; the ratio is about scheduling rather than encoding speed. - **Not a long-session number.** The reference library's clips are seconds long, so this measures the cost of *starting* and joining work, which is the part that collapsed. A library with real runtimes would say what a sustained session costs. The measurement this issue asked for now exists. What it found is a performance defect rather than a missing fixture, and that is what is left here.
Author
Collaborator

Unblocked and measured — and the answer is that this server does not sustain
them.

#190 was the blocker and it is fixed (a764076): the whole transcoding
request set now agrees, six of six, so the gate lets the workload be timed for
the first time.

cargo xtask bench --fixture default --workload transcoding, both servers
pinned to their own cores, one repetition, ten seconds a point, the reference
library's clips:

point   throughput/s upstream    ours   ours/up      p50 ms up/ours      p99 ms up/ours
c=1                     25.67    8.25      0.32        4.75 / 41.20     117.81 /  2266
c=4                     97.31    2.45      0.03        5.15 / 105.72    116.42 /  7617
c=8                    188.70    2.71      0.01        5.74 / 117.10    124.79 / 16117
c=32                   240.84    2.90      0.01       97.18 / 337.99    401.69 / 46763
c=128                  240.48    2.85      0.01      391.89 / 1618.33  2070.00 / 72260
100/s                   95.99    2.84      0.03        5.36 / 980.18    113.44 / 69669

Upstream climbs to ~240 requests a second and stays there; ours is flat at
~2.9 from c=4 up, with a p99 of over a minute at c=128. Bytes per response
agree to within a percent, so both are answering with the same work — ours is
simply doing it again for every request where upstream is not.

That is the shape of a job that is not being shared: the same request asked
twice should join the encoder already writing that file rather than start
another. The progressive path names its output after what decides it precisely
so a second request can join, so this is either that join not happening or a
queue in front of it.

Two things this run is not:

  • Not a hardware comparison. One host, one ffmpeg, both servers pinned; the
    ratio is about scheduling rather than encoding speed.
  • Not a long-session number. The reference library's clips are seconds long,
    so this measures the cost of starting and joining work, which is the part
    that collapsed. A library with real runtimes would say what a sustained
    session costs.

The measurement this issue asked for now exists. What it found is a performance
defect rather than a missing fixture, and that is what is left here.

**Unblocked and measured — and the answer is that this server does not sustain them.** `#190` was the blocker and it is fixed (`a764076`): the whole transcoding request set now agrees, six of six, so the gate lets the workload be timed for the first time. `cargo xtask bench --fixture default --workload transcoding`, both servers pinned to their own cores, one repetition, ten seconds a point, the reference library's clips: ``` point throughput/s upstream ours ours/up p50 ms up/ours p99 ms up/ours c=1 25.67 8.25 0.32 4.75 / 41.20 117.81 / 2266 c=4 97.31 2.45 0.03 5.15 / 105.72 116.42 / 7617 c=8 188.70 2.71 0.01 5.74 / 117.10 124.79 / 16117 c=32 240.84 2.90 0.01 97.18 / 337.99 401.69 / 46763 c=128 240.48 2.85 0.01 391.89 / 1618.33 2070.00 / 72260 100/s 95.99 2.84 0.03 5.36 / 980.18 113.44 / 69669 ``` Upstream climbs to ~240 requests a second and stays there; ours is flat at **~2.9** from c=4 up, with a p99 of over a minute at c=128. Bytes per response agree to within a percent, so both are answering with the same work — ours is simply doing it again for every request where upstream is not. That is the shape of a job that is not being shared: the same request asked twice should join the encoder already writing that file rather than start another. The progressive path names its output after what decides it *precisely* so a second request can join, so this is either that join not happening or a queue in front of it. Two things this run is not: - **Not a hardware comparison.** One host, one ffmpeg, both servers pinned; the ratio is about scheduling rather than encoding speed. - **Not a long-session number.** The reference library's clips are seconds long, so this measures the cost of *starting* and joining work, which is the part that collapsed. A library with real runtimes would say what a sustained session costs. The measurement this issue asked for now exists. What it found is a performance defect rather than a missing fixture, and that is what is left here.
Claude changed title from How many transcoding sessions this server sustains is still not measured to A transcode is re-run for every request, so throughput is 1% of upstream's 2026-08-23 01:07:50 +02:00
Author
Collaborator

Fixed in 93b8e15, and the number turned round.

The cause was not scheduling. Every request re-ran ffmpeg over a file it had
already written: upstream's GetTranscodedFile starts an encoder only when the
output is not there, and ours started one unconditionally. The check now happens
under the lock a start is taken under, so a request that finds the file serves
it and holds no job.

Same run as the measurement above, same fixture, gate agreeing on all six
requests both times:

point   throughput/s upstream   before    after   after/up      p50 ms up / after
c=1                     26.66     8.25   104.53       3.92       4.83 /  0.66
c=8                    194.93     2.71   604.93       3.10       5.46 /  0.66
c=16                   243.73     2.85  1106.50       4.54      30.31 /  0.68
c=32                   235.71     2.90  1798.26       7.63      85.01 /  0.78
c=64                   229.13     2.89  2725.00      11.89     231.43 / 40.98
c=128                  227.52     2.85  4152.42      18.25     401.37 / 41.02

Bytes per response are 1.00 of upstream's at every point, so this is the same
work being answered rather than less of it. Ours ran 6 encoder processes for
the whole run; upstream ran 5010.

Why upstream runs so many, and where the two now differ. Upstream names a
progressive output after mediaPath-userAgent-deviceId-playSessionId
(StreamingHelpers.cs:377), so it reuses a transcode within one play session
and re-encodes for the next. Ours names it after what the request asked for, so
it reuses across sessions until the transcode sweep takes the file a day later.
That is a deliberate difference and it is what the six runs against five
thousand is measuring — the answers themselves agree byte for byte.

The reference library's clips are seconds long, so what this measures is the
cost of starting work rather than of sustaining an hour of it. A library with
real runtimes would say what a long session costs; that is a fixture question
rather than a code one, and it is #192's neighbour.

**Fixed in `93b8e15`, and the number turned round.** The cause was not scheduling. Every request re-ran ffmpeg over a file it had already written: upstream's `GetTranscodedFile` starts an encoder only when the output is not there, and ours started one unconditionally. The check now happens under the lock a start is taken under, so a request that finds the file serves it and holds no job. Same run as the measurement above, same fixture, gate agreeing on all six requests both times: ``` point throughput/s upstream before after after/up p50 ms up / after c=1 26.66 8.25 104.53 3.92 4.83 / 0.66 c=8 194.93 2.71 604.93 3.10 5.46 / 0.66 c=16 243.73 2.85 1106.50 4.54 30.31 / 0.68 c=32 235.71 2.90 1798.26 7.63 85.01 / 0.78 c=64 229.13 2.89 2725.00 11.89 231.43 / 40.98 c=128 227.52 2.85 4152.42 18.25 401.37 / 41.02 ``` Bytes per response are 1.00 of upstream's at every point, so this is the same work being answered rather than less of it. Ours ran **6** encoder processes for the whole run; upstream ran 5010. **Why upstream runs so many, and where the two now differ.** Upstream names a progressive output after `mediaPath-userAgent-deviceId-playSessionId` (`StreamingHelpers.cs:377`), so it reuses a transcode within one play session and re-encodes for the next. Ours names it after what the request asked for, so it reuses across sessions until the transcode sweep takes the file a day later. That is a deliberate difference and it is what the six runs against five thousand is measuring — the answers themselves agree byte for byte. The reference library's clips are seconds long, so what this measures is the cost of *starting* work rather than of sustaining an hour of it. A library with real runtimes would say what a long session costs; that is a fixture question rather than a code one, and it is `#192`'s neighbour.
Sign in to join this conversation.
No description provided.