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 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(); var tagSetValueSet = db.Set(); var tagValueInsertedCount = 0; var tagSetValueInsertedCount = 0; var logs = new List(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(Table.TagSetValue, testDateRanges); var logs2 = TestSelect(Table.TagValue, testDateRanges); return [.. logs1, .. logs2]; } private static Log[] TestSelect(Table table, (DateTimeOffset begin, DateTimeOffset end)[] ranges) where T : class, ITimestamped { using var db = GetDb(); var dbSet = db.Set(); var totalCount = dbSet.Count(); var logs = new List(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(); var min1 = dbSet1.Min(e => e.Timestamp); var max1 = dbSet1.Max(e => e.Timestamp); var dbSet2 = db.Set(); 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()); context.Database.EnsureCreated(); return context; } private static TestDataItem[] GenerateData(int count, DateTimeOffset begin, TimeSpan increment) { var chunkLimit = 4000; var chunks = new List((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 tagValues = []; List 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; } }