|
@@ -10,21 +10,20 @@ using System.Runtime.CompilerServices;
|
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
|
using Npgsql;
|
|
using Npgsql;
|
|
|
|
|
+using NpgsqlTypes;
|
|
|
|
|
|
|
|
namespace PlatformBenchmarks;
|
|
namespace PlatformBenchmarks;
|
|
|
|
|
|
|
|
public sealed class RawDb
|
|
public sealed class RawDb
|
|
|
{
|
|
{
|
|
|
- private readonly ConcurrentRandom _random;
|
|
|
|
|
private readonly MemoryCache _cache
|
|
private readonly MemoryCache _cache
|
|
|
= new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) });
|
|
= new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) });
|
|
|
|
|
|
|
|
private readonly NpgsqlDataSource _dataSource;
|
|
private readonly NpgsqlDataSource _dataSource;
|
|
|
|
|
|
|
|
- public RawDb(ConcurrentRandom random, AppSettings appSettings)
|
|
|
|
|
|
|
+ public RawDb(AppSettings appSettings)
|
|
|
{
|
|
{
|
|
|
- _random = random;
|
|
|
|
|
- _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString);
|
|
|
|
|
|
|
+ _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).EnableArrays().Build();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public async Task<World> LoadSingleQueryRow()
|
|
public async Task<World> LoadSingleQueryRow()
|
|
@@ -42,10 +41,10 @@ public sealed class RawDb
|
|
|
var result = new CachedWorld[count];
|
|
var result = new CachedWorld[count];
|
|
|
var cacheKeys = _cacheKeys;
|
|
var cacheKeys = _cacheKeys;
|
|
|
var cache = _cache;
|
|
var cache = _cache;
|
|
|
- var random = _random;
|
|
|
|
|
|
|
+
|
|
|
for (var i = 0; i < result.Length; i++)
|
|
for (var i = 0; i < result.Length; i++)
|
|
|
{
|
|
{
|
|
|
- var id = random.Next(1, 10001);
|
|
|
|
|
|
|
+ var id = Random.Shared.Next(1, 10001);
|
|
|
var key = cacheKeys[id];
|
|
var key = cacheKeys[id];
|
|
|
if (cache.TryGetValue(key, out var cached))
|
|
if (cache.TryGetValue(key, out var cached))
|
|
|
{
|
|
{
|
|
@@ -76,7 +75,7 @@ public sealed class RawDb
|
|
|
{
|
|
{
|
|
|
result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
|
|
result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
|
|
|
|
|
|
|
|
- id = rawdb._random.Next(1, 10001);
|
|
|
|
|
|
|
+ id = Random.Shared.Next(1, 10001);
|
|
|
idParameter.TypedValue = id;
|
|
idParameter.TypedValue = id;
|
|
|
key = cacheKeys[id];
|
|
key = cacheKeys[id];
|
|
|
}
|
|
}
|
|
@@ -109,30 +108,18 @@ public sealed class RawDb
|
|
|
|
|
|
|
|
using var connection = await _dataSource.OpenConnectionAsync();
|
|
using var connection = await _dataSource.OpenConnectionAsync();
|
|
|
|
|
|
|
|
- using var batch = new NpgsqlBatch(connection)
|
|
|
|
|
- {
|
|
|
|
|
- // Inserts a PG Sync message between each statement in the batch, required for compliance with
|
|
|
|
|
- // TechEmpower general test requirement 7
|
|
|
|
|
- // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
|
|
|
|
|
- EnableErrorBarriers = true
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- for (var i = 0; i < count; i++)
|
|
|
|
|
|
|
+ // It is not acceptable to execute multiple SELECTs within a single complex query.
|
|
|
|
|
+ // It is not acceptable to retrieve all required rows using a SELECT ... WHERE id IN (...) clause.
|
|
|
|
|
+ // Pipelining of network traffic between the application and database is permitted.
|
|
|
|
|
+
|
|
|
|
|
+ var (queryCmd, queryParameter) = CreateReadCommand(connection);
|
|
|
|
|
+ using (queryCmd)
|
|
|
{
|
|
{
|
|
|
- batch.BatchCommands.Add(new()
|
|
|
|
|
|
|
+ for (var i = 0; i < results.Length; i++)
|
|
|
{
|
|
{
|
|
|
- CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
|
|
|
|
|
- Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- using var reader = await batch.ExecuteReaderAsync();
|
|
|
|
|
-
|
|
|
|
|
- for (var i = 0; i < count; i++)
|
|
|
|
|
- {
|
|
|
|
|
- await reader.ReadAsync();
|
|
|
|
|
- results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) };
|
|
|
|
|
- await reader.NextResultAsync();
|
|
|
|
|
|
|
+ queryParameter.TypedValue = Random.Shared.Next(1, 10001);
|
|
|
|
|
+ results[i] = await ReadSingleRow(queryCmd);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
return results;
|
|
@@ -142,34 +129,55 @@ public sealed class RawDb
|
|
|
{
|
|
{
|
|
|
var results = new World[count];
|
|
var results = new World[count];
|
|
|
|
|
|
|
|
- using var connection = CreateConnection();
|
|
|
|
|
- await connection.OpenAsync();
|
|
|
|
|
|
|
+ var ids = new int[count];
|
|
|
|
|
+ var numbers = new int[count];
|
|
|
|
|
+
|
|
|
|
|
+ for (var i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ ids[i] = Random.Shared.Next(1, 10001);
|
|
|
|
|
+ }
|
|
|
|
|
+ Array.Sort(ids);
|
|
|
|
|
+
|
|
|
|
|
+ // Ensure unique ids by incrementing duplicates
|
|
|
|
|
+ for (var i = 1; i < count; i++)
|
|
|
|
|
+ if (ids[i] == ids[i - 1])
|
|
|
|
|
+ ids[i] = (ids[i] % 10000) + 1;
|
|
|
|
|
+
|
|
|
|
|
+ using var connection = await _dataSource.OpenConnectionAsync();
|
|
|
|
|
|
|
|
|
|
+ // Each row must be selected randomly using one query in the same fashion as the single database query test
|
|
|
|
|
+ // Use of IN clauses or similar means to consolidate multiple queries into one operation is not permitted.
|
|
|
|
|
+ // Similarly, use of a batch or multiple SELECTs within a single statement are not permitted
|
|
|
var (queryCmd, queryParameter) = CreateReadCommand(connection);
|
|
var (queryCmd, queryParameter) = CreateReadCommand(connection);
|
|
|
using (queryCmd)
|
|
using (queryCmd)
|
|
|
{
|
|
{
|
|
|
for (var i = 0; i < results.Length; i++)
|
|
for (var i = 0; i < results.Length; i++)
|
|
|
{
|
|
{
|
|
|
|
|
+ queryParameter.TypedValue = ids[i];
|
|
|
results[i] = await ReadSingleRow(queryCmd);
|
|
results[i] = await ReadSingleRow(queryCmd);
|
|
|
- queryParameter.TypedValue = _random.Next(1, 10001);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection))
|
|
|
|
|
|
|
+ for (var i = 0; i < count; i++)
|
|
|
{
|
|
{
|
|
|
- for (var i = 0; i < results.Length; i++)
|
|
|
|
|
|
|
+ var randomNumber = Random.Shared.Next(1, 10001);
|
|
|
|
|
+ if (results[i].RandomNumber == randomNumber)
|
|
|
{
|
|
{
|
|
|
- var randomNumber = _random.Next(1, 10001);
|
|
|
|
|
-
|
|
|
|
|
- updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = results[i].Id });
|
|
|
|
|
- updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = randomNumber });
|
|
|
|
|
-
|
|
|
|
|
- results[i].RandomNumber = randomNumber;
|
|
|
|
|
|
|
+ randomNumber = (randomNumber % 10000) + 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- await updateCmd.ExecuteNonQueryAsync();
|
|
|
|
|
|
|
+ results[i].RandomNumber = randomNumber;
|
|
|
|
|
+ numbers[i] = randomNumber;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id";
|
|
|
|
|
+
|
|
|
|
|
+ using var updateCmd = new NpgsqlCommand(update, connection);
|
|
|
|
|
+ updateCmd.Parameters.Add(new NpgsqlParameter<int[]> { TypedValue = ids, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer });
|
|
|
|
|
+ updateCmd.Parameters.Add(new NpgsqlParameter<int[]> { TypedValue = numbers, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer });
|
|
|
|
|
+
|
|
|
|
|
+ await updateCmd.ExecuteNonQueryAsync();
|
|
|
|
|
+
|
|
|
return results;
|
|
return results;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -203,7 +211,7 @@ public sealed class RawDb
|
|
|
private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
|
|
private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
|
|
|
{
|
|
{
|
|
|
var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection);
|
|
var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection);
|
|
|
- var parameter = new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) };
|
|
|
|
|
|
|
+ var parameter = new NpgsqlParameter<int> { TypedValue = Random.Shared.Next(1, 10001) };
|
|
|
|
|
|
|
|
cmd.Parameters.Add(parameter);
|
|
cmd.Parameters.Add(parameter);
|
|
|
|
|
|