Slide 1

Slide 1 text

03.11.2018 Магия Dapper + Oracle Орлов Юрий Разработчик C#

Slide 2

Slide 2 text

Обо мне  Разрабатываю серверные приложения на платформе .NET в CUSTIS  Изучаю балансировку нагрузки с использованием методов для решения NP-трудных задач, пишу статьи в научных изданиях, являюсь инженером-исследователем в ФИЦ ИУ РАН  Разрабатываю на .NET Core клиент-серверную игру 2

Slide 3

Slide 3 text

План  Пролог  История проблемы  Что нужно для достижения результата?  Решение  Dapper  Ускорение логики на C#  Фишки Oracle  Подведем итоги 3

Slide 4

Slide 4 text

UI SERVER Сторонняя Extract, Transform, Load (ETL) система ПРОЛОГ 4

Slide 5

Slide 5 text

История проблемы 5

Slide 6

Slide 6 text

С чего все началось? 6

Slide 7

Slide 7 text

Основные требования  Сложный алгоритм расчета показателей  Необходимо зачитывать, обрабатывать, и записывать десятки миллионов строк  Время расчета ограничено несколькими часами 7

Slide 8

Slide 8 text

Что нужно для достижения результата? 8

Slide 9

Slide 9 text

Как достичь результата?  Избавиться от замедляющих факторов при работе с данными  Максимальная экономия оперативной памяти  Оптимизация процесса расчета с использованием возможностей C# в .NET  Простое и наглядное использование кода 9

Slide 10

Slide 10 text

Стек технологий  Dapper  Логика расчета на .NET  Oracle DB 10

Slide 11

Slide 11 text

UI SERVER РЕШЕНИЕ Business Logic (.NET) Dapper Services Oracle Features 11

Slide 12

Slide 12 text

Dapper 12

Slide 13

Slide 13 text

Немного о Dapper  Создана 7 лет назад Сэмом Саффроном  «Микро ORM»  Тонкая обертка вокруг ADO.NET  По сути маппер, предназначенный для быстрой зачитки данных 13

Slide 14

Slide 14 text

Почему именно Dapper?  Плюсы  Позволяет быстро обрабатывать сколь угодно сложные запросы  Избегает проблемы большинства ORM (лишние JOIN, внезапные LEFT JOIN)  Позволяет работать с «плоскими» сущностями  Минусы  Это не совсем ORM, кроме маппинга, ничего в нем нет 14

Slide 15

Slide 15 text

Простой пример с dynamic public IList GetOrders() { var sql = "SELECT * FROM Order"; using (var connection = new OracleConnection ("DataSource=SomeDBConnection")) { var orders = connection.Query(sql).Select(o => new Order { Id = o.Id, Quantity = o.Quantity, ClientId = o.ClientId }).ToList(); } } 15

Slide 16

Slide 16 text

Простой пример c автомаппингом public IList GetOrders() { var sql = "SELECT * FROM Order"; using (var connection = new OracleConnection ("DataSource=SomeDBConnection")) { var orders = connection.Query(sql).ToList(); } } 16

Slide 17

Slide 17 text

Проблема стандартной реализации  Нужно постоянно оборачивать в using(…) { … }  Дублируем код для каждого репозитория  Для такого типа систем чаще всего требуется всего 1 соединение 17

Slide 18

Slide 18 text

Упрощаем жизнь базовым репозиторием private IDbConnection CreateConnection(string connectionString = null) => new OracleConnection(connectionString ?? _connectionStringsManager.GetConnectionString()); protected T InConnection(Func runnable) { using (var connection = CreateConnection()) { connection.Open(); return runnable(connection); } } 18

Slide 19

Slide 19 text

Теперь можно делать так public IList GetOrders() => InConnection(connection => connection. Query("SELECT * FROM Order")) .ToList(); 19

Slide 20

Slide 20 text

А что с параметрами? protected IEnumerable Query(string query, object parameters) => InConnection(_ => _.Query(query, parameters)); protected IEnumerable Query(string query, object parameters) => InConnection(_ => _.Query(query, parameters)); public IList GetOrdersByClientId(long id) => InConnection(connection => connection. Query( @"SELECT * FROM Order WHERE ClientId = :id”, , new {id})) .ToList(); 20

Slide 21

Slide 21 text

Аналогичный синтаксический сахар делаем для остальных методов Dapper  Execute  Query  QueryFirst  QueryFirstOrDefault  QuerySingle  QuerySingleOrDefault  QueryMultiple 21

Slide 22

Slide 22 text

Все ли теперь хорошо? Например, могут понадобиться такие конструкции: public Dictionary GetPairs() => InConnection(connection => connection. Query ( @"SELECT Id, ClientId FROM Order o JOIN Client c ON c.Id = o.ClientId”)) .Select(d => new { ClientId = d.ClientId, OrderId = d.Id }) .ToArray().GroupBy(d => d.ClientId) .ToDictionary(p => p.Key, p => p.ToArray()); 22

Slide 23

Slide 23 text

Воспользуемся паттерном IdOf public struct IdOf: IEquatable> { public IdOf(long id) => Id = id; public long Id { get; } public static implicit operator long(IdOf id) => id.Id; public static bool operator ==(IdOf left, long right) {return left.Id == right;} public static bool operator !=(IdOf left, long right) {return left.Id != right;} public override string ToString() => Id.ToString(); public override bool Equals(object obj) => !ReferenceEquals(null, obj) && obj is IdOf id && Equals(id); public override int GetHashCode() => Id.GetHashCode(); public bool Equals(IdOf other) => Id == other.Id; } 23

Slide 24

Slide 24 text

Немного о IEquatable  Поможет избежать боксинга при сравнении структур  Реализуется у всех примитивов-структур  Поддерживается стандартными Generic коллекциями (вызывается через IEqualityComparer.Equals(T, T))  Подробнее https://habr.com/post/315168/ 24

Slide 25

Slide 25 text

Также будет полезно public static class IdOfExtensions { public static IdOf AsIdOf(this long id) => new IdOf(id); public static IdOf AsIdOf(this int id) => new IdOf(id); } 25

Slide 26

Slide 26 text

Теперь можно делать так public Dictionary, IdOf[]> GetPairs() => InConnection(connection => connection. Query ( @"SELECT Id, ClientId FROM Order o JOIN Client c ON c.Id = o.ClientId”)) .Select(d => new { Client = d.ClientId.AsIdOf(), Order = d.Id.AsIdOf() }) .ToArray().GroupBy(d => d.Client) .ToDictionary(p => p.Key, p => p.ToArray()); 26

Slide 27

Slide 27 text

Иные преимущества Dapper  Можно подключать хэндлеры типов, что позволяет, например, маппить IdOf как параметры  Поддерживает асинхронные, буферизированные запросы  Позволяет использовать транзакционность InTransaction() => … 27

Slide 28

Slide 28 text

Ускорение логики на C# 28

Slide 29

Slide 29 text

А что же делать с логикой на С#?  Сколько раз сработает сборщик мусора при последовательной вычитке десятков миллионов строк?  Сколько будут «весить» все полученные сущности? 29

Slide 30

Slide 30 text

Корень проблемы - объект сущности  Каждый объект сущности будет жить в куче  Относительно долгий процесс создания и инициализации  Имеет оверхед (за счет ссылок на блок синхронизации и VMT)  8 бит для 32х битной архитектуры  16 бит для 64х битной архитектуры  Adam Sitnik - Value Types vs Reference Types 30

Slide 31

Slide 31 text

Структуры как сущности  Сами по себе не задействуют кучу, сокращая количество сборок мусора  Обеспечивают экономию памяти машины в процессе расчета  В огромном ряде случаев быстрее инициализируются данными (например, при вычитке и маппинге)  Удобно копировать данные 31

Slide 32

Slide 32 text

Минусы такого подхода  Нельзя переопределить конструктор по умолчанию  Предпочтительней использовать плоскую реализацию для каждой отдельной структуры  Нужно обеспечивать самостоятельный контроль боксинга  Пробрасывать в большом стеке вызовов методов придется по ссылке, либо избегать таких конструкций в коде  Нельзя выстраивать иерархии наследования 32

Slide 33

Slide 33 text

Стоит ли использовать структуры?  В общем случае – нет, так как нарушается множество принципов для работы с сущностями в объектно ориентированном стиле  Чревато массой неудобств  Не все ОРМ хорошо заточены для работы с ними  Структуры как сущности полезны только там, где это необходимо – для узкого круга задач  .NET Value Type (struct) as a DDD Value Object 33

Slide 34

Slide 34 text

Оптимальное использование  Зачитываем сущности в структуры  Структуры создаем плоскими  Все ID оборачиваем в паттерн IdOf  Не забываем реализовать IEquatable  Следим за боксингами, избегаем неправильных пробросов в параметрах. 34

Slide 35

Slide 35 text

Как еще можно ускорить расчет и обработку данных?  Использовать параллельную обработку данных по пачкам, например, используя Parallel LINQ  Иногда дает профит реализация приложения, где различные части расчета осуществляются последовательным запуском приложения с разными аргументами командной строки 35

Slide 36

Slide 36 text

Фишки Oracle 36

Slide 37

Slide 37 text

А что же с Oracle?  OracleBulkCopy  Merge 37

Slide 38

Slide 38 text

OracleBulkCopy  Содержится в Oracle.DataAccess  Позволяет крайне быстро массово копировать данные  Игнорирует индексы, требует их восстановления  Плохо дружит с триггерами и констрейнтами  Требует особого подхода при использовании материализованных View 38

Slide 39

Slide 39 text

Алгоритм использования  Создается DataTable, содержащий данные для копирования  Truncate Table  Сделать триггеры неактивными при необходимости  Вызвать BulkCopy (можно поиграться с настройками)  Восстановить индексы  Активировать триггеры при необходимости 39

Slide 40

Slide 40 text

На что стоит обратить внимание  Формирование ключей придется делать вручную, так как триггеры будут недоступны  Данные в DataTable лучше всего располагать в том же порядке, что и в таблице БД  Маппить Enum придется вручную  Удобнее всего реализовывать в виде адаптера к базовому репозиторию  Есть реализация BulkCopy для SqlServer (SqlBulkCopy) 40

Slide 41

Slide 41 text

Merge 41 Промежуточная таблица Конечная таблица Данные приложения BulkCopy Merge

Slide 42

Slide 42 text

Подведем итоги 42

Slide 43

Slide 43 text

Где это может быть полезно?  Нужно быстро обработать и записать большое количество данных  Есть необходимость обновлять данные с определенной периодичностью  Данные используются на протяжение длительного периода сторонними приложениями  Есть гарантия, что на период расчета пользователю данные будут не нужны 43

Slide 44

Slide 44 text

Резюме  Dapper обеспечивает быстрый и удобный доступ к данным Это не совсем ORM, но и не голый Adapter  При создании приложения с Dapper лучше всего использовать облегчающие жизнь конструкции  Использование структур позволяет достичь большых плюсов в производительности и расходах памяти, если их использовать с умом, не допуская лишних боксингов  Для быстрой записи данных используйте BulkCopy и Merge вместо update и insert 44

Slide 45

Slide 45 text

Благодарности коллегам  Серавкину Всеволоду  Щекочихиной Марии 45

Slide 46

Slide 46 text

Спасибо! Вопросы? 46 Орлов Юрий [email protected]