Skip to content

Commit c82a856

Browse files
tomerdGitHub Enterprise
authored and
GitHub Enterprise
committed
performance improvments (#17)
motivation: better performance changes: * simply url configuration to ip and port so we can reoslve the socket add ress faster * skip happy eyeballs * limit eventloop group to 1 thread * remove use of UUID and URL * fix backtrace url
1 parent dea25d0 commit c82a856

File tree

10 files changed

+47
-71
lines changed

10 files changed

+47
-71
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ let package = Package(
1010
dependencies: [
1111
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
1212
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
13-
.package(url: "https://github.com/ianpartridge/swift-backtrace.git", from: "1.1.0"),
13+
.package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.1.0"),
1414
],
1515
targets: [
1616
.target(name: "SwiftAwsLambda", dependencies: ["Logging", "Backtrace", "NIOHTTP1"]),

Sources/SwiftAwsLambda/HttpClient.swift

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,33 @@ import NIOHTTP1
2020
internal class HTTPClient {
2121
private let eventLoop: EventLoop
2222
private let configuration: Lambda.Configuration.RuntimeEngine
23+
private let targetHost: String
2324

2425
private var state = State.disconnected
2526
private let lock = Lock()
2627

2728
init(eventLoop: EventLoop, configuration: Lambda.Configuration.RuntimeEngine) {
2829
self.eventLoop = eventLoop
2930
self.configuration = configuration
31+
self.targetHost = "\(self.configuration.ip):\(self.configuration.port)"
3032
}
3133

3234
func get(url: String, timeout: TimeAmount? = nil) -> EventLoopFuture<Response> {
33-
return self.execute(Request(url: self.configuration.baseURL.appendingPathComponent(url),
35+
return self.execute(Request(targetHost: self.targetHost,
36+
url: url,
3437
method: .GET,
3538
timeout: timeout ?? self.configuration.requestTimeout))
3639
}
3740

3841
func post(url: String, body: ByteBuffer, timeout: TimeAmount? = nil) -> EventLoopFuture<Response> {
39-
return self.execute(Request(url: self.configuration.baseURL.appendingPathComponent(url),
42+
return self.execute(Request(targetHost: self.targetHost,
43+
url: url,
4044
method: .POST,
4145
body: body,
4246
timeout: timeout ?? self.configuration.requestTimeout))
4347
}
4448

49+
4550
private func execute(_ request: Request) -> EventLoopFuture<Response> {
4651
self.lock.lock()
4752
switch self.state {
@@ -80,23 +85,30 @@ internal class HTTPClient {
8085
UnaryHandler(keepAlive: self.configuration.keepAlive)])
8186
}
8287
}
83-
return bootstrap.connect(host: self.configuration.baseURL.host, port: self.configuration.baseURL.port).flatMapThrowing { channel in
84-
self.state = .connected(channel)
88+
89+
do {
90+
// connect directly via socket address to avoid happy eyeballs (perf)
91+
let address = try SocketAddress(ipAddress: self.configuration.ip, port: self.configuration.port)
92+
return bootstrap.connect(to: address).flatMapThrowing { channel in
93+
self.state = .connected(channel)
94+
}
95+
} catch {
96+
return eventLoop.makeFailedFuture(error)
8597
}
8698
}
8799

88100
internal struct Request: Equatable {
89-
let url: Lambda.HTTPURL
101+
let url: String
90102
let method: HTTPMethod
91-
let target: String
103+
let targetHost: String
92104
let headers: HTTPHeaders
93105
let body: ByteBuffer?
94106
let timeout: TimeAmount?
95107

96-
init(url: Lambda.HTTPURL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: ByteBuffer? = nil, timeout: TimeAmount?) {
108+
init(targetHost: String, url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: ByteBuffer? = nil, timeout: TimeAmount?) {
109+
self.targetHost = targetHost
97110
self.url = url
98111
self.method = method
99-
self.target = url.path + (url.query.map { "?" + $0 } ?? "")
100112
self.headers = headers
101113
self.body = body
102114
self.timeout = timeout
@@ -137,9 +149,9 @@ private class HTTPHandler: ChannelDuplexHandler {
137149
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
138150
let request = unwrapOutboundIn(data)
139151

140-
var head = HTTPRequestHead(version: .init(major: 1, minor: 1), method: request.method, uri: request.target, headers: request.headers)
152+
var head = HTTPRequestHead(version: .init(major: 1, minor: 1), method: request.method, uri: request.url, headers: request.headers)
141153
if !head.headers.contains(name: "Host") {
142-
head.headers.add(name: "Host", value: request.url.host)
154+
head.headers.add(name: "Host", value: request.targetHost)
143155
}
144156
if let body = request.body {
145157
head.headers.add(name: "Content-Length", value: String(body.readableBytes))

Sources/SwiftAwsLambda/Lambda.swift

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import Darwin.C
1919
#endif
2020

2121
import Backtrace
22-
import Foundation // for URL
2322
import Logging
2423
import NIO
2524
import NIOConcurrencyHelpers
25+
import Dispatch
2626

2727
public enum Lambda {
2828
/// Run a Lambda defined by implementing the `LambdaClosure` closure.
@@ -53,7 +53,7 @@ public enum Lambda {
5353
@discardableResult
5454
internal static func run(handler: LambdaHandler, configuration: Configuration = .init()) -> LambdaLifecycleResult {
5555
do {
56-
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
56+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) // only need one thread, will improve performance
5757
defer { try! eventLoopGroup.syncShutdownGracefully() }
5858
let result = try self.runAsync(eventLoopGroup: eventLoopGroup, handler: handler, configuration: configuration).wait()
5959
return .success(result)
@@ -188,8 +188,8 @@ public enum Lambda {
188188
let stopSignal: Signal
189189

190190
init(id: String? = nil, maxTimes: Int? = nil, stopSignal: Signal? = nil) {
191-
self.id = id ?? UUID().uuidString
192-
self.maxTimes = maxTimes ?? env("MAX_REQUESTS").flatMap(Int.init) ?? 0
191+
self.id = id ?? "\(DispatchTime.now().uptimeNanoseconds)"
192+
self.maxTimes = maxTimes ?? env("MAX_REQUESTS").flatMap(Int.init) ?? 0
193193
self.stopSignal = stopSignal ?? env("STOP_SIGNAL").flatMap(Int32.init).flatMap(Signal.init) ?? Signal.TERM
194194
precondition(self.maxTimes >= 0, "maxTimes must be equal or larger than 0")
195195
}
@@ -200,20 +200,26 @@ public enum Lambda {
200200
}
201201

202202
struct RuntimeEngine: CustomStringConvertible {
203-
let baseURL: HTTPURL
203+
let ip: String
204+
let port: Int
204205
let keepAlive: Bool
205206
let requestTimeout: TimeAmount?
206207
let offload: Bool
207208

208209
init(baseURL: String? = nil, keepAlive: Bool? = nil, requestTimeout: TimeAmount? = nil, offload: Bool? = nil) {
209-
self.baseURL = HTTPURL(baseURL ?? "http://\(env("AWS_LAMBDA_RUNTIME_API") ?? "127.0.0.1:7000")")
210+
let ipPort = env("AWS_LAMBDA_RUNTIME_API")?.split(separator: ":") ?? ["127.0.0.1", "7000"]
211+
guard ipPort.count == 2, let port = Int(ipPort[1]) else {
212+
preconditionFailure("invalid ip+port configuration \(ipPort)")
213+
}
214+
self.ip = String(ipPort[0])
215+
self.port = port
210216
self.keepAlive = keepAlive ?? env("KEEP_ALIVE").flatMap(Bool.init) ?? true
211217
self.requestTimeout = requestTimeout ?? env("REQUEST_TIMEOUT").flatMap(Int64.init).flatMap { .milliseconds($0) }
212218
self.offload = offload ?? env("OFFLOAD").flatMap(Bool.init) ?? false
213219
}
214220

215221
var description: String {
216-
return "\(RuntimeEngine.self)(baseURL: \(self.baseURL), keepAlive: \(self.keepAlive), requestTimeout: \(String(describing: self.requestTimeout)), offload: \(self.offload)"
222+
return "\(RuntimeEngine.self)(ip: \(self.ip), port: \(self.port), keepAlive: \(self.keepAlive), requestTimeout: \(String(describing: self.requestTimeout)), offload: \(self.offload)"
217223
}
218224
}
219225

@@ -223,49 +229,6 @@ public enum Lambda {
223229
}
224230
}
225231

226-
internal struct HTTPURL: Equatable, CustomStringConvertible {
227-
private let url: URL
228-
let host: String
229-
let port: Int
230-
231-
init(_ url: String) {
232-
guard let url = Foundation.URL(string: url) else {
233-
preconditionFailure("invalid url")
234-
}
235-
guard let host = url.host else {
236-
preconditionFailure("invalid url host")
237-
}
238-
guard let port = url.port else {
239-
preconditionFailure("invalid url port")
240-
}
241-
self.url = url
242-
self.host = host
243-
self.port = port
244-
}
245-
246-
init(url: URL, host: String, port: Int) {
247-
self.url = url
248-
self.host = host
249-
self.port = port
250-
}
251-
252-
func appendingPathComponent(_ pathComponent: String) -> HTTPURL {
253-
return .init(url: self.url.appendingPathComponent(pathComponent), host: self.host, port: self.port)
254-
}
255-
256-
var path: String {
257-
return self.url.path
258-
}
259-
260-
var query: String? {
261-
return self.url.query
262-
}
263-
264-
var description: String {
265-
return self.url.description
266-
}
267-
}
268-
269232
private enum LifecycleState: Int {
270233
case idle
271234
case initializing

Tests/SwiftAwsLambdaTests/Lambda+CodeableTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private func assertLambdaLifecycleResult(result: LambdaLifecycleResult, shoudHav
7171

7272
// TODO: taking advantage of the fact we know the serialization is json
7373
private struct GoodBehavior: LambdaServerBehavior {
74-
let requestId = NSUUID().uuidString
74+
let requestId = UUID().uuidString
7575

7676
func getWork() -> GetWorkResult {
7777
guard let payload = try? JSONEncoder().encode(Request(requestId: requestId)) else {

Tests/SwiftAwsLambdaTests/Lambda+StringTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private func assertLambdaLifecycleResult(result: LambdaLifecycleResult, shoudHav
7070
}
7171

7272
private struct GoodBehavior: LambdaServerBehavior {
73-
let requestId = NSUUID().uuidString
73+
let requestId = UUID().uuidString
7474
let payload = "hello"
7575
func getWork() -> GetWorkResult {
7676
return .success((requestId: self.requestId, payload: self.payload))

Tests/SwiftAwsLambdaTests/LambdaRunnerTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import XCTest
1818
class LambdaRunnerTest: XCTestCase {
1919
func testSuccess() throws {
2020
struct Behavior: LambdaServerBehavior {
21-
let requestId = NSUUID().uuidString
21+
let requestId = UUID().uuidString
2222
let payload = "hello"
2323
func getWork() -> GetWorkResult {
2424
return .success((self.requestId, self.payload))
@@ -46,7 +46,7 @@ class LambdaRunnerTest: XCTestCase {
4646
func testFailure() throws {
4747
struct Behavior: LambdaServerBehavior {
4848
static let error = "boom"
49-
let requestId = NSUUID().uuidString
49+
let requestId = UUID().uuidString
5050
func getWork() -> GetWorkResult {
5151
return .success((requestId: self.requestId, payload: "hello"))
5252
}

Tests/SwiftAwsLambdaTests/LambdaTest.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class LambdaTest: XCTestCase {
9898
let timeout: Int64 = 100
9999
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: 1),
100100
runtimeEngine: .init(requestTimeout: .milliseconds(timeout)))
101+
101102
let server = try MockLambdaServer(behavior: GoodBehavior(requestId: "timeout", payload: "\(timeout * 2)")).start().wait()
102103
let result = Lambda.run(handler: EchoHandler(), configuration: configuration)
103104
try server.stop().wait()
@@ -161,7 +162,7 @@ private struct GoodBehavior: LambdaServerBehavior {
161162
let requestId: String
162163
let payload: String
163164

164-
init(requestId: String = NSUUID().uuidString, payload: String = NSUUID().uuidString) {
165+
init(requestId: String = UUID().uuidString, payload: String = UUID().uuidString) {
165166
self.requestId = requestId
166167
self.payload = payload
167168
}

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ This library is designed to simplify implementing an AWS Lambda using the Swift
4848
}
4949
```
5050

51-
See a complete example in [SwiftAwsLambdaSample](https://github.com/swift-server/swift-aws-lambda-sample).
51+
See a complete example in SwiftAwsLambdaSample.
5252

53-
3. Deploy to AWS Lambda. To do so, you need to compile your Application for EC2 Linux, package it as a Zip file, and upload to AWS. You can find sample build and deployment scripts in [SwiftAwsLambdaSample](https://github.com/swift-server/swift-aws-lambda-sample).
53+
3. Deploy to AWS Lambda. To do so, you need to compile your Application for EC2 Linux, package it as a Zip file, and upload to AWS. You can find sample build and deployment scripts in SwiftAwsLambdaSample.
5454

5555
## Architecture
5656

readme.md-e

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ This library is designed to simplify implementing an AWS Lambda using the Swift
4848
}
4949
```
5050

51-
See a complete example in [SwiftAwsLambdaSample](https://github.com/swift-server/swift-aws-lambda-sample).
51+
See a complete example in SwiftAwsLambdaSample.
5252

53-
3. Deploy to AWS Lambda. To do so, you need to compile your Application for EC2 Linux, package it as a Zip file, and upload to AWS. You can find sample build and deployment scripts in [SwiftAwsLambdaSample](https://github.com/swift-server/swift-aws-lambda-sample).
53+
3. Deploy to AWS Lambda. To do so, you need to compile your Application for EC2 Linux, package it as a Zip file, and upload to AWS. You can find sample build and deployment scripts in SwiftAwsLambdaSample.
5454

5555
## Architecture
5656

scripts/performance_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ cleanup() {
3434

3535
trap "cleanup" ERR
3636

37-
iterations=100
37+
iterations=1000
3838
results=()
3939

4040
#------------------

0 commit comments

Comments
 (0)