Returning streams, either with WriteStreamAsync or WriteFileStreamAsync or their handler equivalents, is extremely slow
reinux opened this issue · comments
With this code:
open System.Net.Http
open System.IO
open Giraffe
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
let data =
use client = new HttpClient()
client.GetByteArrayAsync("https://apod.nasa.gov/apod/image/0704/carina_hst_big.jpg").Result
printfn $"Image length: %.2f{float data.Length/1024.} KB"
let webApp =
choose [
route "/test-bytes" >=> warbler (fun _ -> setBody data)
route "/test-stream" >=> warbler (fun _ -> streamData false (new MemoryStream(data)) None None)
]
let builder = WebApplication.CreateBuilder()
builder.Logging.AddFilter(fun _ -> true) |> ignore
builder.Services.AddGiraffe() |> ignore
let app = builder.Build()
app.UseGiraffe webApp
app.Run()
/test-bytes
takes around 50ms, whereas /test-stream
takes 500ms-750ms.
The image is around 2.6mb, and almost all the time is spent in receiving the response as opposed to waiting for the response to begin:
I tried turning ranged processing on and off, as well as Firefox, Brave, Chromium, curl and System.Net.Http.HttpClient
.
Just a shot in the dark, but I wonder if the response stream buffer isn't optimal, and it's causing a lot of TCP round trips?
Okay, yeah, so changing the buffer size in WriteStreamToBodyAsync
to 1MB kind of did the trick -- now it's down to 100ms or so, which suggests it's probably still doing 2 or 3 round trips as opposed to just one.
I'm not sure what the right value for this should be, though 64kb seems a bit small for modern times. HTTP3 would probably solve this issue, but the Linux community's support of libmsquic is unsurprisingly tepid.
Nice, thanks for commenting the workaround you found @reinux. Do you think we can close this issue now?
I'll close it now. Please open a new issue if you'd like to discuss more about this topic.