Многие, кто использует EF Core в работе в качестве ORM (Object-Relational Mapping) встречаются с множеством прелестей этого инструмента:
-
Достаточная гибкость
-
Скорость разработки
-
Возможность поддерживать Legacy
-
Написание хранимок и прочее…
И в одном из проектов я наткнулся на формат его использования, который приводил к бОльшим нагрузкам и поеданию памяти.
Закрывая очередную задачу по бизнес-логике я заметил в выводе терминала очень много странных записей, которых там быть не должно.
Покопавшись дальше я увидел сообщение о том, что *100 записей были добавлены в ChangeTracker.
Странно, но я же просто открыл страницу и нафига мне тут отслеживать изменения?
После я сразу же полез в код и обнаружил, что в части проекта напрочь забыто про такую вещь как Tracking behavior
И тут ради интереса мне захотелось посмотреть влияние трэкинга на быстродействие запросов не формате «Так однозначно быстрее», а с пруфами и метриками. Ссылки на статьи прикреплю ниже*
Перейдем к результатам
Изначально я собрал проект на .NET Core и подключил пакеты и создал тестовую базу данн/ых на PostrgeSQL (ссылку на github прицеплю ниже).
код бенчмарка
public class Benchmark { [Benchmark] public async Task<int> WithTracking() { using var dbContext = new AppDbContext(); var products = await dbContext.Product.AsTracking().ToListAsync(); return products.Count; } [Benchmark] public async Task<int> WithoutTracking() { using var dbContext = new AppDbContext(); var products = await dbContext.Product.AsNoTracking().ToListAsync(); return products.Count; } [Benchmark] public async Task<int> WithTrackingAndOrder() { using var dbContext = new AppDbContext(); var products = await dbContext.Product.AsTracking().OrderBy(x => x.Name).ToListAsync(); return products.Count; } [Benchmark] public async Task<int> WithoutTrackingAndOrder() { using var dbContext = new AppDbContext(); var products = await dbContext.Product.AsNoTracking().OrderBy(x => x.Name).ToListAsync(); return products.Count; } }
Итак, 1-й набор данных на 100 записей:
|
Method |
Mean |
Error |
StdDev |
|---|---|---|---|
|
521.6 us |
10.28 us |
11.43 us |
|
|
395.2 us |
6.43 us |
5.70 us |
|
|
683.7 us |
12.76 us |
13.11 us |
|
|
548.2 us |
10.76 us |
20.72 us |
Для такого объема данных разница уже существенна, но для пользователя не особо заметна. (Кстати, именно поэтому на стартах проектов чаще всего забивают на тонкости оптимизации)
Далее — 1.000 записей в таблице
|
Method |
Mean |
Error |
StdDev |
|---|---|---|---|
|
WithTracking |
2,193.3 us |
38.75 us |
36.25 us |
|
WithoutTracking |
833.0 us |
12.46 us |
10.41 us |
|
WithTrackingAndOrder |
3,707.3 us |
55.82 us |
46.61 us |
|
WithoutTrackingAndOrder |
2,073.3 us |
26.42 us |
24.71 us |
Здесь мы видим уже существенный (быстрее более чем в 2 раза) прув от трэкинга.
Что дальше?
10.000 записей
|
Method |
Mean |
Error |
StdDev |
Median |
|---|---|---|---|---|
|
WithTracking |
30.820 ms |
0.5955 ms |
1.5895 ms |
30.323 ms |
|
WithoutTracking |
7.939 ms |
0.1398 ms |
0.1308 ms |
7.942 ms |
|
WithTrackingAndOrder |
42.891 ms |
0.8493 ms |
0.7944 ms |
42.776 ms |
|
WithoutTrackingAndOrder |
22.248 ms |
0.4247 ms |
0.3765 ms |
22.346 ms |
В простом случае (без учета OrderBy) мы видим, что опция AsNoTracking работает в 4 раза быстрее. И в то же время AsNoTracking и OrderBy дают непропорциональные результаты результаты. Я думаю, что это связано с оптимизациями производительности, внедренными в EF Core и работой с коллекциями.
100.000 записей
|
Method |
Mean |
Error |
StdDev |
|---|---|---|---|
|
WithTracking |
312.45 ms |
5.936 ms |
6.351 ms |
|
WithoutTracking |
81.35 ms |
1.508 ms |
1.411 ms |
|
WithTrackingAndOrder |
453.65 ms |
8.072 ms |
7.551 ms |
|
WithoutTrackingAndOrder |
208.51 ms |
4.040 ms |
4.149 ms |
Как видите, результата очень схож и коррелирует с выборкой в 10.000 записей.
Ради интереса, 1млн записей
|
Method |
Mean |
Error |
StdDev |
|---|---|---|---|
|
WithTracking |
3,511.5 ms |
70.10 ms |
122.77 ms |
|
WithoutTracking |
851.1 ms |
13.34 ms |
12.48 ms |
|
WithTrackingAndOrder |
4,083.4 ms |
49.27 ms |
43.68 ms |
|
WithoutTrackingAndOrder |
1,634.6 ms |
31.82 ms |
40.25 ms |
Тенденция схожа и никаких коллизий тут не отмечено.
Выводы, кэп?
Мы обсудили и сравнили результаты с одним из параметров — AsNoTracking.
Как видно, этот параметр значительно влияет на эффективность запросов и обработку данных, и он работает в 4 раза быстрее на наборе данных из более чем 10 000 объектов.
Как жить?
1) Вы можете установить это значение по-умолчанию как AsNoTracking, и, при необходимости, когда вам потребуется отслеживать изменения в объектах, вы сможете получить объекты с помощью метода AsTracking
services.AddDbContext<DatabaseContext>(options => { options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); ... });
2) Использовать маппинг в DTO-модели при получении данных и избегать загрузку всех связанных сущностей и ненужных данных
public List<ProductDTO> GetProducts() { return _context.Products .Select(x => _mapper.Map<ProductDTO>(x)) .ToArray(); }
3) Избегать загрузки больших коллекций в память, которые планируется править.
Берите на заметку и быстрых решений!
Ссылки:
Контакты:
ссылка на оригинал статьи https://habr.com/ru/articles/772604/
Добавить комментарий