Browse Source

Use Hummingbird connection pool (#7187)

Dropped PostgresKit
Use query.decode methods
Adam Fowler 3 years ago
parent
commit
8512f57a34

+ 5 - 5
frameworks/Swift/hummingbird/src-postgres/Package.swift

@@ -1,4 +1,4 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.5
 // The swift-tools-version declares the minimum version of Swift required to build this package.
 
 import PackageDescription
@@ -10,17 +10,17 @@ let package = Package(
         .executable(name: "server", targets: ["server"])
     ],
     dependencies: [
-        .package(url: "https://github.com/hummingbird-project/hummingbird.git", .upToNextMinor(from: "0.13.1")),
+        .package(url: "https://github.com/hummingbird-project/hummingbird.git", .upToNextMinor(from: "0.16.0")),
         .package(url: "https://github.com/hummingbird-project/hummingbird-mustache.git", from: "1.0.1"),
-        .package(url: "https://github.com/vapor/postgres-kit.git", from: "2.3.0"),
+        .package(url: "https://github.com/vapor/postgres-nio.git", from: "1.8.0"),
     ],
     targets: [
-        .target(name: "server",
+        .executableTarget(name: "server",
             dependencies: [
                 .product(name: "Hummingbird", package: "hummingbird"),
                 .product(name: "HummingbirdFoundation", package: "hummingbird"),
                 .product(name: "HummingbirdMustache", package: "hummingbird-mustache"),
-                .product(name: "PostgresKit", package: "postgres-kit"),
+                .product(name: "PostgresNIO", package: "postgres-nio"),
             ],
             swiftSettings: [
                 // Enable better optimizations when building in Release configuration. Despite the use of

+ 14 - 8
frameworks/Swift/hummingbird/src-postgres/Sources/server/Controllers/FortunesController.swift

@@ -1,6 +1,6 @@
 import Hummingbird
 import HummingbirdMustache
-import PostgresKit
+import PostgresNIO
 
 struct HTML: HBResponseGenerator {
     let html: String
@@ -11,9 +11,11 @@ struct HTML: HBResponseGenerator {
 }
 
 class FortunesController {
+    let connectionPoolGroup: HBConnectionPoolGroup<PostgresConnectionSource>
     let template: HBMustacheTemplate
 
-    init() {
+    init(connectionPoolGroup: HBConnectionPoolGroup<PostgresConnectionSource>) {
+        self.connectionPoolGroup = connectionPoolGroup
         self.template = try! HBMustacheTemplate(string: """
         <!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>{{#.}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/.}}</table></body></html>
         """)
@@ -24,16 +26,20 @@ class FortunesController {
     }
 
     func fortunes(request: HBRequest) -> EventLoopFuture<HTML> {
-        return request.db.query("SELECT id, message FROM Fortune").map { results in
-            var fortunes = results.map {
-                return Fortune(
-                    id: $0.column("id")?.int32,
-                    message: $0.column("message")?.string ?? ""
-                )
+        return self.connection(for: request) { connection in 
+            return connection.query("SELECT id, message FROM Fortune")
+        }.flatMapThrowing { results in
+            var fortunes = try results.map { result -> Fortune in
+                let decoded = try result.decode((Int32, String).self, context: .default)
+                return Fortune(id: decoded.0, message: decoded.1)
             }
             fortunes.append(.init(id: 0, message: "Additional fortune added at request time."))
             let sortedFortunes = fortunes.sorted { $0.message < $1.message }
             return HTML(html: self.template.render(sortedFortunes) )
         }
     }
+
+    @discardableResult func connection<NewValue>(for request: HBRequest, closure: @escaping (PostgresConnection) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
+        return self.connectionPoolGroup.lease(on: request.eventLoop, logger: request.logger, process: closure)
+    }
 }

+ 30 - 22
frameworks/Swift/hummingbird/src-postgres/Sources/server/Controllers/WorldController.swift

@@ -1,7 +1,9 @@
 import Hummingbird
-import PostgresKit
+import PostgresNIO
+
+struct WorldController {
+    let connectionPoolGroup: HBConnectionPoolGroup<PostgresConnectionSource>
 
-class WorldController {
     func add(to router: HBRouter) {
         router.get("db", use: single)
         router.get("queries", use: multiple)
@@ -10,14 +12,14 @@ class WorldController {
 
     func single(request: HBRequest) -> EventLoopFuture<World> {
         let id = Int32.random(in: 1...10_000)
-        return request.db.query("SELECT id, randomnumber FROM World WHERE id = $1", [
-            PostgresData(int32: id)
-        ]).flatMapThrowing { result -> World in
+        return self.connection(for: request) { connection in
+            return connection.query("SELECT id, randomnumber FROM World WHERE id = $1", [
+                PostgresData(int32: id)
+            ])
+        }.flatMapThrowing { result -> World in
             guard let firstResult = result.first else { throw HBHTTPError(.notFound) }
-            return World(
-                id: id,
-                randomNumber: firstResult.column("randomnumber")?.int32 ?? 0
-            )
+            let result = try firstResult.decode((Int32, Int32).self, context: .default)
+            return World(id: result.0, randomNumber: result.1)
         }
     }
 
@@ -25,14 +27,14 @@ class WorldController {
         let queries = (request.uri.queryParameters.get("queries", as: Int.self) ?? 1).bound(1, 500)
         let futures: [EventLoopFuture<World>] = (0 ..< queries).map { _ -> EventLoopFuture<World> in
             let id = Int32.random(in: 1...10_000)
-            return request.db.query("SELECT id, randomnumber FROM World WHERE id = $1", [
-                PostgresData(int32: id)
-            ]).flatMapThrowing { result -> World in
+            return self.connection(for: request) { connection in
+                return connection.query("SELECT id, randomnumber FROM World WHERE id = $1", [
+                    PostgresData(int32: id)
+                ])
+            }.flatMapThrowing { result -> World in
                 guard let firstResult = result.first else { throw HBHTTPError(.notFound) }
-                return World(
-                    id: id,
-                    randomNumber: firstResult.column("randomnumber")?.int32 ?? 0
-                )
+                let result = try firstResult.decode((Int32, Int32).self, context: .default)
+                return World(id: result.0, randomNumber: result.1)
             }
         }
         return EventLoopFuture.whenAllSucceed(futures, on: request.eventLoop)
@@ -44,17 +46,23 @@ class WorldController {
         let futures: [EventLoopFuture<World>] = ids.map { _ -> EventLoopFuture<World> in
             let id = Int32.random(in: 1...10_000)
             let randomNumber = Int32.random(in: 1...10_000)
-            return request.db.query("SELECT id, randomnumber FROM World WHERE id = $1", [
-                PostgresData(int32: id)
-            ]).flatMap { result in
-                return request.db.query("UPDATE World SET randomnumber = $1 WHERE id = $2", [
-                    PostgresData(int32: randomNumber),
+            return self.connection(for: request) { connection in
+                return connection.query("SELECT id, randomnumber FROM World WHERE id = $1", [
                     PostgresData(int32: id)
-                ])
+                ]).flatMap { result in
+                    return connection.query("UPDATE World SET randomnumber = $1 WHERE id = $2", [
+                        PostgresData(int32: randomNumber),
+                        PostgresData(int32: id)
+                    ])
+                }
             }.map { _ in
                 return World(id: id, randomNumber: randomNumber)
             }
         }
         return EventLoopFuture.whenAllSucceed(futures, on: request.eventLoop)
     }
+
+    @discardableResult func connection<NewValue>(for request: HBRequest, closure: @escaping (PostgresConnection) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
+        return self.connectionPoolGroup.lease(on: request.eventLoop, logger: request.logger, process: closure)
+    }
 }

+ 1 - 1
frameworks/Swift/hummingbird/src-postgres/Sources/server/Models/Fortune.swift

@@ -2,7 +2,7 @@ import Hummingbird
 import HummingbirdMustache
 
 struct Fortune: HBResponseEncodable {
-    var id: Int32?
+    var id: Int32
     var message: String
 }
 

+ 1 - 1
frameworks/Swift/hummingbird/src-postgres/Sources/server/Models/World.swift

@@ -1,7 +1,7 @@
 import Hummingbird
 
 struct World: HBResponseEncodable {
-    var id: Int32?
+    var id: Int32
     var randomNumber: Int32
 }
 

+ 25 - 0
frameworks/Swift/hummingbird/src-postgres/Sources/server/PostgresConnectionSource.swift

@@ -0,0 +1,25 @@
+import Hummingbird
+import Logging
+import PostgresNIO
+
+extension PostgresConnection: HBConnection {
+    public func close(on eventLoop: EventLoop) -> EventLoopFuture<Void> {
+        return close().hop(to: eventLoop)
+    }
+}
+
+struct PostgresConnectionSource: HBConnectionSource {
+    typealias Connection = PostgresConnection
+    
+    let configuration: PostgresConnection.Configuration
+
+    init(configuration: PostgresConnection.Configuration) {
+        self.configuration = configuration
+    }
+
+    func makeConnection(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<Connection> {
+        let connection = PostgresConnection.connect(on: eventLoop, configuration: self.configuration, id: 0, logger: logger)
+        return connection
+    }
+}
+

+ 0 - 33
frameworks/Swift/hummingbird/src-postgres/Sources/server/database.swift

@@ -1,33 +0,0 @@
-import Hummingbird
-import PostgresKit
-
-// tfb-server (aka, citrine) uses 28 hyper-threaded cores
-// postgresql.conf specifies max_connections = 2000
-//
-// 2000 / (28 * 2) = 35.7 (theoretical max)
-//
-// https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Environment#citrine-self-hosted
-// https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/toolset/databases/postgres/postgresql.conf#L64
-let maxConnectionsPerEventLoop = 32
-var connectionPool: EventLoopGroupConnectionPool<PostgresConnectionSource>!
-
-extension HBApplication {
-    func initConnectionPool() {
-        connectionPool = EventLoopGroupConnectionPool(
-            source: PostgresConnectionSource(configuration: .init(
-                hostname: "tfb-database",
-                username: "benchmarkdbuser",
-                password: "benchmarkdbpass",
-                database: "hello_world"
-            )),
-            maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
-            on: self.eventLoopGroup
-        )
-    }
-}
-
-extension HBRequest {
-    var db: PostgresDatabase {
-        connectionPool.pool(for: self.eventLoop).database(logger: self.logger)
-    }
-}

+ 36 - 5
frameworks/Swift/hummingbird/src-postgres/Sources/server/main.swift

@@ -1,6 +1,15 @@
 import Hummingbird
 import HummingbirdFoundation
-import PostgresKit
+import PostgresNIO
+
+// tfb-server (aka, citrine) uses 28 hyper-threaded cores
+// postgresql.conf specifies max_connections = 2000
+//
+// 2000 / (28 * 2) = 35.7 (theoretical max)
+//
+// https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Environment#citrine-self-hosted
+// https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/toolset/databases/postgres/postgresql.conf#L64
+let maxConnectionsPerEventLoop = 32
 
 extension Int {
     func bound(_ minValue: Int, _ maxValue: Int) -> Int {
@@ -8,6 +17,16 @@ extension Int {
     }
 }
 
+extension HBApplication {
+    var postgresConnectionGroup: HBConnectionPoolGroup<PostgresConnectionSource> {
+        get { self.extensions.get(\.postgresConnectionGroup) }
+        set { 
+            self.extensions.set(\.postgresConnectionGroup, value: newValue) { group in
+                try group.close().wait()
+            }
+        }
+    }
+}
 func runApp() throws {
     let env = HBEnvironment()
     let serverHostName = env.get("SERVER_HOSTNAME") ?? "127.0.0.1"
@@ -19,10 +38,22 @@ func runApp() throws {
     )
     let app = HBApplication(configuration: configuration)
     app.encoder = JSONEncoder()
-    app.initConnectionPool()
-
-    WorldController().add(to: app.router)
-    FortunesController().add(to: app.router)
+ 
+    app.postgresConnectionGroup = .init(
+        source: .init(
+            configuration: .init(
+                connection: .init(host: "tfb-database"),
+                authentication: .init(username: "benchmarkdbuser", database: "hello_world", password: "benchmarkdbpass"),
+                tls: .disable
+            )
+        ), 
+        maxConnections: maxConnectionsPerEventLoop, 
+        eventLoopGroup: app.eventLoopGroup, 
+        logger: app.logger
+    )
+ 
+    WorldController(connectionPoolGroup: app.postgresConnectionGroup).add(to: app.router)
+    FortunesController(connectionPoolGroup: app.postgresConnectionGroup).add(to: app.router)
 
     try app.start()
     app.wait()