268 lines
11 KiB
C#
268 lines
11 KiB
C#
|
||
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;
|
||
}
|
||
|
||
}
|