diff --git a/frameworks/FSharp/giraffe/benchmark_config.json b/frameworks/FSharp/giraffe/benchmark_config.json
index 5552420b37f..cc2e70a748f 100644
--- a/frameworks/FSharp/giraffe/benchmark_config.json
+++ b/frameworks/FSharp/giraffe/benchmark_config.json
@@ -7,6 +7,7 @@
"json_url": "/json",
"db_url": "/db",
"query_url": "/queries?queries=",
+ "update_url": "/updates?queries=",
"fortune_url": "/fortunes",
"port": 8080,
"approach": "Realistic",
diff --git a/frameworks/FSharp/giraffe/config.toml b/frameworks/FSharp/giraffe/config.toml
index a44ef83d919..ada171349cb 100644
--- a/frameworks/FSharp/giraffe/config.toml
+++ b/frameworks/FSharp/giraffe/config.toml
@@ -2,11 +2,12 @@
name = "giraffe"
[main]
-urls.plaintext = "/plaintext"
urls.json = "/json"
urls.db = "/db"
urls.query = "/queries?queries="
urls.fortune = "/fortunes"
+urls.update = "/updates?queries="
+urls.plaintext = "/plaintext"
approach = "Realistic"
classification = "fullstack"
database = "Postgres"
diff --git a/frameworks/FSharp/giraffe/src/App/App.fsproj b/frameworks/FSharp/giraffe/src/App/App.fsproj
index 215f3bfc001..ef58a0c3618 100644
--- a/frameworks/FSharp/giraffe/src/App/App.fsproj
+++ b/frameworks/FSharp/giraffe/src/App/App.fsproj
@@ -7,10 +7,10 @@
-
+
diff --git a/frameworks/FSharp/giraffe/src/App/Program.fs b/frameworks/FSharp/giraffe/src/App/Program.fs
index b4e63e7333f..f7d62db7d33 100644
--- a/frameworks/FSharp/giraffe/src/App/Program.fs
+++ b/frameworks/FSharp/giraffe/src/App/Program.fs
@@ -18,15 +18,15 @@ module Common =
Database=hello_world;
User Id=benchmarkdbuser;
Password=benchmarkdbpass;
- SSL Mode=Disable;
Maximum Pool Size=1024;
NoResetOnClose=true;
Enlist=false;
- Max Auto Prepare=4;
- Multiplexing=true;
- Write Coalescing Buffer Threshold Bytes=1000
+ Max Auto Prepare=4
"""
+ []
+ let MultiplexedConnectionString = ConnectionString + ";Multiplexing=true"
+
[]
type JsonMode =
| System
@@ -46,8 +46,43 @@ module Common =
member _.Next () = rnd.Next (min, max)
- []
- let MaxDegreeOfParallelism = 3
+ // Cache SQL strings for bulk updates to avoid rebuilding on every request
+ // This module generates and caches SQL UPDATE statements that use PostgreSQL's
+ // VALUES clause to update multiple rows in a single statement.
+ module BatchUpdateSql =
+ let private cache = Array.zeroCreate 501
+
+ let get (count: int) =
+ match cache.[count] with
+ | null ->
+ let lastIndex = count - 1
+
+ // Build the VALUES clause: (@Id_0, @Rn_0), (@Id_1, @Rn_1), ...
+ // Each pair represents (id, new_randomnumber) for one row
+ let valueClauses =
+ List.init lastIndex (fun i -> sprintf "(@Id_%d, @Rn_%d), " i i)
+ |> String.concat ""
+
+ // The final SQL uses a CTE-like VALUES construct to update multiple rows:
+ // UPDATE world SET randomnumber = temp.randomnumber
+ // FROM (VALUES ...) AS temp(id, randomnumber)
+ // WHERE temp.id = world.id
+ let sql =
+ sprintf
+ """
+ UPDATE world
+ SET randomnumber = temp.randomnumber
+ FROM (VALUES %s(@Id_%d, @Rn_%d) ORDER BY 1)
+ AS temp(id, randomnumber)
+ WHERE temp.id = world.id
+ """
+ valueClauses
+ lastIndex
+ lastIndex
+
+ cache.[count] <- sql
+ sql
+ | sql -> sql
[]
module HtmlViews =
@@ -87,10 +122,12 @@ module HttpHandlers =
message = "Additional fortune added at request time."
}
- let private fortunes: HttpHandler =
+ let fortunes: HttpHandler =
fun _ ctx ->
task {
- use conn = new NpgsqlConnection (ConnectionString)
+ let dataSource = ctx.GetService ()
+ use conn = dataSource.CreateConnection ()
+ do! conn.OpenAsync ()
let! data = conn.QueryAsync ("SELECT id, message FROM fortune")
@@ -106,11 +143,13 @@ module HttpHandlers =
return! ctx.WriteBytesAsync bytes
}
- let private db: HttpHandler =
+ let db: HttpHandler =
fun _ ctx ->
task {
let rnd = ctx.GetService ()
- use conn = new NpgsqlConnection (ConnectionString)
+ let dataSource = ctx.GetService ()
+ use conn = dataSource.CreateConnection ()
+ do! conn.OpenAsync ()
let! data =
conn.QuerySingleAsync (
@@ -121,7 +160,7 @@ module HttpHandlers =
return! ctx.WriteJsonAsync data
}
- let private queries: HttpHandler =
+ let queries: HttpHandler =
fun _ ctx ->
task {
let queryParam =
@@ -137,30 +176,94 @@ module HttpHandlers =
|> Option.defaultValue 1
let rnd = ctx.GetService ()
+ let dataSource = ctx.GetService ()
+
+ use conn = dataSource.CreateConnection ()
+ do! conn.OpenAsync ()
- let! res =
- Array.init queryParam (fun _ -> rnd.Next ())
- |> Array.map (fun id ->
- use conn = new NpgsqlConnection (ConnectionString)
+ // Read all rows sequentially
+ let results = Array.zeroCreate queryParam
+ for i in 0 .. queryParam - 1 do
+ let! world =
conn.QuerySingleAsync (
"SELECT id, randomnumber FROM world WHERE id = @Id",
- {| Id = id |}
+ {| Id = rnd.Next () |}
)
- |> Async.AwaitTask
+
+ results.[i] <- world
+
+ return! ctx.WriteJsonAsync results
+ }
+
+ let updates: HttpHandler =
+ fun _ ctx ->
+ task {
+ let queryParam =
+ ctx.TryGetQueryStringValue "queries"
+ |> Option.map (fun value ->
+ match System.Int32.TryParse value with
+ | true, intValue ->
+ if intValue < 1 then 1
+ elif intValue > 500 then 500
+ else intValue
+ | false, _ -> 1
)
- |> fun computations ->
- Async.Parallel (computations, MaxDegreeOfParallelism)
+ |> Option.defaultValue 1
+
+ let rnd = ctx.GetService ()
+
+ // Use multiplexed connection for updates (more efficient for sequential ops)
+ use conn = new NpgsqlConnection (MultiplexedConnectionString)
+ do! conn.OpenAsync ()
+
+ // Read all rows sequentially
+ let readResults = Array.zeroCreate queryParam
- return! ctx.WriteJsonAsync res
+ for i in 0 .. queryParam - 1 do
+ let! world =
+ conn.QuerySingleAsync (
+ "SELECT id, randomnumber FROM world WHERE id = @Id",
+ {| Id = rnd.Next () |}
+ )
+
+ readResults.[i] <- world
+
+ // Update random numbers functionally
+ let updatedData =
+ readResults
+ |> Array.map (fun data -> { data with randomNumber = rnd.Next () })
+
+ // Build bulk update parameters functionally
+ // We use a single UPDATE statement with a VALUES clause to update all rows at once.
+ //
+ // Example SQL for 2 rows:
+ // UPDATE world SET randomnumber = temp.randomnumber
+ // FROM (VALUES (@Id_0, @Rn_0), (@Id_1, @Rn_1) ORDER BY 1) AS temp(id, randomnumber)
+ // WHERE temp.id = world.id
+ let updateParams =
+ updatedData
+ |> Array.mapi (fun i data -> [
+ sprintf "@Id_%d" i, box data.id // Parameter for the id
+ sprintf "@Rn_%d" i, box data.randomNumber // Parameter for the new random number
+ ])
+ |> Array.collect List.toArray // Flatten the list of parameter pairs
+ |> dict // Convert to dictionary for Dapper
+
+ // Execute bulk update using Dapper
+ let sql = BatchUpdateSql.get queryParam
+ let! _ = conn.ExecuteAsync (sql, updateParams)
+
+ return! ctx.WriteJsonAsync updatedData
}
let endpoints: Endpoint list = [
- route "/plaintext" (text "Hello, World!")
route "/json" (json {| message = "Hello, World!" |})
route "/db" db
route "/queries" queries
route "/fortunes" fortunes
+ route "/updates" updates
+ route "/plaintext" (text "Hello, World!")
]
@@ -173,6 +276,7 @@ module Main =
open Microsoft.Extensions.Logging
open System.Text.Json
open Newtonsoft.Json
+ open Npgsql
[]
let main args =
@@ -202,6 +306,7 @@ module Main =
builder.Services
.AddSingleton(jsonSerializer)
.AddSingleton(rnd)
+ .AddSingleton(NpgsqlDataSource.Create (ConnectionString))
.AddGiraffe ()
|> ignore