New tagBag scheme tested
All checks were successful
Unit tests / test (push) Successful in 1m37s

This commit is contained in:
ngfrolov 2024-12-25 13:27:14 +05:00
parent 4fa5423265
commit 58aa41c1b9
Signed by: ng.frolov
GPG Key ID: E99907A0357B29A7
6 changed files with 410 additions and 2 deletions

View File

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity; namespace DD.Persistence.Database.Entity;
@ -35,8 +36,21 @@ public class TagSetValue: ITimestamped
public float RotorTorque {get;set;} public float RotorTorque {get;set;}
public float RotorSpeed {get;set;} public float RotorSpeed {get;set;}
public float Flow {get;set;} public float Flow {get;set;}
public float Mse {get;set;} public int Mse {get;set;}
public int Mode {get;set;}
public float Pump0Flow {get;set;} public float Pump0Flow {get;set;}
public float Pump1Flow {get;set;} public float Pump1Flow {get;set;}
public float Pump2Flow { 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; }
}

View File

@ -13,6 +13,7 @@ public class PersistenceDbContext : DbContext
public DbSet<TagValue> TagValues => Set<TagValue>(); public DbSet<TagValue> TagValues => Set<TagValue>();
public DbSet<TagSetValue> TagSetValues => Set<TagSetValue>(); public DbSet<TagSetValue> TagSetValues => Set<TagSetValue>();
public DbSet<TagBag> TagBag => Set<TagBag>();
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
@ -41,5 +42,9 @@ public class PersistenceDbContext : DbContext
modelBuilder.Entity<ChangeLog>() modelBuilder.Entity<ChangeLog>()
.Property(e => e.Value) .Property(e => e.Value)
.HasJsonConversion(); .HasJsonConversion();
modelBuilder.Entity<TagBag>()
.Property(e => e.Values)
.HasJsonConversion();
} }
} }

View File

@ -5,8 +5,17 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<StartupObject>DD.Persistence.TestTelemetryStress.Program_v2</StartupObject>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Program_v1.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Program_v1.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" /> <ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -9,7 +9,7 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
namespace DD.Persistence.TestTelemetryStress; namespace DD.Persistence.TestTelemetryStress;
internal class Program internal class Program_v1
{ {
record TestDataItem(TagValue[] TagValues, TagSetValue[] TagSetValue); record TestDataItem(TagValue[] TagValues, TagSetValue[] TagSetValue);
enum Table { TagValue, TagSetValue } enum Table { TagValue, TagSetValue }

View 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;
}
}

View 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