Compare commits
2 Commits
master
...
test/Telem
Author | SHA1 | Date | |
---|---|---|---|
|
58aa41c1b9 | ||
|
4fa5423265 |
56
DD.Persistence.Database/Entity/TagValue.cs
Normal file
56
DD.Persistence.Database/Entity/TagValue.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
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 int Mse {get;set;}
|
||||
public int Mode {get;set;}
|
||||
public float Pump0Flow {get;set;}
|
||||
public float Pump1Flow {get;set;}
|
||||
public float Pump2Flow { get; set; }
|
||||
}
|
||||
|
||||
[PrimaryKey(nameof(BagId), nameof(Timestamp))]
|
||||
public class TagBag : ITimestamped
|
||||
{
|
||||
[Comment("Временная отметка"), Key]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
public Guid BagId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public object[] Values { get; set; }
|
||||
}
|
@ -11,6 +11,10 @@ public class PersistenceDbContext : DbContext
|
||||
{
|
||||
public DbSet<DataSaub> DataSaub => Set<DataSaub>();
|
||||
|
||||
public DbSet<TagValue> TagValues => Set<TagValue>();
|
||||
public DbSet<TagSetValue> TagSetValues => Set<TagSetValue>();
|
||||
public DbSet<TagBag> TagBag => Set<TagBag>();
|
||||
|
||||
public DbSet<Setpoint> Setpoint => Set<Setpoint>();
|
||||
|
||||
public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>();
|
||||
@ -38,5 +42,9 @@ public class PersistenceDbContext : DbContext
|
||||
modelBuilder.Entity<ChangeLog>()
|
||||
.Property(e => e.Value)
|
||||
.HasJsonConversion();
|
||||
|
||||
modelBuilder.Entity<TagBag>()
|
||||
.Property(e => e.Values)
|
||||
.HasJsonConversion();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<StartupObject>DD.Persistence.TestTelemetryStress.Program_v2</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Program_v1.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Program_v1.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
267
DD.Persistence.TestTelemetryStress/Program_v1.cs
Normal file
267
DD.Persistence.TestTelemetryStress/Program_v1.cs
Normal 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_v1
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
354
DD.Persistence.TestTelemetryStress/Program_v2.cs
Normal file
354
DD.Persistence.TestTelemetryStress/Program_v2.cs
Normal file
@ -0,0 +1,354 @@
|
||||
using DD.Persistence.Database.Entity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace DD.Persistence.TestTelemetryStress;
|
||||
|
||||
internal class Program_v2
|
||||
{
|
||||
record TestDataItem(TagBag[] TagBags, TagSetValue[] TagSetValue);
|
||||
enum Table { TagBag, 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 значений с одной отметкой времени.
|
||||
* - TagBag - строка таблицы БД - 1 значение с отметкой времени и id параметра, для записи одной телеметрии используется 14 строк.
|
||||
* Данные генерируются одинаковые и за все бурение - 30 дней *24 часа * 60 минут * 60 сек
|
||||
*
|
||||
* Вставка производится порциями по 172_800 записей (2 дня)
|
||||
* Замеряется время каждой вставки для построения графика. Таблицы вставки чередуются.
|
||||
*
|
||||
* После вставки контекст уничтожается и делается пауза
|
||||
*
|
||||
* Создается новые контексты для тестирования каждой выборки по каждой таблице по отдельности.
|
||||
* Параметры выборок для тестирования:
|
||||
* - 10 раз за произвольные 10 минут, интервалы распределены по всему диапазону дат
|
||||
* - 10 раз за произвольные 60 минут, интервалы распределены по всему диапазону дат
|
||||
* - 10 раз за произвольные 24*60 минут, интервалы распределены по всему диапазону дат
|
||||
*/
|
||||
var begin = DateTimeOffset.Now;
|
||||
Console.WriteLine($"Started at {begin}");
|
||||
var insertLogs = FillDB();
|
||||
Pause();
|
||||
var selectLogs = TestSelect();
|
||||
var logs = insertLogs.Union(selectLogs);
|
||||
|
||||
var end = DateTimeOffset.Now;
|
||||
Console.WriteLine($"complete at {end}, estimate {end - begin}");
|
||||
|
||||
AnalyzeAndExportLogs(logs);
|
||||
|
||||
/* Анализ логов:
|
||||
* - не замечено замедление вставки при увеличении размера таблиц 2592000 записей
|
||||
* - размер таблиц в БД TagBag - 778Мб TagSetValue - 285Мб. В 2,73 раз больше
|
||||
* - время вставки всех чанков TagBag - 2м11с, TagSetValue - 3м40с
|
||||
* - время выборки из TagBag 21.69c (256180 записей) на 65% медленнее времени выборки из TagSetValue 13,11с (256180 записей)
|
||||
* Вывод: приемлемо
|
||||
*/
|
||||
}
|
||||
|
||||
private static void AnalyzeAndExportLogs(IEnumerable<Log> logs)
|
||||
{
|
||||
var orderedLogs = logs
|
||||
.OrderBy(l => l.Action)
|
||||
.ThenBy(l => l.Table)
|
||||
.ThenBy(l => l.Timestamp);
|
||||
|
||||
var groups = orderedLogs
|
||||
.GroupBy(log => new { log.Action , log.Table })
|
||||
.OrderBy(group => group.Key.Action)
|
||||
.ThenBy(group => group.Key.Table);
|
||||
|
||||
using var analysisStream = File.CreateText($"Analysis_{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.csv");
|
||||
|
||||
analysisStream.WriteLine("{Table}, {Action}, Sum(Time), Sum(Count), Max(TableLength)");
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var max = group.Max(i => i.tableLength);
|
||||
var count = group.Sum(i => i.count);
|
||||
var timeMs = group.Sum(i => i.TimeSpan.TotalMilliseconds);
|
||||
var time = TimeSpan.FromMilliseconds(timeMs);
|
||||
var line = $"{group.Key.Table}, {group.Key.Action}, {time}, {count}, {max}";
|
||||
analysisStream.WriteLine(line);
|
||||
}
|
||||
|
||||
analysisStream.WriteLine(string.Empty);
|
||||
analysisStream.WriteLine("{Table}, {Action}, Avg(Time), Avg(Count), Max(TableLength)");
|
||||
|
||||
foreach (var group in groups.Where(g => g.Key.Action == Action.select))
|
||||
{
|
||||
var subGroups = group.GroupBy(i => Math.Round( 0.01d * i.count));
|
||||
|
||||
foreach (var subGroup in subGroups)
|
||||
{
|
||||
var max = subGroup.Max(i => i.tableLength);
|
||||
var count = subGroup.Average(i => i.count);
|
||||
var timeMs = subGroup.Average(i => i.TimeSpan.TotalMilliseconds);
|
||||
var time = TimeSpan.FromMilliseconds(timeMs);
|
||||
var line = $"{group.Key.Table}, {group.Key.Action}, {time}, {count}, {max}";
|
||||
analysisStream.WriteLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
analysisStream.WriteLine(string.Empty);
|
||||
analysisStream.WriteLine("Table, Action, Time, Count, TableLength");
|
||||
|
||||
foreach (var log in orderedLogs)
|
||||
{
|
||||
analysisStream.WriteLine($"{log.Table}, {log.Action}, {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 logStopwatch = Stopwatch.StartNew();
|
||||
var data = GenerateData(count, begin, increment);
|
||||
Console.WriteLine($"Data[{data.Length}] generated {logStopwatch.Elapsed}");
|
||||
|
||||
logStopwatch.Restart();
|
||||
using var db = GetDb();
|
||||
db.Database.EnsureDeleted();
|
||||
db.Database.EnsureCreated();
|
||||
Console.WriteLine($"Database recreated {logStopwatch.Elapsed}");
|
||||
|
||||
|
||||
var tagBagSet = db.Set<TagBag>();
|
||||
var tagSetValueSet = db.Set<TagSetValue>();
|
||||
|
||||
var tagBagInsertedCount = 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, tagBagInsertedCount, inserted));
|
||||
tagBagInsertedCount += inserted;
|
||||
db.ChangeTracker.Clear();
|
||||
|
||||
Console.WriteLine($"chunk {i} of {data.Length} TagSetValue[{chunk.TagSetValue.Length}] added {stopwatch.Elapsed}");
|
||||
stopwatch.Restart();
|
||||
|
||||
tagBagSet.AddRange(chunk.TagBags);
|
||||
inserted = db.SaveChanges();
|
||||
logs.Add(new Log(Table.TagBag, Action.insert, DateTimeOffset.UtcNow, stopwatch.Elapsed, tagSetValueInsertedCount, inserted));
|
||||
tagSetValueInsertedCount += inserted;
|
||||
db.ChangeTracker.Clear();
|
||||
|
||||
Console.WriteLine($"chunk {i} of {data.Length} TagBags[{chunk.TagBags.Length}] added {stopwatch.Elapsed}");
|
||||
}
|
||||
|
||||
return logs.ToArray();
|
||||
}
|
||||
|
||||
private static void Pause()
|
||||
{
|
||||
GC.Collect();
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
private static Log[] TestSelect()
|
||||
{
|
||||
var testDateRanges = GetTestDateRanges();
|
||||
var logs1 = TestSelect<TagSetValue>(Table.TagSetValue, testDateRanges);
|
||||
var logs2 = TestSelect<TagBag>(Table.TagBag, 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();
|
||||
var count = selected.Length;
|
||||
var maxTime = selected.Max(e => e.Timestamp);
|
||||
logs.Add(new Log(table, Action.select, DateTimeOffset.UtcNow, stopwatch.Elapsed, totalCount, count));
|
||||
}
|
||||
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<TagBag>();
|
||||
var min2 = dbSet2.Min(e => e.Timestamp);
|
||||
var max2 = dbSet2.Max(e => e.Timestamp);
|
||||
|
||||
var min = min1 < min2 ? min1 : min2;
|
||||
var max = max1 > max2 ? max1 : max2;
|
||||
|
||||
var list1 = CalculateRanges(min, max, TimeSpan.FromMinutes(60), 10);
|
||||
var list2 = CalculateRanges(min, max, TimeSpan.FromMinutes(12 * 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 + range;
|
||||
result[i] = (b, e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DbContext GetDb()
|
||||
{
|
||||
var factory = new Database.Postgres.DesignTimeDbContextFactory();
|
||||
var context = factory.CreateDbContext(Array.Empty<string>());
|
||||
return context;
|
||||
}
|
||||
|
||||
private static TestDataItem[] GenerateData(int count, DateTimeOffset begin, TimeSpan increment)
|
||||
{
|
||||
var chunkLimit = 172_800;
|
||||
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<TagBag> tagBags = [];
|
||||
List<TagSetValue> tagSetValue = [];
|
||||
for (int i = 0; i < chunkLimit; i++)
|
||||
{
|
||||
var tagSet = GenerateTagSetValue(begin);
|
||||
tagSetValue.Add(tagSet);
|
||||
|
||||
var tagBag = MakeTagBag(tagSet);
|
||||
tagBags.Add(tagBag);
|
||||
begin += increment;
|
||||
}
|
||||
|
||||
return new(tagBags.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 = random.Next(),
|
||||
Mode = random.Next(),
|
||||
Pump0Flow = 100f * random.NextSingle(),
|
||||
Pump1Flow = 100f * random.NextSingle(),
|
||||
Pump2Flow = 100f * random.NextSingle(),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Guid BagId = Guid.NewGuid();
|
||||
private static TagBag MakeTagBag(TagSetValue tagSet)
|
||||
{
|
||||
TagBag data = new()
|
||||
{
|
||||
BagId = BagId,
|
||||
Timestamp = tagSet.Timestamp,
|
||||
Values =
|
||||
[
|
||||
tagSet.WellDepth,
|
||||
tagSet.BitDepth,
|
||||
tagSet.BlockPosition,
|
||||
tagSet.BlockSpeed,
|
||||
tagSet.Pressure,
|
||||
tagSet.AxialLoad,
|
||||
tagSet.HookWeight,
|
||||
tagSet.RotorTorque,
|
||||
tagSet.RotorSpeed,
|
||||
tagSet.Flow,
|
||||
tagSet.Mse,
|
||||
tagSet.Mode,
|
||||
tagSet.Pump0Flow,
|
||||
tagSet.Pump1Flow,
|
||||
tagSet.Pump2Flow,
|
||||
],
|
||||
//new Dictionary<string, object>()
|
||||
//{
|
||||
// {"WellDepth", tagSet.WellDepth },
|
||||
// {"BitDepth", tagSet.BitDepth},
|
||||
// {"BlockPosition", tagSet.BlockPosition},
|
||||
// {"BlockSpeed", tagSet.BlockSpeed},
|
||||
// {"Pressure", tagSet.Pressure},
|
||||
// {"AxialLoad", tagSet.AxialLoad},
|
||||
// {"HookWeight", tagSet.HookWeight},
|
||||
// {"RotorTorque", tagSet.RotorTorque},
|
||||
// {"RotorSpeed", tagSet.RotorSpeed},
|
||||
// {"Flow", tagSet.Flow},
|
||||
// {"Mse", tagSet.Mse},
|
||||
// {"Pump0Flow", tagSet.Pump0Flow},
|
||||
// {"Pump1Flow", tagSet.Pump1Flow},
|
||||
// {"Pump2Flow", tagSet.Pump2Flow},
|
||||
//}
|
||||
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
26
DD.Persistence.TestTelemetryStress/Results.md
Normal file
26
DD.Persistence.TestTelemetryStress/Results.md
Normal file
@ -0,0 +1,26 @@
|
||||
## Dictionary<string, object>
|
||||
|
||||
TagBag insert 00:02:31,55 2592000 2419200
|
||||
TagSetValue insert 00:04:14,89 2592000 2419200
|
||||
TagBag select 00:00:16,91 905997 2592000
|
||||
TagSetValue select 00:00:02,42 905997 2592000
|
||||
|
||||
sizeof(TagBag) = 1200M
|
||||
sizeof(TagSetValue) = 285M
|
||||
Tot = 7m
|
||||
|
||||
## object[]
|
||||
|
||||
{Table} {Action} Sum(Time) Sum(Count) Max(TableLength)
|
||||
TagBag insert 00:02:13,39 2592000 2419200
|
||||
TagSetValue insert 00:03:57,70 2592000 2419200
|
||||
TagBag select 00:00:12,60 1331997 2592000
|
||||
TagSetValue select 00:00:02,88 1331997 2592000
|
||||
|
||||
sizeof(TagBag) = 805M
|
||||
sizeof(TagSetValue) = 305M
|
||||
|
||||
## multi rows
|
||||
размер таблиц в БД TagValue - 1200Мб TagSetValue - 119Мб. В 10 раз больше
|
||||
время вставки чанка TagValue в 7.3 раза больше времени вставки чанка TagSetValue
|
||||
время выборки из TagValue в 16,4 раза больше времени выборки из TagSetValue
|
@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Client", "DD
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.App", "DD.Persistence.App\DD.Persistence.App.csproj", "{063238BF-E982-43FA-9DDB-7D7D279086D8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DD.Persistence.TestTelemetryStress", "DD.Persistence.TestTelemetryStress\DD.Persistence.TestTelemetryStress.csproj", "{A75B3712-1E1D-4C5B-B97C-72C80E7D6BDE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
Loading…
Reference in New Issue
Block a user