|
@@ -3,11 +3,7 @@
|
|
|
|
|
|
#if NPGSQL
|
|
#if NPGSQL
|
|
|
|
|
|
-using System;
|
|
|
|
-using System.Collections.Generic;
|
|
|
|
-using System.Linq;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.CompilerServices;
|
|
-using System.Threading.Tasks;
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Npgsql;
|
|
using Npgsql;
|
|
|
|
|
|
@@ -18,53 +14,63 @@ namespace PlatformBenchmarks
|
|
public sealed class RawDb
|
|
public sealed class RawDb
|
|
{
|
|
{
|
|
private readonly ConcurrentRandom _random;
|
|
private readonly ConcurrentRandom _random;
|
|
- private readonly string _connectionString;
|
|
|
|
private readonly MemoryCache _cache = new(
|
|
private readonly MemoryCache _cache = new(
|
|
new MemoryCacheOptions
|
|
new MemoryCacheOptions
|
|
{
|
|
{
|
|
ExpirationScanFrequency = TimeSpan.FromMinutes(60)
|
|
ExpirationScanFrequency = TimeSpan.FromMinutes(60)
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+ private readonly NpgsqlDataSource _dataSource;
|
|
|
|
+
|
|
public RawDb(ConcurrentRandom random, AppSettings appSettings)
|
|
public RawDb(ConcurrentRandom random, AppSettings appSettings)
|
|
{
|
|
{
|
|
_random = random;
|
|
_random = random;
|
|
- _connectionString = appSettings.ConnectionString;
|
|
|
|
|
|
+ _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString);
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<World> LoadSingleQueryRow()
|
|
public async Task<World> LoadSingleQueryRow()
|
|
{
|
|
{
|
|
- using (var db = new NpgsqlConnection(_connectionString))
|
|
|
|
- {
|
|
|
|
- await db.OpenAsync();
|
|
|
|
|
|
+ using var db = _dataSource.CreateConnection();
|
|
|
|
+ await db.OpenAsync();
|
|
|
|
|
|
- var (cmd, _) = CreateReadCommand(db);
|
|
|
|
- using (cmd)
|
|
|
|
- {
|
|
|
|
- return await ReadSingleRow(cmd);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ var (cmd, _) = CreateReadCommand(db);
|
|
|
|
+ using var command = cmd;
|
|
|
|
+ return await ReadSingleRow(cmd);
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<World[]> LoadMultipleQueriesRows(int count)
|
|
public async Task<World[]> LoadMultipleQueriesRows(int count)
|
|
{
|
|
{
|
|
- var result = new World[count];
|
|
|
|
|
|
+ var results = new World[count];
|
|
|
|
+
|
|
|
|
+ using var connection = await _dataSource.OpenConnectionAsync();
|
|
|
|
|
|
- using (var db = new NpgsqlConnection(_connectionString))
|
|
|
|
|
|
+ using var batch = new NpgsqlBatch(connection)
|
|
{
|
|
{
|
|
- await db.OpenAsync();
|
|
|
|
|
|
+ // 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
|
|
|
|
+ };
|
|
|
|
|
|
- var (cmd, idParameter) = CreateReadCommand(db);
|
|
|
|
- using (cmd)
|
|
|
|
|
|
+ for (var i = 0; i < count; i++)
|
|
|
|
+ {
|
|
|
|
+ batch.BatchCommands.Add(new()
|
|
{
|
|
{
|
|
- for (int i = 0; i < result.Length; i++)
|
|
|
|
- {
|
|
|
|
- result[i] = await ReadSingleRow(cmd);
|
|
|
|
- idParameter.TypedValue = _random.Next(1, 10001);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
|
|
|
|
+ Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
- return result;
|
|
|
|
|
|
+ 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();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return results;
|
|
}
|
|
}
|
|
|
|
|
|
public Task<CachedWorld[]> LoadCachedQueries(int count)
|
|
public Task<CachedWorld[]> LoadCachedQueries(int count)
|
|
@@ -77,7 +83,7 @@ namespace PlatformBenchmarks
|
|
{
|
|
{
|
|
var id = random.Next(1, 10001);
|
|
var id = random.Next(1, 10001);
|
|
var key = cacheKeys[id];
|
|
var key = cacheKeys[id];
|
|
- if (cache.TryGetValue(key, out object cached))
|
|
|
|
|
|
+ if (cache.TryGetValue(key, out var cached))
|
|
{
|
|
{
|
|
result[i] = (CachedWorld)cached;
|
|
result[i] = (CachedWorld)cached;
|
|
}
|
|
}
|
|
@@ -91,32 +97,25 @@ namespace PlatformBenchmarks
|
|
|
|
|
|
static async Task<CachedWorld[]> LoadUncachedQueries(int id, int i, int count, RawDb rawdb, CachedWorld[] result)
|
|
static async Task<CachedWorld[]> LoadUncachedQueries(int id, int i, int count, RawDb rawdb, CachedWorld[] result)
|
|
{
|
|
{
|
|
- using (var db = new NpgsqlConnection(rawdb._connectionString))
|
|
|
|
- {
|
|
|
|
- await db.OpenAsync();
|
|
|
|
|
|
+ using var db = rawdb._dataSource.CreateConnection();
|
|
|
|
+ await db.OpenAsync();
|
|
|
|
|
|
- var (cmd, idParameter) = rawdb.CreateReadCommand(db);
|
|
|
|
- using (cmd)
|
|
|
|
- {
|
|
|
|
- Func<ICacheEntry, Task<CachedWorld>> create = async _ =>
|
|
|
|
- {
|
|
|
|
- return await rawdb.ReadSingleRow(cmd);
|
|
|
|
- };
|
|
|
|
|
|
+ var (cmd, idParameter) = rawdb.CreateReadCommand(db);
|
|
|
|
+ using var command = cmd;
|
|
|
|
+ async Task<CachedWorld> create(ICacheEntry _) => await ReadSingleRow(cmd);
|
|
|
|
|
|
- var cacheKeys = _cacheKeys;
|
|
|
|
- var key = cacheKeys[id];
|
|
|
|
|
|
+ var cacheKeys = _cacheKeys;
|
|
|
|
+ var key = cacheKeys[id];
|
|
|
|
|
|
- idParameter.TypedValue = id;
|
|
|
|
|
|
+ idParameter.TypedValue = id;
|
|
|
|
|
|
- for (; i < result.Length; i++)
|
|
|
|
- {
|
|
|
|
- result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
|
|
|
|
|
|
+ for (; i < result.Length; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = await rawdb._cache.GetOrCreateAsync(key, create);
|
|
|
|
|
|
- id = rawdb._random.Next(1, 10001);
|
|
|
|
- idParameter.TypedValue = id;
|
|
|
|
- key = cacheKeys[id];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ id = rawdb._random.Next(1, 10001);
|
|
|
|
+ idParameter.TypedValue = id;
|
|
|
|
+ key = cacheKeys[id];
|
|
}
|
|
}
|
|
|
|
|
|
return result;
|
|
return result;
|
|
@@ -125,21 +124,17 @@ namespace PlatformBenchmarks
|
|
|
|
|
|
public async Task PopulateCache()
|
|
public async Task PopulateCache()
|
|
{
|
|
{
|
|
- using (var db = new NpgsqlConnection(_connectionString))
|
|
|
|
- {
|
|
|
|
- await db.OpenAsync();
|
|
|
|
|
|
+ using var db = _dataSource.CreateConnection();
|
|
|
|
+ await db.OpenAsync();
|
|
|
|
|
|
- var (cmd, idParameter) = CreateReadCommand(db);
|
|
|
|
- using (cmd)
|
|
|
|
- {
|
|
|
|
- var cacheKeys = _cacheKeys;
|
|
|
|
- var cache = _cache;
|
|
|
|
- for (var i = 1; i < 10001; i++)
|
|
|
|
- {
|
|
|
|
- idParameter.TypedValue = i;
|
|
|
|
- cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ var (cmd, idParameter) = CreateReadCommand(db);
|
|
|
|
+ using var command = cmd;
|
|
|
|
+ var cacheKeys = _cacheKeys;
|
|
|
|
+ var cache = _cache;
|
|
|
|
+ for (var i = 1; i < 10001; i++)
|
|
|
|
+ {
|
|
|
|
+ idParameter.TypedValue = i;
|
|
|
|
+ cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
|
|
}
|
|
}
|
|
|
|
|
|
Console.WriteLine("Caching Populated");
|
|
Console.WriteLine("Caching Populated");
|
|
@@ -149,70 +144,81 @@ namespace PlatformBenchmarks
|
|
{
|
|
{
|
|
var results = new World[count];
|
|
var results = new World[count];
|
|
|
|
|
|
- using (var db = new NpgsqlConnection(_connectionString))
|
|
|
|
|
|
+ using var connection = _dataSource.CreateConnection();
|
|
|
|
+ await connection.OpenAsync();
|
|
|
|
+
|
|
|
|
+ using (var batch = new NpgsqlBatch(connection))
|
|
{
|
|
{
|
|
- await db.OpenAsync();
|
|
|
|
|
|
+ // 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
|
|
|
|
+ batch.EnableErrorBarriers = true;
|
|
|
|
|
|
- var (queryCmd, queryParameter) = CreateReadCommand(db);
|
|
|
|
- using (queryCmd)
|
|
|
|
|
|
+ for (var i = 0; i < count; i++)
|
|
{
|
|
{
|
|
- for (int i = 0; i < results.Length; i++)
|
|
|
|
|
|
+ batch.BatchCommands.Add(new()
|
|
{
|
|
{
|
|
- results[i] = await ReadSingleRow(queryCmd);
|
|
|
|
- queryParameter.TypedValue = _random.Next(1, 10001);
|
|
|
|
- }
|
|
|
|
|
|
+ CommandText = "SELECT id, randomnumber FROM world WHERE id = $1",
|
|
|
|
+ Parameters = { new NpgsqlParameter<int> { TypedValue = _random.Next(1, 10001) } }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
- using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), db))
|
|
|
|
- {
|
|
|
|
- var ids = BatchUpdateString.Ids;
|
|
|
|
- var randoms = BatchUpdateString.Randoms;
|
|
|
|
|
|
+ using var reader = await batch.ExecuteReaderAsync();
|
|
|
|
|
|
- for (int i = 0; i < results.Length; i++)
|
|
|
|
- {
|
|
|
|
- var randomNumber = _random.Next(1, 10001);
|
|
|
|
|
|
+ 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();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: ids[i], value: results[i].Id));
|
|
|
|
- updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: randoms[i], value: randomNumber));
|
|
|
|
|
|
+ using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection))
|
|
|
|
+ {
|
|
|
|
+ for (int i = 0; i < results.Length; i++)
|
|
|
|
+ {
|
|
|
|
+ var randomNumber = _random.Next(1, 10001);
|
|
|
|
|
|
- results[i].RandomNumber = randomNumber;
|
|
|
|
- }
|
|
|
|
|
|
+ updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = results[i].Id });
|
|
|
|
+ updateCmd.Parameters.Add(new NpgsqlParameter<int> { TypedValue = randomNumber });
|
|
|
|
|
|
- await updateCmd.ExecuteNonQueryAsync();
|
|
|
|
|
|
+ results[i].RandomNumber = randomNumber;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ await updateCmd.ExecuteNonQueryAsync();
|
|
}
|
|
}
|
|
|
|
|
|
return results;
|
|
return results;
|
|
}
|
|
}
|
|
|
|
|
|
- public async Task<List<Fortune>> LoadFortunesRows()
|
|
|
|
|
|
+ public async Task<List<FortuneUtf8>> LoadFortunesRows()
|
|
{
|
|
{
|
|
- var result = new List<Fortune>();
|
|
|
|
|
|
+ var result = new List<FortuneUtf8>();
|
|
|
|
|
|
- using (var db = new NpgsqlConnection(_connectionString))
|
|
|
|
|
|
+ using (var db = _dataSource.CreateConnection())
|
|
{
|
|
{
|
|
await db.OpenAsync();
|
|
await db.OpenAsync();
|
|
|
|
|
|
- using (var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", db))
|
|
|
|
- using (var rdr = await cmd.ExecuteReaderAsync())
|
|
|
|
|
|
+ using var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", db);
|
|
|
|
+ using var rdr = await cmd.ExecuteReaderAsync();
|
|
|
|
+ while (await rdr.ReadAsync())
|
|
{
|
|
{
|
|
- while (await rdr.ReadAsync())
|
|
|
|
- {
|
|
|
|
- result.Add(new Fortune
|
|
|
|
- (
|
|
|
|
- id:rdr.GetInt32(0),
|
|
|
|
- message: rdr.GetString(1)
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
|
|
+ result.Add(new FortuneUtf8
|
|
|
|
+ (
|
|
|
|
+ id:rdr.GetInt32(0),
|
|
|
|
+ message: rdr.GetFieldValue<byte[]>(1)
|
|
|
|
+ ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." ));
|
|
|
|
|
|
+ result.Add(new FortuneUtf8(id: 0, AdditionalFortune));
|
|
result.Sort();
|
|
result.Sort();
|
|
|
|
|
|
return result;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private readonly byte[] AdditionalFortune = "Additional fortune added at request time."u8.ToArray();
|
|
|
|
+
|
|
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);
|
|
@@ -224,18 +230,16 @@ namespace PlatformBenchmarks
|
|
}
|
|
}
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
- private async Task<World> ReadSingleRow(NpgsqlCommand cmd)
|
|
|
|
|
|
+ private static async Task<World> ReadSingleRow(NpgsqlCommand cmd)
|
|
{
|
|
{
|
|
- using (var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow))
|
|
|
|
- {
|
|
|
|
- await rdr.ReadAsync();
|
|
|
|
|
|
+ using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow);
|
|
|
|
+ await rdr.ReadAsync();
|
|
|
|
|
|
- return new World
|
|
|
|
- {
|
|
|
|
- Id = rdr.GetInt32(0),
|
|
|
|
- RandomNumber = rdr.GetInt32(1)
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
|
|
+ return new World
|
|
|
|
+ {
|
|
|
|
+ Id = rdr.GetInt32(0),
|
|
|
|
+ RandomNumber = rdr.GetInt32(1)
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
|
|
private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
|