persistence/DD.Persistence.TestTelemetryStress/Program.cs
2024-12-24 16:12:46 +05:00

268 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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