imazen / imageflow-dotnet

The official .NET API for Imageflow, the Rust image processing and optimization engine for web servers
GNU Affero General Public License v3.0
143 stars 25 forks source link

ImageJob seems slow #57

Closed andrewdavey closed 5 months ago

andrewdavey commented 5 months ago

My ASP.NET Core web application is using ImageJob to resize images. I have a controller action result to handle the image processing.

For jpeg images that are 1 or 2MB, resizing to 200px wide (command string w=200), I'm seeing request times taking over 1 second. When a groups of image requests come in at once, they get progressively slower to run as well.

The web server is running in AWS ECS. Linux container. 2 vCPU. 4GB memory.

My project references Imageflow.AllPlatforms version 0.12.0.

Am I doing anything badly, which would cause such slow responses?

class ImageResult : IActionResult
{
    readonly Stream imageSource;
    readonly string fileName;
    readonly string contentType;
    readonly string commandString;
    readonly ILogger logger;

    /// <param name="commandString">
    /// Supports commands like "width=100&mode=max"
    /// </param>
    public ImageResult(
        Stream imageSource,
        string fileName,
        string contentType,
        string commandString,
        ILogger logger
    )
    {
        this.imageSource = imageSource;
        this.fileName = fileName;
        this.contentType = string.IsNullOrEmpty(contentType)
            ? InferMediaTypeFromFilename(fileName)
            : contentType;
        this.commandString = commandString;
        this.logger = logger;
    }

    public async Task ExecuteResultAsync(ActionContext context)
    {
        var response = context.HttpContext.Response;

        response.StatusCode = 200;

        var headers = response.GetTypedHeaders();
        headers.ContentDisposition = new ContentDispositionHeaderValue("inline")
        {
            FileName = fileName
        };
        headers.ContentType = new MediaTypeHeaderValue(contentType);

        await SendImage(response.Body);
    }

    async Task SendImage(Stream outputStream)
    {
        var stopwatch = Stopwatch.StartNew();
        using var imageJob = new ImageJob();
        await imageJob
            .BuildCommandString(
                source: new StreamSource(imageSource, true),
                dest: new StreamDestination(outputStream, true),
                commandString: commandString
            )
            .Finish()
            .InProcessAndDisposeAsync();
        logger.LogDebug(
            "ImageJob for {FileName} {commandString} took {Elapsed}ms",
            fileName,
            commandString,
            stopwatch.ElapsedMilliseconds
        );
    }

    static string InferMediaTypeFromFilename(string fileName)
    {
        var ext = Path.GetExtension(fileName)?.ToLowerInvariant();
        return ext switch
        {
            ".png" => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".gif" => "image/gif",
            _ => "application/octet-stream"
        };
    }
}
lilith commented 5 months ago

You are benchmarking the client's internet connection as well as how fast the local disk is responding. Even if those are fast, there's a lot of info I don't have here. A 2MB jpeg could be anywhere from 2 million pixels to a billion; performance depends primarily on the pixel count, not the file size.

ECS is also typically highly shared you're probably having a ton of cache misses and the CPU is frequently reloading parts of the image into L2 cache.

You omitted the instance family, but 2 vCPU sounds like a micro node, which is really underpowered for image processing and encoding. You'll probably get better ROI and throughput at higher vCPU level. But first, please switch to Imageflow Server to rule out other issues.

You should be using Imageflow.NET Server if you're on ASP.NET Core, not Imageflow.Net: https://github.com/imazen/imageflow-dotnet-server There are Dockerfiles if you're running a microservice, but you can also just pull it into your existing project via nuget.

Getting caching, concurrency, and data streaming correct is non-trivial and doesn't work well under Controllers/Actions for a lot of reasons. I verified Imageflow Server is included for free on your company's subscription.

The next version of Imageflow Server will include S3 Express caching (and Azure Blob). If you set up the disk cache on ECS, remember only one server process per cache folder is permitted, since it's a custom write-ahead-logged database.

andrewdavey commented 5 months ago

Thank you for the fast response. I'll investigate using ImageFlow.NET Server, and close this issue.

Out of interest, how well does ImageFlow.NET Server perform within a AWS Lambda? I guess there could be similar issues to ECS. Image requests are only a small percentage of my app's traffic. It would be great if running a lambda meant I didn't need to have a large EC2 instance running 24/7.

lilith commented 5 months ago

I haven't yet provided a sample app for Lambda, but it's in the queue for the next release, as the product rewrite has been optimized for that integration and fast start.

On Fri, Mar 15, 2024, 3:03 AM Andrew Davey @.***> wrote:

Thank you for the fast response. I'll investigate using ImageFlow.NET Server, and close this issue.

Out of interest, how well does ImageFlow.NET Server perform within a AWS Lambda? I guess there could be similar issues to ECS. Image requests are only a small percentage of my app's traffic. It would be great if running a lambda meant I didn't need to have a large EC2 instance running 24/7.

— Reply to this email directly, view it on GitHub https://github.com/imazen/imageflow-dotnet/issues/57#issuecomment-1999215748, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAA2LH7ZCC4JPWPVA4SKLC3YYK2P7AVCNFSM6AAAAABEWBT6LKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSOJZGIYTKNZUHA . You are receiving this because you commented.Message ID: @.***>