|
@@ -1,32 +1,8 @@
|
|
namespace App
|
|
namespace App
|
|
|
|
|
|
open System
|
|
open System
|
|
-open System.Collections.Generic
|
|
|
|
open Oxpecker
|
|
open Oxpecker
|
|
|
|
|
|
-[<AutoOpen>]
|
|
|
|
-module Common =
|
|
|
|
-
|
|
|
|
- [<Struct>]
|
|
|
|
- [<CLIMutable>]
|
|
|
|
- type JsonMessage = {
|
|
|
|
- message : string
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- [<CLIMutable>]
|
|
|
|
- type Fortune = {
|
|
|
|
- id: int
|
|
|
|
- message: string
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- [<Literal>]
|
|
|
|
- let ConnectionString = "Server=tfb-database;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"
|
|
|
|
-
|
|
|
|
- let FortuneComparer = {
|
|
|
|
- new IComparer<Fortune> with
|
|
|
|
- member self.Compare(a,b) = String.CompareOrdinal(a.message, b.message)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
[<RequireQualifiedAccess>]
|
|
[<RequireQualifiedAccess>]
|
|
module HtmlViews =
|
|
module HtmlViews =
|
|
open Oxpecker.ViewEngine
|
|
open Oxpecker.ViewEngine
|
|
@@ -48,7 +24,7 @@ module HtmlViews =
|
|
th() { raw "message" }
|
|
th() { raw "message" }
|
|
}
|
|
}
|
|
|
|
|
|
- let fortunes fortunesData =
|
|
|
|
|
|
+ let fortunes (fortunesData: ResizeArray<Fortune>) =
|
|
table() {
|
|
table() {
|
|
fortunesTableHeader
|
|
fortunesTableHeader
|
|
for fortune in fortunesData do
|
|
for fortune in fortunesData do
|
|
@@ -60,8 +36,6 @@ module HtmlViews =
|
|
|
|
|
|
[<RequireQualifiedAccess>]
|
|
[<RequireQualifiedAccess>]
|
|
module HttpHandlers =
|
|
module HttpHandlers =
|
|
- open Dapper
|
|
|
|
- open Npgsql
|
|
|
|
open System.Text
|
|
open System.Text
|
|
open Microsoft.AspNetCore.Http
|
|
open Microsoft.AspNetCore.Http
|
|
open System.Text.Json
|
|
open System.Text.Json
|
|
@@ -72,32 +46,24 @@ module HttpHandlers =
|
|
message = "Additional fortune added at request time."
|
|
message = "Additional fortune added at request time."
|
|
}
|
|
}
|
|
|
|
|
|
- let rec private renderFortunes (ctx: HttpContext) (dbFortunes: Fortune seq) =
|
|
|
|
- let data = dbFortunes.AsList()
|
|
|
|
|
|
+ let rec private renderFortunes (ctx: HttpContext) (data: ResizeArray<Fortune>) =
|
|
data.Add extra
|
|
data.Add extra
|
|
data.Sort FortuneComparer
|
|
data.Sort FortuneComparer
|
|
data |> HtmlViews.fortunes |> ctx.WriteHtmlView
|
|
data |> HtmlViews.fortunes |> ctx.WriteHtmlView
|
|
|
|
|
|
- let private fortunes : EndpointHandler =
|
|
|
|
|
|
+ let fortunes : EndpointHandler =
|
|
fun ctx ->
|
|
fun ctx ->
|
|
task {
|
|
task {
|
|
- use conn = new NpgsqlConnection(ConnectionString)
|
|
|
|
- let! dbFortunes = conn.QueryAsync<Fortune>("SELECT id, message FROM fortune")
|
|
|
|
|
|
+ let! dbFortunes = loadFortunes ()
|
|
return! renderFortunes ctx dbFortunes
|
|
return! renderFortunes ctx dbFortunes
|
|
}
|
|
}
|
|
|
|
|
|
- [<Struct>]
|
|
|
|
- [<CLIMutable>]
|
|
|
|
- type World = {
|
|
|
|
- id: int
|
|
|
|
- randomnumber: int
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let private readSingleRow (conn: NpgsqlConnection) =
|
|
|
|
- conn.QueryFirstOrDefaultAsync<World>(
|
|
|
|
- "SELECT id, randomnumber FROM world WHERE id = @Id",
|
|
|
|
- {| Id = Random.Shared.Next(1, 10001) |}
|
|
|
|
- )
|
|
|
|
|
|
+ let singleQuery : EndpointHandler =
|
|
|
|
+ fun ctx ->
|
|
|
|
+ task {
|
|
|
|
+ let! result = loadSingleRow()
|
|
|
|
+ return! ctx.WriteJsonChunked result
|
|
|
|
+ }
|
|
|
|
|
|
let private parseQueries (ctx: HttpContext) =
|
|
let private parseQueries (ctx: HttpContext) =
|
|
match ctx.TryGetRouteValue<string>("count") with
|
|
match ctx.TryGetRouteValue<string>("count") with
|
|
@@ -107,66 +73,19 @@ module HttpHandlers =
|
|
| _, _ -> 1
|
|
| _, _ -> 1
|
|
| _ -> 1
|
|
| _ -> 1
|
|
|
|
|
|
- let private singleQuery : EndpointHandler =
|
|
|
|
- fun ctx ->
|
|
|
|
- task {
|
|
|
|
- use conn = new NpgsqlConnection(ConnectionString)
|
|
|
|
- let! result = readSingleRow conn
|
|
|
|
- return! ctx.WriteJsonChunked result
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let private multipleQueries : EndpointHandler =
|
|
|
|
|
|
+ let multipleQueries : EndpointHandler =
|
|
fun ctx ->
|
|
fun ctx ->
|
|
let count = parseQueries ctx
|
|
let count = parseQueries ctx
|
|
- let results = Array.zeroCreate<World> count
|
|
|
|
task {
|
|
task {
|
|
- use conn = new NpgsqlConnection(ConnectionString)
|
|
|
|
- do! conn.OpenAsync()
|
|
|
|
- for i in 0..results.Length-1 do
|
|
|
|
- let! result = readSingleRow conn
|
|
|
|
- results[i] <- result
|
|
|
|
|
|
+ let! results = loadMultipleRows count
|
|
return! ctx.WriteJsonChunked results
|
|
return! ctx.WriteJsonChunked results
|
|
}
|
|
}
|
|
|
|
|
|
- let private maxBatch = 500
|
|
|
|
- let private queries = Array.zeroCreate (maxBatch + 1)
|
|
|
|
-
|
|
|
|
- let private batchUpdateString batchSize =
|
|
|
|
- match queries[batchSize] with
|
|
|
|
- | null ->
|
|
|
|
- let lastIndex = batchSize - 1
|
|
|
|
- let sb = StringBuilder()
|
|
|
|
- sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ") |> ignore
|
|
|
|
- for i in 0..lastIndex-1 do
|
|
|
|
- sb.AppendFormat("(@Id_{0}, @Rn_{0}), ", i) |> ignore
|
|
|
|
- sb.AppendFormat("(@Id_{0}, @Rn_{0}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id", lastIndex) |> ignore
|
|
|
|
- let result = sb.ToString()
|
|
|
|
- queries[batchSize] <- result
|
|
|
|
- result
|
|
|
|
- | q ->
|
|
|
|
- q
|
|
|
|
-
|
|
|
|
- let private generateParameters (results: World[]) =
|
|
|
|
- let parameters = Dictionary<string,obj>()
|
|
|
|
- for i in 0..results.Length-1 do
|
|
|
|
- let randomNumber = Random.Shared.Next(1, 10001)
|
|
|
|
- parameters[$"@Rn_{i}"] <- randomNumber
|
|
|
|
- parameters[$"@Id_{i}"] <- results[i].id
|
|
|
|
- results[i] <- { results[i] with randomnumber = randomNumber }
|
|
|
|
- parameters
|
|
|
|
-
|
|
|
|
- let private multipleUpdates : EndpointHandler =
|
|
|
|
|
|
+ let multipleUpdates : EndpointHandler =
|
|
fun ctx ->
|
|
fun ctx ->
|
|
let count = parseQueries ctx
|
|
let count = parseQueries ctx
|
|
- let results = Array.zeroCreate<World> count
|
|
|
|
task {
|
|
task {
|
|
- use conn = new NpgsqlConnection(ConnectionString)
|
|
|
|
- do! conn.OpenAsync()
|
|
|
|
- for i in 0..results.Length-1 do
|
|
|
|
- let! result = readSingleRow conn
|
|
|
|
- results[i] <- result
|
|
|
|
- let parameters = generateParameters results
|
|
|
|
- let! _ = conn.ExecuteAsync(batchUpdateString count, parameters)
|
|
|
|
|
|
+ let! results = doMultipleUpdates count
|
|
return! ctx.WriteJsonChunked results
|
|
return! ctx.WriteJsonChunked results
|
|
}
|
|
}
|
|
|
|
|