MCR blob transfers truncated at exact 8 MiB boundaries for slow readers (Detected on London CDN) · Issue #196 · microsoft/containerregistry · GitHub
Skip to content

MCR blob transfers truncated at exact 8 MiB boundaries for slow readers (Detected on London CDN) #196

Open

Description

@juanjsebgarcia

Summary

Large blob downloads from mcr.microsoft.com are silently truncated partway through
when the client reads slowly. The response is an ordinary HTTP 200 with a correct
full Content-Length; the body simply stops arriving. Every truncation we have
observed lands on an exact multiple of 8 MiB.

The behaviour is strongly associated with one Front Door edge cluster. Across 12
interleaved transfers of the same blob from the same host within one hour, requests
served by cluster LON601060 truncated 5 times in 6, while requests served by
LON281181 completed 6 times in 6 (Fisher exact, two-sided, p = 0.0152).

We are a container scanning vendor; this puts Windows base images intermittently out
of reach for our customers, and the 200-with-short-body shape means naive clients
accept a partial layer as a complete one.

Environment

  • Client: curl in a container, --limit-rate used to emulate a client that
    decompresses inline and therefore drains the socket below line rate.
  • Vantage: single host, London; all transfers served by LON edges.
  • Unthrottled line rate available: ~60 MB/s. Throttle used below: 12 MB/s.
  • Date of runs: 2026-09-17.

Reproduction

BLOB=sha256:5b663e3b9104ae6bf9135c0beb5291952314d59565b1be257a362a53b219b889
URL=https://mcr.microsoft.com/v2/windows/servercore/blobs/$BLOB

# full rate: completes every time, ~25s
curl -sSL -o /dev/null -w '%{http_code} %{size_download}\n' "$URL"

# throttled: truncates intermittently, always on an 8 MiB boundary
curl -sSL -o /dev/null --limit-rate 12M -w '%{http_code} %{size_download} %{exitcode}\n' "$URL"

Expected length 1,526,802,685 bytes.

Observed behaviour

A failing transfer proceeds normally at the throttled rate, then stalls for roughly
two minutes
with no further bytes, then terminates with a receive error
(curl exit 56). It is not a clean connection close: the server declares the full
Content-Length up front and never revises it, so the client is left holding a short
body against a correct declared length.

Truncation offsets are exactly 8 MiB granular

bytes received multiple of 8 MiB edge (Ref B)
612,368,384 73 LON601060108054
746,586,112 89 LON601060108052
654,311,424 78 LON601060105062
771,751,936 92 LON601060108025
1,157,627,904 138 LON601060106011

Not approximately 8 MiB granular. Exactly, in every case, across every run we have
recorded, including five earlier observations at 17, 26, 34, 70 and 95 multiples.

Azure Front Door's own caching documentation states that Front Door requests files
from origin "in chunks of 8 MB" and prefetches the next chunk in parallel. The
granularity matches exactly. The origin object is an Azure Storage AppendBlob
with 365 committed blocks over 1.53 GB, i.e. ~4 MiB blocks, so each 8 MiB Front Door
chunk spans two append blocks.

The association with edge cluster

12 interleaved transfers of the same blob, same host, within one hour:

edge cluster (Ref B prefix) requests truncated rate
LON281181 6 0 0%
LON601060 6 5 83%

Fisher exact, two-sided: p = 0.0152.

A separate 40-request survey from the same host shows roughly 75% of connections are
assigned to LON281181 and 25% to LON601060, which is consistent with the overall
intermittency we see.

This is the finding we would most like you to pursue. If one edge cluster is
mishandling chunked origin fetches for large objects under slow drain, that is a
concrete, localised thing to look at rather than a vague intermittency report.

Correlation IDs

What we have ruled out

Each of these was eliminated by a specific control rather than by argument:

  • Network instability at our end. Unthrottled transfers of the same blob
    completed 8 of 8, elapsed times within 3% of one another.
  • Connection duration. A 0.60 GB Linux layer on MCR survived 285 s on the wire at
    2 MB/s, longer than any failing Windows transfer.
  • Object size. A 1.92 GB Linux layer on MCR survived 4 of 4 at 12 MB/s, larger
    than the 1.53 GB object that fails.
  • A single unlucky object. Three distinct objects fail, across windows/servercore
    and windows.
  • Registries in general. Docker Hub never truncated a throttled reader: 2.98 GB
    held for 237 s, 2 of 2.
  • Range requests as a workaround. A closed Range request covering the whole
    remainder truncates just as readily as an unranged GET (3 of 6 versus 2 of 6 in the
    interleaved runs above). Ranging does not avoid this.

What we have NOT established

We want to be straight about the limits of our own evidence, because an earlier
internal analysis of ours reached a confident and wrong conclusion:

  • We initially believed the problem was specific to non-distributable (foreign)
    layers
    , since those are the Windows base layers and the failures cluster there.
    Our control for this compared a foreign layer against a distributable one, but the
    two also differed in Azure Storage block geometry (~4 MiB blocks versus ~100 MB
    blocks) and were not controlled for edge cluster. We no longer regard the media
    type as established, and we are re-testing with a matched control.
  • We cannot tell from outside whether the cause is the edge cluster, the origin blob's
    append-block layout, the interaction of the two, or something else entirely.

Impact

Windows base images are affected across the board, including images published
elsewhere whose manifests point back at MCR. For any client that reads the body while
decompressing - which is the normal way to stream a container layer - the transfer
runs at a fraction of line rate and sits squarely in the band where this occurs.

The failure shape is the dangerous part. A 200 with a correct Content-Length and a
short body is accepted as valid by any client that does not verify the digest and
length itself. Clients that do verify simply fail the pull; clients that do not
silently process a partial filesystem.

Questions

  1. Can you confirm the 8 MiB chunked origin fetch is involved, and whether an
    AppendBlob origin with ~4 MiB blocks is handled differently from a BlockBlob or a
    large-block AppendBlob?
  2. Can you check the health of edge cluster LON601060 against the correlation IDs
    above?
  3. Is there any client-side signal that distinguishes a truncated transfer from a
    complete one, other than verifying the digest ourselves?

Happy to run any instrumented reproduction you would find useful, including from
other vantages or against objects of your choosing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions