Stress test with results
All checks were successful
Unit tests / test (push) Successful in 1m6s

This commit is contained in:
ngfrolov 2024-12-24 16:12:46 +05:00
parent 9a85e21b22
commit 4fa5423265
Signed by: ng.frolov
GPG Key ID: E99907A0357B29A7
5 changed files with 332 additions and 0 deletions

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace DD.Persistence.Database.Entity;
public interface ITimestamped
{
DateTimeOffset Timestamp { get; set; }
}
[PrimaryKey(nameof(ParameterId), nameof(Timestamp))]
public class TagValue: ITimestamped
{
[Comment("Id параметра")]
public int ParameterId { get; set; }
[Comment("Временная отметка")]
public DateTimeOffset Timestamp { get; set; }
[Comment("Значение параметра")]
public float Value { get; set; }
}
public class TagSetValue: ITimestamped
{
[Comment("Временная отметка"), Key]
public DateTimeOffset Timestamp { get; set; }
public float WellDepth {get;set;}
public float BitDepth {get;set;}
public float BlockPosition {get;set;}
public float BlockSpeed {get;set;}
public float Pressure {get;set;}
public float AxialLoad {get;set;}
public float HookWeight {get;set;}
public float RotorTorque {get;set;}
public float RotorSpeed {get;set;}
public float Flow {get;set;}
public float Mse {get;set;}
public float Pump0Flow {get;set;}
public float Pump1Flow {get;set;}
public float Pump2Flow { get; set; }
}

View File

@ -11,6 +11,9 @@ public class PersistenceDbContext : DbContext
{ {
public DbSet<DataSaub> DataSaub => Set<DataSaub>(); public DbSet<DataSaub> DataSaub => Set<DataSaub>();
public DbSet<TagValue> TagValues => Set<TagValue>();
public DbSet<TagSetValue> TagSetValues => Set<TagSetValue>();
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>(); public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>();

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,267 @@

using DD.Persistence.Database.Entity;
using Microsoft.EntityFrameworkCore;
using Npgsql.Internal;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Intrinsics.X86;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace DD.Persistence.TestTelemetryStress;
internal class Program
{
record TestDataItem(TagValue[] TagValues, TagSetValue[] TagSetValue);
enum Table { TagValue, TagSetValue }
enum Action { insert, select }
record Log(
Table Table,
Action Action,
DateTimeOffset Timestamp,
TimeSpan TimeSpan,
int tableLength,
int count);
static Random random = new Random((int)(DateTimeOffset.UtcNow.Ticks % 0x7FFFFFFF));
static void Main(string[] args)
{
/* Цель теста сравнить время отклика БД с телеметрией за все бурение
* по 2м схемам хранения:
* - TagSetValue - строка таблицы БД - 14 значений с одной отметкой времени.
* - TagValue - строка таблицы БД - 1 значение с отметкой времени и id параметра, для записи одной телеметрии используется 14 строк.
* Данные генерируются одинаковые и за все бурение - 30 дней *24 часа * 60 минут * 60 сек
*
* Вставка производится порциями по 56_000 записей для TagValue и по 4000 (56к/14) для TagSetValue.
* Замеряется время каждой вставки для построения графика. Таблицы вставки чередуются.
*
* После вставки контекст уничтожается и делается пауза
*
* Создается новые контексты для тестирования каждой выборки по каждой таблице по отдельности.
* Параметры выборок для тестирования:
* - 10 раз за произвольные 10 минут, интервалы распределены по всему диапазону дат
* - 10 раз за произвольные 60 минут, интервалы распределены по всему диапазону дат
* - 10 раз за произвольные 24*60 минут, интервалы распределены по всему диапазону дат
*/
var insertLogs = FillDB();
Pause();
var selectLogs = TestSelect();
var logs = insertLogs.Union(selectLogs);
ExportLogs(logs);
/* Анализ логов:
* - не замечено замедление вставки при увеличении размера таблиц
* - размер таблиц в БД TagValue - 1200Мб TagSetValue - 119Мб. В 10 раз больше
* - время вставки чанка TagValue в 7.3 раза больше времени вставки чанка TagSetValue
* - время выборки из TagValue в 16,4 раза больше времени выборки из TagSetValue
*/
}
private static void ExportLogs(IEnumerable<Log> logs)
{
using var stream = File.CreateText("log.csv");
foreach (Log log in logs)
{
stream.WriteLine($"{log.Table}, {log.Action}, {log.Timestamp}, {log.TimeSpan}, {log.count}, {log.tableLength}");
}
}
private static Log[] FillDB()
{
var begin = DateTimeOffset.UtcNow;
var increment = TimeSpan.FromSeconds(1);
var count = 30 * 24 * 60 * 60;
var data = GenerateData(count, begin, increment);
using var db = GetDb();
var tagValueSet = db.Set<TagValue>();
var tagSetValueSet = db.Set<TagSetValue>();
var tagValueInsertedCount = 0;
var tagSetValueInsertedCount = 0;
var logs = new List<Log>(data.Length * 2);
for (var i = 0; i < data.Length; i++)
{
var chunk = data[i];
var inserted = 0;
var stopwatch = Stopwatch.StartNew();
tagSetValueSet.AddRange(chunk.TagSetValue);
inserted = db.SaveChanges();
logs.Add(new Log(Table.TagSetValue, Action.insert, DateTimeOffset.UtcNow, stopwatch.Elapsed, tagSetValueInsertedCount, count));
tagValueInsertedCount += inserted;
db.ChangeTracker.Clear();
stopwatch.Restart();
tagValueSet.AddRange(chunk.TagValues);
inserted = db.SaveChanges();
logs.Add(new Log(Table.TagValue, Action.insert, DateTimeOffset.UtcNow, stopwatch.Elapsed, tagValueInsertedCount, count));
tagSetValueInsertedCount += inserted;
db.ChangeTracker.Clear();
}
return logs.ToArray();
}
private static void Pause()
{
Thread.Sleep(1000);
}
private static Log[] TestSelect()
{
var testDateRanges = GetTestDateRanges();
var logs1 = TestSelect<TagSetValue>(Table.TagSetValue, testDateRanges);
var logs2 = TestSelect<TagValue>(Table.TagValue, testDateRanges);
return [.. logs1, .. logs2];
}
private static Log[] TestSelect<T>(Table table, (DateTimeOffset begin, DateTimeOffset end)[] ranges)
where T : class, ITimestamped
{
using var db = GetDb();
var dbSet = db.Set<T>();
var totalCount = dbSet.Count();
var logs = new List<Log>(ranges.Length);
foreach (var range in ranges)
{
var stopwatch = Stopwatch.StartNew();
var selected = dbSet
.Where(e => e.Timestamp > range.begin)
.Where(e => e.Timestamp < range.end)
.ToArray();
logs.Add(new(table, Action.select, DateTimeOffset.UtcNow, stopwatch.Elapsed, totalCount, selected.Length));
}
return logs.ToArray();
}
private static (DateTimeOffset begin, DateTimeOffset end)[] GetTestDateRanges()
{
using var db = GetDb();
var dbSet1 = db.Set<TagSetValue>();
var min1 = dbSet1.Min(e => e.Timestamp);
var max1 = dbSet1.Max(e => e.Timestamp);
var dbSet2 = db.Set<TagValue>();
var min2 = dbSet2.Min(e => e.Timestamp);
var max2 = dbSet2.Max(e => e.Timestamp);
var min = min1 < min2 ? min1 : min2;
var max = max1 < max2 ? max2 : max1;
var list1 = CalculateRanges(min, max, TimeSpan.FromMinutes(10), 10);
var list2 = CalculateRanges(min, max, TimeSpan.FromMinutes(60), 10);
var list3 = CalculateRanges(min, max, TimeSpan.FromMinutes(24 * 60), 10);
return [..list1, ..list2, ..list3];
}
private static (DateTimeOffset begin, DateTimeOffset end)[] CalculateRanges(DateTimeOffset min, DateTimeOffset max, TimeSpan range, int count)
{
if (max - min < range)
throw new ArgumentException("max - min < range", nameof(range));
var result = new (DateTimeOffset begin, DateTimeOffset end)[count];
var max1 = max - range;
var delta = max1 - min;
var step = delta / count;
for (var i = 0; i < count; i++)
{
var b = min + i * step;
var e = b + step;
result[i] = (b, e);
}
return result;
}
private static DbContext GetDb()
{
var factory = new Database.Postgres.DesignTimeDbContextFactory();
var context = factory.CreateDbContext(Array.Empty<string>());
context.Database.EnsureCreated();
return context;
}
private static TestDataItem[] GenerateData(int count, DateTimeOffset begin, TimeSpan increment)
{
var chunkLimit = 4000;
var chunks = new List<TestDataItem>((count + chunkLimit) / chunkLimit);
for (int i = 0; i < count; i += chunkLimit)
{
var item = GenerateDataChunk(begin, increment, chunkLimit);
chunks.Add(item);
begin += increment * chunkLimit;
}
return chunks.ToArray();
}
private static TestDataItem GenerateDataChunk(DateTimeOffset begin, TimeSpan increment, int chunkLimit)
{
List<TagValue> tagValues = [];
List<TagSetValue> tagSetValue = [];
for (int i = 0; i < chunkLimit; i++)
{
var tagSet = GenerateTagSetValue(begin);
tagSetValue.Add(tagSet);
var items = MakeTagValues(tagSet);
tagValues.AddRange(items);
begin += increment;
}
return new(tagValues.ToArray(), tagSetValue.ToArray());
}
private static TagSetValue GenerateTagSetValue(DateTimeOffset begin)
{
var result = new TagSetValue
{
Timestamp = begin,
WellDepth = 100f * random.NextSingle(),
BitDepth = 100f * random.NextSingle(),
BlockPosition = 100f * random.NextSingle(),
BlockSpeed = 100f * random.NextSingle(),
Pressure = 100f * random.NextSingle(),
AxialLoad = 100f * random.NextSingle(),
HookWeight = 100f * random.NextSingle(),
RotorTorque = 100f * random.NextSingle(),
RotorSpeed = 100f * random.NextSingle(),
Flow = 100f * random.NextSingle(),
Mse = 100f * random.NextSingle(),
Pump0Flow = 100f * random.NextSingle(),
Pump1Flow = 100f * random.NextSingle(),
Pump2Flow = 100f * random.NextSingle(),
};
return result;
}
private static TagValue[] MakeTagValues(TagSetValue tagSet)
{
TagValue[] data =
[
new (){ Timestamp = tagSet.Timestamp, ParameterId = 1, Value = tagSet.WellDepth},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 2, Value = tagSet.BitDepth},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 3, Value = tagSet.BlockPosition},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 4, Value = tagSet.BlockSpeed},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 5, Value = tagSet.Pressure},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 6, Value = tagSet.AxialLoad},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 7, Value = tagSet.HookWeight},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 8, Value = tagSet.RotorTorque},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 9, Value = tagSet.RotorSpeed},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 10, Value = tagSet.Flow},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 11, Value = tagSet.Mse},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 12, Value = tagSet.Pump0Flow},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 13, Value = tagSet.Pump1Flow},
new (){ Timestamp = tagSet.Timestamp, ParameterId = 14, Value = tagSet.Pump2Flow},
];
return data;
}
}

View File

@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Client", "DD
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.App", "DD.Persistence.App\DD.Persistence.App.csproj", "{063238BF-E982-43FA-9DDB-7D7D279086D8}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.App", "DD.Persistence.App\DD.Persistence.App.csproj", "{063238BF-E982-43FA-9DDB-7D7D279086D8}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DD.Persistence.TestTelemetryStress", "DD.Persistence.TestTelemetryStress\DD.Persistence.TestTelemetryStress.csproj", "{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -57,6 +59,10 @@ Global
{063238BF-E982-43FA-9DDB-7D7D279086D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {063238BF-E982-43FA-9DDB-7D7D279086D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{063238BF-E982-43FA-9DDB-7D7D279086D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {063238BF-E982-43FA-9DDB-7D7D279086D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{063238BF-E982-43FA-9DDB-7D7D279086D8}.Release|Any CPU.Build.0 = Release|Any CPU {063238BF-E982-43FA-9DDB-7D7D279086D8}.Release|Any CPU.Build.0 = Release|Any CPU
{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE