Vapor: High CPU utilization when uploading a file

Created on 2 Dec 2016  路  4Comments  路  Source: vapor/vapor

This code uses 99-100% CPU:

drop.post("upload") { request in
    guard let file = request.multipart?["zip"]?.file, let name = file.name else {
        throw Abort.badRequest
    }

    try Data(bytes: file.data).write(to: URL(fileURLWithPath: "/tmp/\(name)"))
    return "OK"
}

How to optimize uploading file?

Update:

After upgrading Vapor to version 1.2 uploading time reduced to 10 seconds.
The server and client running on one local machine (MacBook Pro).

Tested code:

drop.post("upload") { request in
    // No work here. Only Vapor testing.
    return "OK"
}
POST /upload HTTP/1.1
Content-Type: multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__
Host: 0.0.0.0:8080
Connection: close
User-Agent: Paw/3.0.12 (Macintosh; OS X/10.12.1) GCDHTTPRequest
Content-Length: 37379899

--__X_PAW_BOUNDARY__
Content-Disposition: form-data; name="ipa"; filename="file.ipa"
Content-Type: application/octet-stream

PK
聶yIPayload/UX
...

File size: _37379899 bytes_
vapor 1.1.x: _110 seconds_ (Very long time!)
vapor 1.2: _10 seconds_ (Still a long! It's just a copying file on the local machine)

Most helpful comment

One of the culprits seems to be this line in TCPSocket.swift: https://github.com/vapor/socks/blob/bd9af15409002dbc924b92f9220788c25c278d53/Sources/SocksCore/TCPSocket.swift#L47

Replacing the line

let out = Array(finalBytes.map({ UInt8($0) }))

with

let out = Array(finalBytes)

improves time to about 1.5 seconds.

All 4 comments

One of the culprits seems to be this line in TCPSocket.swift: https://github.com/vapor/socks/blob/bd9af15409002dbc924b92f9220788c25c278d53/Sources/SocksCore/TCPSocket.swift#L47

Replacing the line

let out = Array(finalBytes.map({ UInt8($0) }))

with

let out = Array(finalBytes)

improves time to about 1.5 seconds.

New tests

File size: _20585454 bytes_
Vapor (1.3.1): _113 seconds_ (Something went wrong :()
Code:

drop.post("upload") { request in
    guard let file = request.multipart?["zip"]?.file, let name = file.name else {
        throw Abort.badRequest
    }
    try Data(bytes: file.data).write(to: URL(fileURLWithPath: "/tmp/\(name)"))
    return "OK"
}

Perfect (2.1.8): _605 milliseconds_ !!! (Perfect! :))
Code:

routes.add(method: .post, uri: "/upload") { (request, response) in

    let fileDir = Dir(Dir.workingDir.path + "uploads")
    do {
        try fileDir.create()
    } catch {
        print(error)
    }

    if let uploads = request.postFileUploads, uploads.count > 0 {
        for upload in uploads {
            let thisFile = File(upload.tmpFileName)
            do {
                let _ = try thisFile.moveTo(path: fileDir.path + upload.fileName, overWrite: true)
            } catch {
                print(error)
            }
        }
    }

    response.appendBody(string: "OK")
    response.completed()
}

@nsleader we're seeing multiple issues related to multipart parsing. We're working on them now.

Until then, you can use the raw request.body as a workaround for accessing uploaded files.

Closing since we have a workaround and multipart is an ongoing discussion. It should be noted that there are third party multipart libraries that seem quite sufficient!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Joannis picture Joannis  路  4Comments

loganwright picture loganwright  路  3Comments

OlegKorchickiy picture OlegKorchickiy  路  3Comments

xiaoyifan picture xiaoyifan  路  3Comments

Igor-Palaguta picture Igor-Palaguta  路  3Comments