Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>>>оптимизировать sql нужно тогда, когда есть проблемы с перфомансом. 90% операций приложения это таки CRUD, а с ним любая ORM отлично справляется. G>>Ерунда. Нужно сразу писать оптимальный код. Преждевременная пессимизация приводит к проектам, которые и за полгода не отрефакторишь. HBM>преждевременная оптимизация не меньшее зло. код должен быть нормальным, но нет смысла гнаться за +1% перфоманса.
Это неверно.
Когда Дейкстра писал про преждевременную оптимизацию он имел ввиду мелкие хаки на уровне отдельных команд процессора, которые дают незначительный прирост быстродействия, но значительно ухудшают читаемость.
Частично это же касается выбора алгоримтма, типа лучше сначала запилить линейный поиск, чем ловить баги в двоичном поиске с интерполяцией.
Но когда мы говорим о высокоуровневой оптимизации — на уровне структур данных, взаимодействия компонентов, базы данных, то оптимизировать нужно с самого начала, иначе потом цена исправления растет экспоненциально.
Оптимизация SQL касается как раз такого вида оптимизации.
У меня есть прекрасный пример. В 2008 году, когда только появился linq, я делал сайт и один человек из моей команды занимался построением навигации.
Нужно было сделать нетривиальный запрос, который сделает выборку.
Это человек следуя подходу "потом оптимизируем" написал вместо сложного запроса пару foreach циклов. Мало того, что получил select n+1, так еще и код представления ориентировался на объекты модели, каждый из которых требовал поднятия по несколько КБ текста. В итоге навигация строилась полсекунды.
Я ему сказал, что надо переписать это дело в запрос и сделать проекцию, но он сказал, что это фигня и лучше мы применим кэш. И прикрутил кэш для объектов модели.
Потом мы на сайте прогрузили реальные данные клиента и получилось на построение навигации 8 секунд. Даже отлаживать стало неудобно, даже кеш не помогал, ибо разработчик запускал с нуля приложение чуть ли не каждую минуту.
В итоге надо было переделать запросы, сделать DTO, переделать код представления, переделать механизм кеширования. По сути переписать ВСЕ, что делал этот программист.
Вот такая цена неоптимальных запросов.
Еще хуже оказывается ситуация неоптимальной структуры БД, тогда нужно не просто запросы переписать, а еще и данные перелить и не дай бог приложение у заказчика работает.
Здравствуйте, Gattaka, Вы писали:
G>Коллеги, G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах?
"Высокопроизводительные хранимые процедуры" это оксюморон.
Типичное приложение — это CRUD. В простом приложении CRUD это 100% всей работы с базой, в сложном — не менее 60% по моим подсчетам.
Высокопроизводительное чтение данных строится на простом принципе — делаем максимально точный предикат (без всяких t.Field = @p or @p is null) для выборки и минимальную проекцию.
Это значит что тебе нужно будет или 100500 процедур для чтения данных, или клеить строки в SQL.
Естественно никто в здравом уме так не делает и использует запросы общего вида, с неоптимальными проекциями и предикатами, получая быстродействие заведомо хуже того, что могло бы быть.
Что касается изменения данных, то картина тоже не в пользу процедур. В случае изменения\добавления\удаления одной строки никакой разницы с запросами из приложения нет.
Зато если надо сразу несколько строк добавить\обновить\удалить, то ORMы просто формируют батч и отправляют на сервер, а для процедуры нужно данные сериализовать в какой-нибудь xml\json или на крайняк в табличный параметр. Потом распарсить параметр в процедуре и только после этого выполнить операции. Естественно подход с процедурами также заведомо неоптимален.
Реальная высокая производительность в процедурах достигается не в OLTP сценариях, а в OLAP, когда нужно гонять массивы данных, желательно без перекачки их в приложение.
G>Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы? Учитывая, что код на sql как правило более локаничный и лучше читается.
Код на SQL который более лаконичный и лучше читается не имеет никакого отношения к высокой производительности.
Здравствуйте, Gattaka, Вы писали: S>>Вы это к чему? Я не предлагаю заиенить SQL Server, вы спорите о чём-то не том. Для того, что делает SQL Server, альтернативы, в общем-то нет. Только другие RDBMS того же класса. S>>А вот для того, чему хватит коллекции в памяти, SQL Server будет оверкиллом. С этим лучше заранее смириться, иначе вся жизнь уйдёт на борьбу с ветряными мельницами.
G>Это я все к тому, что SQL высокоуровневый язык. Если в C# из чего-то подобного есть только garbage collector, в SQL посмотрите сколько всего и эскалации блокировок и т.д. и т.п. Поэтому код на SQL получается компактным и аналогичный код на C# был бы очень сильно больше.
В C# из "чего-то подобного" есть Linq. Он рвёт T-SQL на тряпки по возможностям декомпозиции. В T-SQL из чего-то подобного linq есть только склейка строк и EXECUTE, которые адски тяжело отлаживать и поддерживать.
Получается, что каждому — своё. На С# мы пишем тяжёлую логику, которую трудно вручную выписывать на SQL. А блокировки и оптимизация планов остаётся в SQL Server.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали:
G>>Проблема в том, чтобы получить транзитивное замыкание? Тогда и процедура не сильно поможет. Лучше замыкание делать не в момент запроса, а строить заранее. G>Нет, я попробовал еще раз описать исходя из вопросов которые возникают: http://rsdn.ru/forum/design/6482289.1
G>Проблема в том, что данных дофига — 36 ГБ и проекция вам не поможет.
Проблема в том, что ты проблему не можешь объяснить, ты уже четвертый пост ходишь вокруг и даже запрос не привел и user flow объяснить не можешь нормально. У тебя есть только отмазка "данных много".
и ОРМ в чистом виде и DAL на хранимках — оба эти подхода в чистом виде дают одинаково отвратный результат
Не позволяй фанбоям какой-то конкретной технологии запудрить тебе мозг, делай то что выгодно в каждом конкретном случае.
Гибкость в выборе подхода даст куда лучший результат
G>Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы?
Не мода, экономика.
Большинство все равно нихрена в БД не понимает и сделает хуже чем ОРМ
Здравствуйте, Gattaka, Вы писали:
G>Коллеги, G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы? Учитывая, что код на sql как правило более локаничный и лучше читается.
Про фэн-шуй не будем, это и без меня расскажут. С практической точки зрения — ну можно, да. При наличии определённых условий, разумеется.
1. Проект долгоживущий, сознательно завязан на определённую РСУБД и не планирует мигрировать в облако "как-нибудь потом". Если тут объяснения нужны — дополню.
2. Скрипты хранятся вместе с прочими исходниками (отдельным проектом, разумеется) и проверяются по тем же принципам — тесты, билд-сервер, скрипты миграции с обязательной проверкой деплоем на предыдущие выпущенные версии и тыды и тыпы.
3. В скриптах лежит только то, что туда действительно имеет смысл выносить. Тупо перегонять всю БЛ на сторону СУБД неэффективно во всех смыслах, что по поддержке, что по производительности.
Ну а что именно паковать в скрипты — родной sql, хранимки или view — эт уже на усмотрение разработчиков.
G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах?
вопрос изначально поставлен так, что вы уже для себя ришили что лучше. пришли найти поддержку своего решения?
G>Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы?
ужасный SQL код они генерируют когда человек, который пишет запрос с использованием ORM, не понимает что он делает.
но тот же результат будет если этот же человек начнет писать sql запрос.
G>Учитывая, что код на sql как правило более локаничный и лучше читается.
только что касается выборки реляционных данных.
в остальном мы получаем кучу проблем.
— реализовывать сложную логику в разы сложнее и код становится страшным
— сложности с переиспользованием кода. либо просядет по перфомансу, либо копипастить
— плохая тестируемость
Здравствуйте, Gattaka, Вы писали:
IT>>Что у тебя за ORM? G>Ну я знаю про вашу ОРМ... У меня NHibernate и Entity Framework был. Но сама идея парочна.
Ты выбрал два худших представителя тяжёлых ORM и делаешь выводы об идее. Первый, кстати, уже давно протухший трупик. Второй впитал в себя все худшие идеи первого.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, wildwind, Вы писали:
W>В общем виде я бы сказал так. Пока ваше приложение достаточно простое, некритичное к производительности и полностью владеет базой, используйте ORM. Но если систему планируется масштабировать (или просто возможностей ORMа перестанет хватать), нужно быть готовым перенести часть логики в хранимки.
Я бы сказал так. Увидели хранимку — отбейте железной ленейкой руки тому, кто это сделал. Не существует ни одного реального аргумента, почему ХП должны ещё жить. И уж точно масштабируемость тут ни при чём.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали: HBM>>только что касается выборки реляционных данных. HBM>>в остальном мы получаем кучу проблем. HBM>>- реализовывать сложную логику в разы сложнее и код становится страшным G>Сказки про страшный код.
К сожалению, это не сказки. В SQL отвратительные возможности по декомпозиции. Появление table-valued функций немножко улучшило ситуацию, но недостаточно радикально.
Поэтому типичная бизнес-логика на SQL — это бесконечные повторы однообразных оборотов типа "where (t1.OwnerID = @userID or t1.GroupIP = @userGroupId) inner join ... ... where (t2.OwnerID = @userID or t2.GroupIP = @userGroupId)". Это тяжело отлаживать (к примеру, для SQL Server интерактивный отладчик появился уже после того, как я перестал на нём программировать, а для остальных СУБД, как я понимаю, такого до сих пор нет), легко ошибиться, и нет статической проверки корректности компилятором.
Поэтому при прочих равных я предпочту эту бизнес-логику видеть написанной на Linq, где можно с одной стороны писать декларативные стейтменты в стиле SQL, не задуряясь порядком обхода джойнов, а с другой стороны есть все плюшки декомпозиции современного ЯП.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали:
G>>Итак у вас уже словарик rw-локов нужно написать, не много пока. Ну а как вы будете делать блокировки диапозонов? Это еще придумать надо и написать... G>>А будут ли у вас блокировки о намереньях? А эскалации блокировок у вас будут или все будет по простому и как следствие неэффективно? А средства диагностики у вас какие на коллекции какие будут? Дедлоки как ловить? А что если ваша коллекция не влазит в оперативную память целиком, но вы активно работаете с некоторой частью, то есть имеется активная и пасивная части. Пасивная спокойно никому не мешая может лежать на диске. SQL Server это предоставляет. А фрагментация памяти? Если у вас в середине удаляются записи, сколько коллекция памяти будет занимать? Одним словом, написать что-то похожее на SQL Server сложно и в итоге вы получите SQL Server, только на C# и с багами...
S>Вы это к чему? Я не предлагаю заиенить SQL Server, вы спорите о чём-то не том. Для того, что делает SQL Server, альтернативы, в общем-то нет. Только другие RDBMS того же класса. S>А вот для того, чему хватит коллекции в памяти, SQL Server будет оверкиллом. С этим лучше заранее смириться, иначе вся жизнь уйдёт на борьбу с ветряными мельницами.
Это я все к тому, что SQL высокоуровневый язык. Если в C# из чего-то подобного есть только garbage collector, в SQL посмотрите сколько всего и эскалации блокировок и т.д. и т.п. Поэтому код на SQL получается компактным и аналогичный код на C# был бы очень сильно больше.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>Выкини NHibernate, возьми EF или linq2db, там нет таких проблем. G>Вы будете смеятся, но мы до этого выкинули EF. Был болезненный переход на NH... У EF тоже куча недостатков, например он не позволяется получить вам чистые доменные объекты. То есть в классах доменных сущностей у вас присутсвуют ссылки на типы из EF, а это не правильно. Я, например, должен свободно использовать доменную сущность на клиенте не потащив туда ORM... Да и запросы у EF просто жесткач некоторые, NH в этом плане получше, хотя тоже косячит.
Тут надо разобраться в истории:
Команда компилятора выпустила linq2sql чтобы оправдать наличие Expression Trees в C#. Так как парни в команде компилятора очень крутые, то и из библиотека получилась очень крутой. При этом как и весь компилятор — нифига не расширяемой. Планов на долгосрочное развитие linq2sql не было.
Параллельно упоротые чуваки из команды sql server programmability взяли никому не нужный, переусложненный маппер object spaces и натянули на него linq. Получился EF. Он толком ничего не умел, требовал наследоваться от своих классов и имел довольно странный набор возможностей.
К версии .NET 4.0 в linq2sql поправили пару багов, но больше ничего не произошло, EF тоже немного улучшили (ef4), сделали хотя бы внешние включи в сущностях, поправили немного linq провайдер. Но все равно ef4 был говном.
После этого, в 2012 году, ef отдали команде asp.net. ASP.NET команда очень крутая, решает реальные проблемы, а не выдумывает сфероконей. Первым делом они поверх дерьмового EF написали code first и миграции, с этого момента стало вообще возможно использовать EF (ef5).
Потом избавились от EDM и по максимуму выкинули тяжелое наследие object spaces (к сожалению не полностью), получился вполне достойный ORM (ef6). B самое главное выложили все исходники в опенсорс, появилось дофига поезных расширений.
Теперь решили окончательно выкинуть старый EF, оставили только code first api, для провайдера воспользовались либой relinq (которой кстати пользуется и NHibernate). Это называется EF7 или EFCore.
Куча недостатков, о которых ты пишешь — это в EF4. А если нужен маппер баз всякого барахла уже сейчас можно брать EFCore.
G>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
И какую задачу решить надо?
G>>>>Код на SQL который более лаконичный и лучше читается не имеет никакого отношения к высокой производительности. G>>>Ну это был второй аргумент: 1. производительность, 2. локаничность и выразительность. Все таки SQL очень мощный он позволяет вам запросить что вам нужно, а не как извлеч данные. G>>Ок, напиши запрос, удовлетворяющий следующим условиям: G>>1) Если пользователь админ, то ему показать все записи G>>2) Если пользователь не админ, то G>> — показать ему записи, где пользователь = автор G>> — или автор записи входит в группу друзей текущего пользователя G>> — и записи не должны быть скрыты G>>3) Записи должны быть отфильтрованы по категории, которую выбрал пользователь (id категории — параметр запроса, может быть пусто)
G>>При этом запрос должен быть максимально лаконичным и быстрым. G>Я считаю — прекрасно, практически на естественном языке G>
G>select r.id, r.name
G>from rows as r
G>where r.IsHidden = false and
G> (exists(select * from Administrators a where a.User = r.User) or
G> exist(select * from UserFriends uf where uf.User = r.User and r.Friend = @currentUser ))
G>
1) Фильтр по категории забыл
2) Фильтр по автору поста забыл
3) Фильтр по администраторам неверный, должен быть текущий пользователь админом, а не автор
Самое главное даже в таком виде база не построит оптимальный план и запрос будет выполняться плохо.
Правильный подход — сделать 4 запроса:
1) Для админа без фильра
2) Для админа с фильтром
3) Для обычного пользователя без фильтра
4) Для обычного пользователя с фильтром
Эти 4 запрос будут работать лучше любого одного, который ты напишешь.
И с linq это приведет всего к двум if в логике программы.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Sinclair, Вы писали:
G>>>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db? S>>Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании. G>То есть вы сам подход rich domain model не одобряете? Вроде бы там все стройно и гладко получается — многим нравится в сравнении с анемичной моделью.
Поймите, хорошая абстракция — это такая абстракция, которая игнорирует несущественные детали.
Делать вид, что ваши объекты — "вот они", на расстоянии одного вызова метода — неконструктивно.
Оказывается, что код вида
foreach(var employee in Db.GetAllEmployees())
{
if (employee.CurrentPosition = position)
{
var c = Db.FindContractByEmployee(employee);
c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
}
}
легко писать, но он чудовищно тормозит. Люди тратят годы на оптимизацию подобного булшита, пользователи привыкают к операциям, которые длятся сутки и часто откатываются с conflicting change из-за optimistic locking, и так далее. (Ещё веселее, когда Rich Model Fan добавляет метод AdjustSalary прямо к Employee, и прячет логику по подгрузке контракта прямо тудысь. А полные фанаты вообще считают, что подобный код не имеет права ходить вне объектов, и считают само собой разумеющимся метод EmployeePosition.AdjustSalary(int increasePercent, Date effectiveDate). )
При этом тот же самый код прекрасно летает на моках, в которых Db заменён на in-memory collection.
Хорошая абстракция должна учитывать, что стоимость каждого FindContractByEmployee — запредельна по сравнению с временем исполнения кода на клиенте. Что фильтрация по position на клиенте — это факап. Что вообще идея поднимать в память клиента employee, который тебе нафиг не нужен — это лишнее напряжение канала между базой и апп-сервером, а удержание транзакции на такие длинные сроки — должностное преступление.
Ещё важнее то, что вот этот вот чудестный код меняется раз в месяц вместе с бизнес требованиями, а структура данных почти неизменна. При этом у нас нет механизма по отделению изменений в логике от изменений в структуре.
В анемик-модели у нас были бы lean entities типа Employee и Contract, а AdjustSalary был бы методом отдельного stateless-модуля, написанного с массовым использованием linq.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали: G>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
Структура базы не имеет значения. Вы просто мыслите категориями ORM девяностых годов, когда теоретики верили в преодоление Impedance mismatch и прочий булшит.
Речь о том, что
а) поднимать никакие данные в память не нужно. Ни в клиента, ни в аппсервер в трёхзвенке. В памяти нужны только те данные, которые там используются.
б) ХП в таких случаях работают ничуть не лучше, чем обычный SQL batch. Я встречал множество людей, искренне полагающих себя знатоками SQL, которые вообще не знали о возможности отправить на сервер несколько стейтментов кряду.
в) писать голый SQL в таких случаях — занятие малоперспективное. Не тот это язык, на котором удобно писать что-то длиннее пары килобайт.
г) поэтому правильный ответ — это использование современных средств типа linq2sql.
Пишете на нём тот самый SQL, который вы хотели запихать в ХП. Смотрите на него, убираете однообразные повторы путём выноса во вспомогательные процедуры/функции. Наслаждаетесь статическим контролем компилятора и возможностями рефакторинга. Профит!
А через некоторое время замечаете ещё один волшебный бонус: гарантия непротиворечивости версии базы и аппсервера. Расхождений в коде ХП между стейджингом и продакшном больше нет как класса.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
G>Я бы про ОРМ высказался "ну можно, да..." Когда вы проверили генерируемый запрос, что этой ОРМ и этой версией ОРМ он более ни менее нормально генерируется. И если не так критично вытаскивание из базы избыточного объема данных чтобы заполнить поля сущности, которые не используются.
кто мешает вытащить только те данные, которые нужно и не тащить лишних?
G>А в целом от ОРМ гораздо больше проблем, чем выгоды.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>кто мешает создать еще одну DTO с теми полями, которые нужны и не тянуть все?
DTO? Опа... Ну ладно, потому что этих DTO ваших станет немерено просто — рост логарифмический буквально.
G>>>>А в целом от ОРМ гораздо больше проблем, чем выгоды. HBM>>>может быть вы просто не умеете его готовить? G>>Вот затраты на эту готовку слишком большие. Вы должны написать запрос с помощью ORM, потом посмотреть во что оно мапится, потом переписать, потом увидеть что не помогло и т.д. Вместо того, чтобы сразу написать нормальный SQL запрос. И как раз таки я заметил что среди сторонников ORM много тех, кто не умеет готовить SQL как раз таки
HBM>ну конечно. 100500 CRUD хранимых это прям рай. HBM>на каждый чих по хранимой. на каждую мелкую сущность по 5 хранимых. HBM>не не не девид блейн
Но вот тут как раз таки можно ОРМ применять, как я написал выше. Это единственное с чем оно может справлятся.
HBM>оптимизировать sql нужно тогда, когда есть проблемы с перфомансом. 90% операций приложения это таки CRUD, а с ним любая ORM отлично справляется.
Ерунда. Нужно сразу писать оптимальный код. Преждевременная пессимизация приводит к проектам, которые и за полгода не отрефакторишь. HBM>а дальше при необходимости в некоторых местах можно и хранимыи использовать, если будет нужно.
HBM>только что касается выборки реляционных данных. HBM>в остальном мы получаем кучу проблем.
Не совсем с той стороны проблемы описываете. Всё это решается, только за совсем отдельные деньги. Во-первых, девелопер должен _знать_ выбранную РСУБД. Не уметь писать select*from, а именно знать. Как отдельный язык программирования.
Во-вторых, sql не прощает ошибок из-за поголовной убогости инструментальных средств. Рефакторинг, глобальная проверка скриптов, поменять схему данных без приседаний? Не, не слышал. Т.е. разработчик должен быть ещё и опытным и уметь думать _до_ того, как делать (что после уютной студии немножко так напрягает).
Ну и в третьих, разработчик должен хоть немножко уметь в архитектуру и в предметную область. См предыдущий пункт, поздно всплывший косяк в дизайне схемы может не чиниться годами, ибо его исправление стоит на пару порядков дороже, чем час работы горячей линии, которая про этот косяк уже знает и умеет чинить где-то раз в год.
Ну и где вы таких добровольцев нынче найдёте в товарных количествах? И, главное, замотивируете тратить время на нудятину поддержки вместо более полезных вещей?
Поэтому лучше использовать "голый" sql там, где от него баланс польза/страдания положительный и закопать его подальше во всех остальных местах.
HBM>>красивый код понятие конечно относительно. HBM>>я вкладываю в это часто лаконичность. HBM>>если я могу при помощи F# решить задачу 3мя строками, а на SQL — 25 строк, то выбор для меня очевиден G>Вот только на F# у вас не 3 строчки получится и не 30 и не 300 чтобы получить аналог. Когда начинаешь рассказывать про эскалации блокировок, про уровни изоляции транзакция, про то что СУБД может автоматически выбирать nested loop или merge join или hash join. И что эффективность в зависимости от данных сильно разная. Люди начинают понимать, что это не просто select под капотом куча алгоритмов и что SQL гораздо более высокоуровневый чем они думали. Тогда становится понятным какой примитив эти 3 строчки на F#.
есть задачи, где этот метод решения будет лучшим.
есть задачи, когда проще поднять какой-нить кластер из 100 машинок и распараллелить вычисления.
это может быть проще и дешевле, чем искать крутого спеца в сиквеле.
проще говоря, нужно использовать подходящий инструмент в каждом конкретном случае.
а у нас сферический конь в вакууме.
HBM>>что тут непонятного? переиспользовать имеющийся код в SQL крайне сложно. модульности нет. G>Вы переоцениваете значимость красивого и удобного кода, как и многие программисты. Компаниям глубоко плевать на то насколько качественный код. Но вот если они потеряют данные или у них будет что-то тормозить — сразу заметят. Неудобный код — решается просто, нанимаем еще одного программиста и все... G>Слишком многие программисты гонятся за "красивостью", слишком ее переоценивая.
плохой код сложно поддерживать.
когда из-за плохого кода реализация новой фичи начнет занимать не 1 неделю, а месяц, бизнесс это заметит куда быстрее, из-за возврастающих дополнительных расходов, нежели разницу в отклике rest api между 400мс и 600мс.
HBM>>писать интеграционные тесты сложнее и накладнее. G>Только они будут тестировать то, что происходит на самом деле, а не моки.
а вы не тестируйте моки в юнит-тестах и всё будет хорошо.
HBM>>кто мешает вытащить только те данные, которые нужно и не тащить лишних? G>ОРМ мешает. У меня, допустим замеплина таблица на сущность, а из этой сущности нужно пару столбцов для отчета. ОРМ всю сущность вытащит и дальше вы будете работать с теми полями что вам нужно.
Пара столбцов для отчета — это для ОРМ не сильно проблема. Проблема — когда надо из большой группы сущностей превратить общий отчетный документ. Здесь, конечно, намного проще (лично для меня) составить процедуру, которая бы последовательно формировала нужную выборку. А вот для обычного crud все-таки ОРМ очень удобен. Я бы не бросался в крайности и использовал совместное решение: ОРМ + сложные выборки на sql
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>горизонтальное масштабирование всегда дешевле вертикального.
На практике вертикальное дешевле.
И даже банальные расчеты это показывают. Предположим что цена железа растет линейно мощности.
Чтобы масштабировать сервер горизонтально вам надо его продублировать. То есть купить такой же набор дисков, такой же процессор, такую же мать, столько же памяти. То есть понести двойные затраты.
Чтобы масштабировать вертикально не надо все покупать в двойном размере, потому что упирается система во что-то одно. Например диск или процессор, изредка в память. Поэтому вертикальное масштабирование обходится дешевле.
Кроме того при горизонтальном масштабировании растут издержки на запуск системного ПО. ОС всегда кушает часть ресурсов и чем больше у вас компов, тем больше кушает ОС.
На практике же цена на железо растет медленнее, чем линейно. 16ГБ оп стоят дешевле, чем 2 по 8, диск на ТБ стоит дешевле двух на 500 ГБ. Процессор в 2 раза быстрее не в два раза дороже медленного.
Конечно такая зависимость сохраняется не всегда, если мы возьмем масштабы гугла или ФБ, то там вертикальное масштаирование или физически невозможно или стоит сильно больше горизонтального. Но это гугл и ФБ, таких масштабов в жизни мало кто сможет увидеть. А для обычных приложений вертикальное масштабирование выгоднее.
Почитай про stackoverflow, при их нагрузках они масштабируются вертикально в основном.
Здравствуйте, Sinix, Вы писали:
S>Тут от языка / фреймворков / требований проекта / СУБД в бэкенде зависит. S> Если язык не умеет в linq, фреймворк (подставить косяк по вкусу), проект позволяет тратить время на сопровождение скриптов при каждом рефакторинге, а оптимизатор СУБД захлёбывается на простейших запросах (не будем говорить кто, но постгре в этом смысле ещё ничего), то да — наличие / отсутствие ORM меньшая из проблем.
Если вы на начальной стадии подошли грамотно к проектированию системы структура табличек — редко меняется. Если часто, то что-то не то в консерватории... Чтобы оптимизатор не захлёбывался используйте SQL Server у него очень хороший оптимизатор, говорю на собственном опыте. Вот эти ребята подтвердят: https://www.youtube.com/watch?v=AW87RzZJ0Z0
G>>А в целом от ОРМ гораздо больше проблем, чем выгоды.
S>Ну блин. ORM не сами по себе работает, он должен в стеке использоваться. Т.е. плюс локальные транзакции, плюс change tracking плюс сериализация изменений по сети, + обработка в одной транзакции данных, которые объявлены в разных сборках (т.е. общего репо как такового, нет) + умный коммит + автомиграция / авторазвёртывание и тыды и тыпы.
Change tracking — зло. Вы должны сами отслеживать изменения, а не завязываться на конкретную ORM.
S>Это как бумажные чертежи vs проектирование в цифре — основной выигрыш получается, когда используется вся цепочка, вплоть до инструментального контроля. Иначе получается "дядя Вася с киянкой цифру не понимает — отстой ваша цифра".
Ничего подобного! Это скорее как проф. фотокамера против мыльницы. Мыльница позволяет вам не заморачиваться о выдержке, iso, диафрагмы. Зато теперь вы тоже можете делать фотки прямо как фотограф. Аналогично с ORM зачем читать про SQL и базы данных, если можно работать с обычными и привычными классами, само как-то замепится и выполнится. Вот только на практике этого не бывает и крайними оказываются СУБД в этой истории.
Здравствуйте, Gattaka, Вы писали:
G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы? Учитывая, что код на sql как правило более локаничный и лучше читается.
В общем виде я бы сказал так. Пока ваше приложение достаточно простое, некритичное к производительности и полностью владеет базой, используйте ORM. Но если систему планируется масштабировать (или просто возможностей ORMа перестанет хватать), нужно быть готовым перенести часть логики в хранимки. Хорошо бы, чтобы выбранный ORM это поддерживал. Причем нужно до последнего сопротивляться переносу именно бизнес логики, ограничиваясь логикой выборки и обработки данных.
Проблема в том, что вы SQL толком не знаете. Я не собираюсь вам грубить или хамить... Но у большинства разработчиков делаются глаза круглыми при словах Table Spool, Nested Loop, кластерный и некластерный индекс чем отличаются. И не знание этих простых вещей не мешает им не только писать код под СУБД, но еще и хаять ее, что дескать "тормоза".
Здравствуйте, Gattaka, Вы писали: G>Сейчас вам возразят, что для интеграции приложений шаринг базы данных не самый удачный подход.
Конечно. Даже с одним приложением всегда есть риск разъехаться в версиях базы и клиента. А когда у нас N приложений, да ещё и на разных языках, то обязательно нужен App Server между данными и клиентами, который гарантирует консистентность бизнес-логики.
В таком сценарии использование ХП — это попытка превратить RDBMS в тот самый апп-сервер. Т.е. клиентам закрывают доступ к таблицам; в лучшем случае даём read-only, хотя даже это адски опасно (потому что всегда есть риск, что мы добавили колоночку типа IsDeleted, а где-то в углу завалялся клиент на дельфи, который ничего про неё не знает, и продолжает хреначить репорты с неуловимо отличающимися от официальных цифр данными).
Пусть фигачат хранимки.
Тут мы быстро приезжаем в то, что SQL Server — это плохой AppServer. Во-первых, у него неудачный протокол взаимодействия. Он категорически не может работать с недоверенными клиентами — достаточно сделать begin tran, выполнить процедурку, и оставить соединение открытым, чтобы все остальные ушли курить.
Во-вторых, стандартные ХП написаны в соответствии с парадигмой RPC, неработоспособность которой в сетях уже не обсосал только ленивый.
Банальная штука — ну вот есть у вас процедура, которая должна помечать узлы (или что вы там рассказывали про 700000 записей в своей задаче?...).
Вот я её с клиента позвал. А в ответ мне прилетает connection reset by peer. Что-то там в сеточке взглюкнуло. Что мне делать? Транзакция сработала или таки нет?
Для борьбы с этим 15 лет назад придумали REST, где можно обеспечить идемпотентность.
Вот вы сходу сможете написать свою процедуру идемпотентной? А как отличить идемпотентную от безопасной, и обе от небезопасной?
В итоге, написание клиента, который имеет гарантированную семантику, превращается из лёгкого и приятного занятия в титанический труд по оборачиванию вызовов во всяческие стратегии. Либо имеем приложение, которое работает кое-как — то есть периодически его надо перезапускать, и руками/глазами смотреть, что из последних действий надо повторять.
Чтобы сделать к SQL Server RESTful API на ХП, надо много и упорно трудиться. И всё равно получится не так хорошо, как поверх существующих платформ. Не обязательно WCF — выбор сейчас богатый.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gandjustas, Вы писали: G>Я стараюсь использовать научный метод — подвергать сомнению все, что не доказано. За несколько лет холиваров не нашлось ни одного человека с проектами масштаба SO. Поэтому я сомневаюсь в компетентности всех пишуших про нагрузки.
Хм, ну тогда я вполне обоснованно могу подвергнуть сомнению твое существование
G>Что такое "одновременно" ? Ключевая входящая метрика нагрузки — RPS, "того же порядка" это не число, количество фронтэндов тоже неизвестно. G>Это и дает основание сомневаться.
Я то в курсе, но доступа к статистике прода у меня уже нет, поэтому итак как помню. RPS посчитать можно лихко из предоставленных данных и коммон сенса.
G>Приведи пример чтоли. Не понимаю как шардирование связано с говнокодом. G>Например если из-за говнокода применяется линейный поиск по таблице\коллекции, то шардирование потребует на порядок больше денег, чем исправление говнокода. Но на scale up vs scale out не повлияет.
Вот как раз линейный поиск можно лихко ограничить одним шардом и тем самым минимизировать влияние говнокода. Безусловно, тоже самое можно сделать и с классической scale up схемой, но в ней сложнее отлавливать такие кейсы.
G>>>>>Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет. I>>>>Ну вот, на время апдейта схемы и кода система лежит. G>>>С чего ты взял? I>>Ну вот расскажи(или кинь линку), как сделать по другому. G>Не пойму что сделать по-другому? Что ты делаешь из-за чего у тебя система останавливается?
Ок, давай возьмем пример.
Допустим версия 1 софтины работает с БД, в которой есть табличка Orders с колонкой Comments. В версии 2 той же софтины появляется табличка Comments, связанная N:1 c табличкой Orders. Также в табличке Comments есть поле интовое Kind, которое характеризует тип комментария: жалоба, похвала и тд. Также надо построить полнотекстовый индекс по каменту, ключам и Kind-у.
Необходимо проапдейтать БД и смигрировать данные в новую таблицу. Каждый имеющейся камент необходимо проверить полдесятком регэкспов и в зависимости от результата выставить коле Kind.
В таблице — десятки лямов записей и вся процедура занимает около часа.
Твой solution?
Здравствуйте, IT, Вы писали:
IT>Я бы сказал так. Увидели хранимку — отбейте железной ленейкой руки тому, кто это сделал. Не существует ни одного реального аргумента, почему ХП должны ещё жить. И уж точно масштабируемость тут ни при чём.
Странно слышать столь категоричные утверждения от человека с большим опытом. Хранимки это API к базе, один из видов. А их придумано немало, помимо голого SQL: view с триггерами, MDX, REST HTTP, HandlerSocket и т.д.; и завтра придумают еще. У каждого своя ниша и свои особенности. Для меня "хранимки не должны жить" звучит столь же абсурдно, как "не используйте TCP/IP, только raw sockets".
Ты имел дело с базами, используемыми несколькими приложениями, да еше написанными на разных языках? Наверняка имел. Там не было ни одной хранимки?
А масштабируемость вот при чем. В процессе масштабирования обязательно наступает период, когда нужно выжать из СУБД максимум, чтобы оттянуть апгрейд железа. Начинается поиск вещей, которые эффективнее выполнить внутри базы, чем гонять данные по сети и растягивать транзакции. И ради эффективности приходится жертвовать читаемостью, сопровождаемостью и еще чем-то из чеклиста "идеальный проект". А хранимки позволяют это смягчить. Если их не использовать, то получишь те же "хранимки", но обфусцированные, хранящиеся в коде и создаваемые каждый раз на время выполнения. Что еще хуже.
Кроме того, хранимки могут содержать не только бизнес логику. Более того, от некоторых бизнес-приложению ни жарко, ни холодно. Как то:
триггеры и общие алгоритмы, используемые в них;
RLS;
поддержка хитрых схем репликации, ETL и прочей интеграции с другими системами;
поддержка администриративных процедур;
Здравствуйте, Gattaka, Вы писали:
G>Ну так проблема версий сервера везде существует. Разве не может получится разных версий API и клиентов? Запросто... В БД вы просто делаете таблицу с текущим номером версии схемы и остальные приложения в эту таблицу смотрят. Делов то...
Проблема не в этом, а в том, что логика в каждом из приложений реализована немножко по-другому. В одном округляется до 2х знаков после запятой, а в другом — до 4х. G>В случае с сервером с АPI используется аналогичный подход, так что это не то чем уж так разительно подходы отличаются.
Нет, в случае с сервером нельзя забыть "посмотреть в таблицу", потому что версия API уезжает в сервер в обязательном порядке в составе URL.
И даже если вы первую версию АПИ выпустили не подумав про версионирование, то всегда можете положить новый API "рядом" с существующим.
G>DML — нужно запрещать... У вас ведь с App сервером тоже никто контракт внезапно поменять не может. Добавить поле в DTO IsDeleted?
А при чём тут DTO? Мы добавили в схему поле isDeleted, и подпилили код единственного апп-сервера так, чтобы при попытке прочитать такой ресурс получалось 410 Gone, а не данные.
Если внутри апп-сервер устроен нормально (например, вместо чистого sql использует linq), то мы фильтрацию по isDeleted = 0 ставим в единственном месте, которое отвечает за возврать IQueryable<MyEntity> MyEntities. И имеем желаемый накал.
G>Зато есть преимущества, которые никто больше не может перекрыть. Это скорость.
Скорость чего? И по сравнению с чем? Скорость исполнения? Я вас разочарую — ХП исполняется настолько же быстро, сколько и эквивалентный ей SQL батч.
Скорость написания — в разы хуже, потому что язык хреновый. С# по скорости написания корректного кода уделывает любой диалект SQL. G>И огромная куча функционала под капотом. Мне, например, нравится пример с коллекцией в памяти, которую куча потоков модифицирует в памяти. Как организоывать блокировки? Для каждой строчки read-write lock, а блокировки на диапозоны, а что насчет эскалации блокировок. В SQL Server этот код уже написан, отлажен и работает максимально эффективно. Если вы будете делать что-то аналогичное, то либо потратите кучу времени и напишите кучу кода, либо получите неэффективное решение.
Если честно, то это плохой пример. Для коллекции в памяти иметь словарик rw-локов безо всякой иерархии окажется на несколько порядков эффективнее SQL Server. Просто потому, что он упирается в IOPS диска, а коллекция в памяти — в ширину шины.
Но дело не в этом, а в том, что никто не предлагает отказаться от SQL Server. Просто хранимки в нём для бизнес-логики не нужны. Бизнес-логика пусть будет в сервере приложений, который должен отправлять в сервер нужные нам батчи. Вместо хранимки на 50 килобайт с кучей ветвлений и corner cases у нас будут уезжать 5 килобайт стейтментов, которые делают то же самое.
G>В случае с SQL Server у вас данные в едином месте и управляются единым кодом — там все максимально эффективно, нет лишних сериализаций, переливаний из сущностей в DTO и т.п.
Вообще-то, максимально эффективно SQL Server работает только в рамках стейтментов. В рамках ХП есть много типичных приёмов для просада производительности.
К примеру, т.к. в SQL нет способа, например, передать "стейтмент" как аргумент метода, то для обобщения кода, который должен работать с разными источниками данных, обычно предварительно сливают промежуточные данные во временную таблицу, чтобы потом над ней уже выполнить общую часть алгоритма. Это второй (после курсоров) способ убить производительность сервера на корню. В linq таких ограничений нету, и мы можем на ходу генерировать нужные нам стейтменты. Это позволяет сочетать выразительную мощь современного языка программирования общего назначения с вычислительной эффективностью сиквельного оптимизатора запросов. Итоговая производительность будет лучше, чем у чистых хранимок.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Sinclair, Вы писали:
S>>Здравствуйте, Gattaka, Вы писали:
G>>>Подозрительная у вас rich model... В частности вызов метод Db.FindContractByEmployee(employee) G>>>
G>>>foreach(var employee in Db.GetAllEmployees())
G>>>{
G>>> if (employee.CurrentPosition = position)
G>>> {
G>>> var c = employee.Contracts.FirstOrDefault();// Либо Where... все обеспечивается ORM. С linq2db такой код не получится писать я так понимаю. Есть там LazyLoad?
G>>> c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
G>>> }
G>>>}
G>>>
S>>Спасибо за исправление. Lazy Load в linq2db нет, и слава богу. Вы понимаете, чем ужасен этот код? S>>Если посмотреть на его исполнение под SQL Profiler, то волосы зашевелятся даже там, где их нет. G>Ну да... у меня может и шевелятся Но есть сторонники, которые начнут доказывать, что это база данных виновата... Давайте на MongoDB переходить, там тормозов нет.
Ну так правильный ответ-то не в монге, и не в хп.
А в
var employees = from e in db.Employee where e.CurrentPosition = position select e;
var contracts = db.GetActiveContracts(employees); // from c in db.EmployeeContracts where c.EmployeeId in (from e in employees select id) and c.Status = ContractStatus.Active select c;
contracts.Insert(db.ContractChange,
c => new ContractChange {
ContractID = c.Id,
EffectiveDate = effectiveDate,
SalaryAmount = c.CurrentSalary * 1.1
});
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Коллеги,
Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы? Учитывая, что код на sql как правило более локаничный и лучше читается.
Здравствуйте, Sinix, Вы писали: S>Про фэн-шуй не будем, это и без меня расскажут. С практической точки зрения — ну можно, да. При наличии определённых условий, разумеется.
Я бы про ОРМ высказался "ну можно, да..." Когда вы проверили генерируемый запрос, что этой ОРМ и этой версией ОРМ он более ни менее нормально генерируется. И если не так критично вытаскивание из базы избыточного объема данных чтобы заполнить поля сущности, которые не используются.
А в целом от ОРМ гораздо больше проблем, чем выгоды.
HBM>>кто мешает вытащить только те данные, которые нужно и не тащить лишних? G>ОРМ мешает. У меня, допустим замеплина таблица на сущность, а из этой сущности нужно пару столбцов для отчета. ОРМ всю сущность вытащит и дальше вы будете работать с теми полями что вам нужно.
кто мешает создать еще одну DTO с теми полями, которые нужны и не тянуть все?
G>>>А в целом от ОРМ гораздо больше проблем, чем выгоды. HBM>>может быть вы просто не умеете его готовить? G>Вот затраты на эту готовку слишком большие. Вы должны написать запрос с помощью ORM, потом посмотреть во что оно мапится, потом переписать, потом увидеть что не помогло и т.д. Вместо того, чтобы сразу написать нормальный SQL запрос. И как раз таки я заметил что среди сторонников ORM много тех, кто не умеет готовить SQL как раз таки
ну конечно. 100500 CRUD хранимых это прям рай.
на каждый чих по хранимой. на каждую мелкую сущность по 5 хранимых.
не не не девид блейн
оптимизировать sql нужно тогда, когда есть проблемы с перфомансом. 90% операций приложения это таки CRUD, а с ним любая ORM отлично справляется.
а дальше при необходимости в некоторых местах можно и хранимыи использовать, если будет нужно.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
G>>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? HBM>вопрос изначально поставлен так, что вы уже для себя ришили что лучше. пришли найти поддержку своего решения?
А вы заметили, в наблюдательности вам не откажешь... Это мотивация была.
G>>Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы?
HBM>ужасный SQL код они генерируют когда человек, который пишет запрос с использованием ORM, не понимает что он делает. HBM>но тот же результат будет если этот же человек начнет писать sql запрос.
Ну это подмена понятий. Давайте исходить из того, что человек и sql нормально пишет и ORM хорошо знает.
G>>Учитывая, что код на sql как правило более локаничный и лучше читается.
HBM>только что касается выборки реляционных данных. HBM>в остальном мы получаем кучу проблем. HBM>- реализовывать сложную логику в разы сложнее и код становится страшным
Сказки про страшный код. А что такое красивый код? По мне так это высокопроизводительный код. HBM>- сложности с переиспользованием кода. либо просядет по перфомансу, либо копипастить
Не понятен аргумент. HBM>- плохая тестируемость
Отличная тестируемость, просто тестируйте сквозные сценарии и все будет ОК.
HBM>>кто мешает создать еще одну DTO с теми полями, которые нужны и не тянуть все? G>DTO? Опа... Ну ладно, потому что этих DTO ваших станет немерено просто — рост логарифмический буквально.
их станет столько, сколько необходимо. плохого ничего в этом нет.
HBM>>ну конечно. 100500 CRUD хранимых это прям рай. HBM>>на каждый чих по хранимой. на каждую мелкую сущность по 5 хранимых. HBM>>не не не девид блейн G>Но вот тут как раз таки можно ОРМ применять, как я написал выше. Это единственное с чем оно может справлятся.
ну хоть на этом сошлись. уже отлично
HBM>>оптимизировать sql нужно тогда, когда есть проблемы с перфомансом. 90% операций приложения это таки CRUD, а с ним любая ORM отлично справляется. G>Ерунда. Нужно сразу писать оптимальный код. Преждевременная пессимизация приводит к проектам, которые и за полгода не отрефакторишь.
преждевременная оптимизация не меньшее зло. код должен быть нормальным, но нет смысла гнаться за +1% перфоманса.
HBM>>а дальше при необходимости в некоторых местах можно и хранимыи использовать, если будет нужно.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали: HBM>красивый код понятие конечно относительно. HBM>я вкладываю в это часто лаконичность. HBM>если я могу при помощи F# решить задачу 3мя строками, а на SQL — 25 строк, то выбор для меня очевиден
Вот только на F# у вас не 3 строчки получится и не 30 и не 300 чтобы получить аналог. Когда начинаешь рассказывать про эскалации блокировок, про уровни изоляции транзакция, про то что СУБД может автоматически выбирать nested loop или merge join или hash join. И что эффективность в зависимости от данных сильно разная. Люди начинают понимать, что это не просто select под капотом куча алгоритмов и что SQL гораздо более высокоуровневый чем они думали. Тогда становится понятным какой примитив эти 3 строчки на F#.
HBM>>>- сложности с переиспользованием кода. либо просядет по перфомансу, либо копипастить G>>Не понятен аргумент.
HBM>что тут непонятного? переиспользовать имеющийся код в SQL крайне сложно. модульности нет.
Вы переоцениваете значимость красивого и удобного кода, как и многие программисты. Компаниям глубоко плевать на то насколько качественный код. Но вот если они потеряют данные или у них будет что-то тормозить — сразу заметят. Неудобный код — решается просто, нанимаем еще одного программиста и все...
Слишком многие программисты гонятся за "красивостью", слишком ее переоценивая.
HBM>писать интеграционные тесты сложнее и накладнее.
Только они будут тестировать то, что происходит на самом деле, а не моки.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>есть задачи, где этот метод решения будет лучшим. HBM>есть задачи, когда проще поднять какой-нить кластер из 100 машинок и распараллелить вычисления.
Таких задач нет. HBM>это может быть проще и дешевле, чем искать крутого спеца в сиквеле.
Часто сторонники таких решений с открытым ртом наблюдают как старенький ноутбук возит ораву из 10 серверов... Главное какие алгоритмы вы используете, а не сколько у вас машин. Которые, кстати, еще и админить надо.
HBM>проще говоря, нужно использовать подходящий инструмент в каждом конкретном случае. HBM>а у нас сферический конь в вакууме.
Ну давайте конкретный пример разберем — предлагайте. Просто сам по себе форум архитектура подразумевает абстрактыне рассуждения.
HBM>>>что тут непонятного? переиспользовать имеющийся код в SQL крайне сложно. модульности нет. G>>Вы переоцениваете значимость красивого и удобного кода, как и многие программисты. Компаниям глубоко плевать на то насколько качественный код. Но вот если они потеряют данные или у них будет что-то тормозить — сразу заметят. Неудобный код — решается просто, нанимаем еще одного программиста и все... G>>Слишком многие программисты гонятся за "красивостью", слишком ее переоценивая.
HBM>плохой код сложно поддерживать. HBM>когда из-за плохого кода реализация новой фичи начнет занимать не 1 неделю, а месяц, бизнесс это заметит куда быстрее, из-за возврастающих дополнительных расходов, нежели разницу в отклике rest api между 400мс и 600мс.
Они попробуют нанять еще несколько программистов. Долго слушать сказки про рефакторинг никто не будет.
HBM>а вы не тестируйте моки в юнит-тестах и всё будет хорошо.
Ну так я и предлагаю делать сквозное тестирование.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Sinix, Вы писали: S>>Про фэн-шуй не будем, это и без меня расскажут. С практической точки зрения — ну можно, да. При наличии определённых условий, разумеется.
G>Я бы про ОРМ высказался "ну можно, да..." Когда вы проверили генерируемый запрос, что этой ОРМ и этой версией ОРМ он более ни менее нормально генерируется. И если не так критично вытаскивание из базы избыточного объема данных чтобы заполнить поля сущности, которые не используются.
А что мешает сделать проекцию в ORM? Все известные мне ORM поддерживают проекции и динамическое конструирование запросов. Только по религиозным соображениям не все этим пользуются.
Здравствуйте, gandjustas, Вы писали:
HBM>>>>оптимизировать sql нужно тогда, когда есть проблемы с перфомансом. 90% операций приложения это таки CRUD, а с ним любая ORM отлично справляется. G>>>Ерунда. Нужно сразу писать оптимальный код. Преждевременная пессимизация приводит к проектам, которые и за полгода не отрефакторишь. HBM>>преждевременная оптимизация не меньшее зло. код должен быть нормальным, но нет смысла гнаться за +1% перфоманса.
G>Это неверно.
G>Когда Дейкстра писал про преждевременную оптимизацию он имел ввиду мелкие хаки на уровне отдельных команд процессора, которые дают незначительный прирост быстродействия, но значительно ухудшают читаемость. G>Частично это же касается выбора алгоримтма, типа лучше сначала запилить линейный поиск, чем ловить баги в двоичном поиске с интерполяцией.
G>Но когда мы говорим о высокоуровневой оптимизации — на уровне структур данных, взаимодействия компонентов, базы данных, то оптимизировать нужно с самого начала, иначе потом цена исправления растет экспоненциально. G>Оптимизация SQL касается как раз такого вида оптимизации.
<... пример реализации через жопу скипнут...>
G>Еще хуже оказывается ситуация неоптимальной структуры БД, тогда нужно не просто запросы переписать, а еще и данные перелить и не дай бог приложение у заказчика работает.
то что ты описываешь не совсем верно. Из того что я видел, пример преждевременной оптимизации на высоком уровне обычно такой:
Дизайним что-то крупное. И в процессе раскопок потенциальных проблем видны несколько бутылочных горлышек. Какое именно будет главным зависит от поведения пользователей, а его никто не знает(вот так построены бизнес процессы в компании что никто точно не знает как будут использовать фичу/компоненту/продукт, и даже как используют текущие фичи. Т.е. нормальных предположений сделать нельзя.). Тут встает "архитектор" и выдает: "Модель нагрузки будет вот такой <описание фантазий>". Поэтому мы всю архитектуру, заточим под скорость на такой модели. Причем под этими фантазиями нет никаких фактов, просто человеку так показалось. В случае если он не угадает, такую "заоптимизированную" систему переделать под другую модель нагрузки намного сложнее чем нейтральную. И соответственно нормальное решение, в ситуации когда мы не знаем точно что будет горлышком, сделать нейтральную модель и знать как мы будем отслеживать горлышки, что будет делать сапорт в этот момент, как будем менять поведение системы, когда любая комбинация из них сработает...
Опять же зло предварительной оптимизации не должно оправдываться решением через жопу. Если сразу видно, что решение не держит расчетную нагрузку — в топку его.
Здравствуйте, Sinix, Вы писали:
S>Когда типовой бизнес-сценарий — документик в 40 листов A4 и в нём только биз-логика, без деталей реализации, последнее, что тебе захочется делать — раздувать код в n раз только чтобы отслеживать все исправления вручную. Напоминаю, код ещё должен быть написан так, чтобы правился влёт после очередной корректировки ТЗ совместно с заказчиком. Т.е. чем ближе он будет к исходному тексту — тем лучше.
S>Если что, цена ошибки "упс, строчка потерялась" в лучшем случае — бесплатный фикс (считай, нагрел родную контору эдак на 5 мифических человекодней) в худшем — потеря клиентов (про прелести работы с госслужбами таки не будем).
А на клиенте эти изменения как отслеживаются? Я про INotifyPropertyChanged ну там валидация всякая, подсветка красным невалидных полей?
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, gandjustas, Вы писали:
G>>Это человек следуя подходу "потом оптимизируем" написал вместо сложного запроса пару foreach циклов. Мало того, что получил select n+1, так еще и код представления ориентировался на объекты модели, каждый из которых требовал поднятия по несколько КБ текста. В итоге навигация строилась полсекунды.
Doc>Это не "предварительная оптимизация", т.к. сразу было видно что код не тянет. Да еще "а потом кэш добавим" это вообще откровенное предложение изначально ориентироваться на костыль. Так что это просто оправдание плохого кода мотивом "ну все знают про вред преждевременной оптимизации".
Doc>Предлагаю считать что это пример того, что нельзя писать фиговый код, прикрываясь тезисом о преждевременной оптимизации.
Это так и есть. Но это хорошо видно в ретроспективе.
А как ты докажешь в каждом конкретном случае, что это просто фиговый код, а не попытка "преждевременной оптимизации", не зная к каким последствиям это приведет ?
Даже в этой теме высказываются мнения, что можно написать абы-какой sql, а потом "оптимизировать". Хотя в большинстве случаев "оптимизировать" нельзя, надо переписывать, причем сильно.
Здравствуйте, gandjustas, Вы писали:
G>Это так и есть. Но это хорошо видно в ретроспективе. G>А как ты докажешь в каждом конкретном случае, что это просто фиговый код, а не попытка "преждевременной оптимизации", не зная к каким последствиям это приведет ?
Только субъективный опыт. Например, меня бы смутила необходимость "поднятия по несколько КБ текста". Ну а точно задумался бы после получения 0.5 сек на создание меню и предложения добавить кэш.
Здравствуйте, Gattaka, Вы писали:
G>Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей.
Проблема в том, чтобы получить транзитивное замыкание? Тогда и процедура не сильно поможет. Лучше замыкание делать не в момент запроса, а строить заранее.
Здравствуйте, Gattaka, Вы писали:
G>В БД может хранится как угодно, смысл ОРМ в том чтобы замепить доменную сущность на таблицу или таблицы не доставляя головной боли.
К сожалению, с БД эта благоглупость не прошла. Число задач, где это полноценно срабатывает стремится к нулю. Фактически, это работает только, если нужно обеспечить ввод пользователя, точно совпадающий со структурой таблиц.
G>Вот собственно несостоятельность этой идеалистической картины я и пытаюсь донести. Причем идею работы с доменными объектами без привязки к БД я одобряю. Она здравая и интересная, но не реализованная еще...
Фигли там реализовывать? Вопрос в нужности.
G>И есть еще подход типа Dapper или linq2db — это вобще не ORM, это всего лишь способ писать на C# запросы к базе данных. Эдакий SQL без SQL... По мне так лучше сразу писать на SQL и не парится, к чему эта прослойка, которая лишь добавляет сложности, как правило не все умеет. Аргумент интеллисенс и упрощенного рефакторинга ИМХО того не оправдывает...
Аргумент интеллисенс и не упрощённый, а полноценный рефакторинг — это вообще-то мегафича linq. Тот, кто утверждает обратное просто не в теме.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали: G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
То, что вы называете "ОРМ" — это ублюдочное порождение воспалённого воображения теоретиков девяностых. Выбросьте его немедленно! По сравнению с ним даже логика в ХП выглядит прилично.
Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, wildwind, Вы писали:
W>Здравствуйте, Sinclair, Вы писали:
S>>ХП исполняется настолько же быстро, сколько и эквивалентный ей SQL батч.
W>Вот это немного странно для меня. В других СУБД разница бывает хорошо заметной. Ведь затраты на парсинг и семантический контроль никуда не деваются. Особенно если это "горячее" место.
Во всех взрослых базах работает одинаково. Запрос, как и ХП парсится один раз и сохраняется её план. При повторном запросе парсинга не происходит, по хэшу строки запроса\имени процедуры достается из кеша нужный план.
Актуальна разница ХП и adhoc запроса была 20 лет назад.
Здравствуйте, Gattaka, Вы писали: G> S>В таком сценарии использование ХП — это попытка превратить RDBMS в тот самый апп-сервер. Т.е. клиентам закрывают доступ к таблицам; в лучшем случае даём read-only, хотя даже это адски опасно (потому что всегда есть риск, что мы добавили колоночку типа IsDeleted, а где-то в углу завалялся клиент на дельфи, который ничего про неё не знает, и продолжает хреначить репорты с неуловимо отличающимися от официальных цифр данными). G> S>Пусть фигачат хранимки.
G> DML — нужно запрещать... У вас ведь с App сервером тоже никто контракт внезапно поменять не может. Добавить поле в DTO IsDeleted?
Не DML, а DDL. DTO к схеме имеет отношение близкое к никакому. Как-то с владением терминологией не очень Вера в то что ХП безусловно быстрее кода. Не способность описать *значущую* бизнес задачу(а не реализацию) для своего примера. Все это очень затрудняет предметный спор.
Здравствуйте, gandjustas, Вы писали:
G>И даже банальные расчеты это показывают. Предположим что цена железа растет линейно мощности.
Это верно в весьма небольшом диапазоне.
G> если мы возьмем масштабы гугла или ФБ, то там вертикальное масштаирование или физически невозможно или стоит сильно больше горизонтального.
И ты сам это понимаешь.
G>Но это гугл и ФБ, таких масштабов в жизни мало кто сможет увидеть. А для обычных приложений вертикальное масштабирование выгоднее.
На самом деле упомянутый порог достаточно низок и достигается на раз-2 на сколько нибудь нагруженом ресурсе. А если и статистику считать самостоятельно, и контекстные хинты показывать — то все становистя еще веселей.
G>Почитай про stackoverflow, при их нагрузках они масштабируются вертикально в основном.
А почитай про стоимость и время разработки stackoverflow. В обычном же девелопменте квалификация девелоперов сильно ниже отобранных лично джоелом боевых пидарасов на зп сильно выше средней в нуерке, и тайм прессинг сильнее.
А бывают еще требования по high-availability, которые в принципе невозможно выполнить на одной железяке. Например 99.95% аптайма, которые хотят наверное с половину серьезных заказчиков, прямиком ведет к zero-downtime update. Как ты это заимплементишь на одной железяке?
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали: G>>Я стараюсь использовать научный метод — подвергать сомнению все, что не доказано. За несколько лет холиваров не нашлось ни одного человека с проектами масштаба SO. Поэтому я сомневаюсь в компетентности всех пишуших про нагрузки. I>Хм, ну тогда я вполне обоснованно могу подвергнуть сомнению твое существование
Конечно можешь
G>>Что такое "одновременно" ? Ключевая входящая метрика нагрузки — RPS, "того же порядка" это не число, количество фронтэндов тоже неизвестно. G>>Это и дает основание сомневаться. I>Я то в курсе, но доступа к статистике прода у меня уже нет, поэтому итак как помню. RPS посчитать можно лихко из предоставленных данных и коммон сенса.
Нельзя. Потому что "одновременные сессии" могут быть вообще чем угодно. От живого websocket подключения до количества обращений в интервале 30 минут.
G>>Приведи пример чтоли. Не понимаю как шардирование связано с говнокодом. G>>Например если из-за говнокода применяется линейный поиск по таблице\коллекции, то шардирование потребует на порядок больше денег, чем исправление говнокода. Но на scale up vs scale out не повлияет. I>Вот как раз линейный поиск можно лихко ограничить одним шардом и тем самым минимизировать влияние говнокода. Безусловно, тоже самое можно сделать и с классической scale up схемой, но в ней сложнее отлавливать такие кейсы.
Я говорю про поиск по всей коллекции, при чем тут шард? Переписать SQL запрос и добавить индекс попроще будет, чем плодить шарды и ограничивать запросы.
I>Допустим версия 1 софтины работает с БД, в которой есть табличка Orders с колонкой Comments. В версии 2 той же софтины появляется табличка Comments, связанная N:1 c табличкой Orders. Также в табличке Comments есть поле интовое Kind, которое характеризует тип комментария: жалоба, похвала и тд. Также надо построить полнотекстовый индекс по каменту, ключам и Kind-у. I>Необходимо проапдейтать БД и смигрировать данные в новую таблицу. Каждый имеющейся камент необходимо проверить полдесятком регэкспов и в зависимости от результата выставить коле Kind. I>В таблице — десятки лямов записей и вся процедура занимает около часа. I>Твой solution?
А в чем проблема? Делаешь то что нужно — переливаешь данные, выставляешь kind, это занимает хоть 10 часов. Код не трогаешь, работает старый.
За 10 часов тебе нападает еще комментов, переливание которых займет уже не 10 часов, а меньше, пусть час.
Так за несколько итерация время переливания уменьшится практически до нуля. Потом накатить новую версию софта.
Можно и другим способом — при чтении получать старую и новую версию комментов, а писать только в новую. Так стоит делать если у тебя нету доступа к базе.
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали: G>>Я говорю про поиск по всей коллекции, при чем тут шард? Переписать SQL запрос и добавить индекс попроще будет, чем плодить шарды и ограничивать запросы. I>А я тебе говорю про то, что шардирование тебя заставляет думать правильно и не волевым усилием отрубить поиск по всей коллекции.
А без шардирования думать нельзя? Или если какие-то проблемы думать когда нет шардирования?
I>А переписывать проще пока ты один, а если у тебя в команде десяток-полтора архаровцев разной квалификации и мотивации, то тут проще и дешевле плодить шарды.
Проще обучить, ты даже не представляешь как обучение сказывается на продуктивности. А еще проще не набирать идиотов, которые не могут нормально запрос написать.
G>>За 10 часов тебе нападает еще комментов, переливание которых займет уже не 10 часов, а меньше, пусть час. G>>Так за несколько итерация время переливания уменьшится практически до нуля. Потом накатить новую версию софта. I>рабочий вариант, ты упустил перестроение полнотекстового индекса которое очевидно дело долгое. Если расскажешь как с ним быть без просадки производительности, то можно сказать что нет вопросов
Построение индекса асинхронное в любой системе, так что ждать его нет смысла.
Делает все без просадки производительности.
Здравствуйте, AndrewJD, Вы писали:
AJD>Только не зыбываем, что на таблицу Orders могут быть завязаны другие таблицы, индексы, jobs, репликация. Так что даже переименование не такая уж простая операция.
Она непростая с точки зрения SQL. С точки зрения downtime — примитивная, т.к. объём изменяемых данных околонулевой.
AJD>Важно, хотя бы потому, что общая производительность сервера просядет. А если поплывет статистика по индексам, то тем более.
Разжовываю: мы можем сделать эту операцию сколь угодно медленной. Количество выдвижений в секунду — параметр регулируемый. Можно даже динамически: видим, ночью нагрузка снизилась — поехали побыстрее; днём поднялась — прикрутили фитилёк. S>>3. когда все данные уже на месте, можно подменить код и метаданные за секунды. S>>Это самая сложная из задач, или есть ещё что-то? AJD>Это красиво выглядит только на примитивнейшей базе.
Ждём реально нереально сложную задачу в студию. И, главное-то: мы же в этой ветке обсуждаем вертикальное масштабирование против горизонтального.
Поэтому бессмысленно обсуждать само по себе решение задачи — покажите, в какой архитектуре получится её решить лучше. А то складывается впечатление, что при смене схемы данных в NoSQL не потребуется заниматься вычислением comment.kind при помощи регекспов, или полнотекстовые индексы перестраиваются сами собой.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, AndrewJD, Вы писали:
AJD>Если ты отключил репликацию — у тебя изменился функционал
С чего ты взял что её надо отключать?
Еще раз повторю — у тебя будет один скрипт, который отключит, переименует, включит. Это займет меньше, чем интервал репликации.
G>>Да, топик был о том, что вертикальное масштабирование выгоднее горизонтального. На что коллега сказал, что при горизонтальном масштабировании можно сделать zero downtime и предложил такую проблему. Ты пишешь что нельзя это сделать без просадки производительности. AJD>Я пишу что при вертикальном масштабировании просадка производительности недопустимо велика и выгоднее положить серевер, чтобы выкатить новую версию.
Это опять фантазии. Скорость работы можно сделать любую и нагрузку, соответственно, тоже.
AJD>>>Что подверждает тезис что твоя схема работает только при отсуствии сколько нибудь существенной нагрузки. G>>И что? Ты все равно не можешь предложить альтернативу. AJD>Нет. Я не могу предложить альтернативу. Мне наоборот интересно как это сделать на существующей архитектуре. Просто предложеный подход добавиим тут на лету пару табличик и вюьх и все будет пучком плохо работает.
Это снова фантазии или есть конкретные причины почему плохо?
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
G>>Я бы про ОРМ высказался "ну можно, да..." Когда вы проверили генерируемый запрос, что этой ОРМ и этой версией ОРМ он более ни менее нормально генерируется. И если не так критично вытаскивание из базы избыточного объема данных чтобы заполнить поля сущности, которые не используются.
HBM>кто мешает вытащить только те данные, которые нужно и не тащить лишних?
ОРМ мешает. У меня, допустим замеплина таблица на сущность, а из этой сущности нужно пару столбцов для отчета. ОРМ всю сущность вытащит и дальше вы будете работать с теми полями что вам нужно.
G>>А в целом от ОРМ гораздо больше проблем, чем выгоды. HBM>может быть вы просто не умеете его готовить?
Вот затраты на эту готовку слишком большие. Вы должны написать запрос с помощью ORM, потом посмотреть во что оно мапится, потом переписать, потом увидеть что не помогло и т.д. Вместо того, чтобы сразу написать нормальный SQL запрос. И как раз таки я заметил что среди сторонников ORM много тех, кто не умеет готовить SQL как раз таки
G>Я бы про ОРМ высказался "ну можно, да..."
Тут от языка / фреймворков / требований проекта / СУБД в бэкенде зависит.
Если язык не умеет в linq, фреймворк (подставить косяк по вкусу), проект позволяет тратить время на сопровождение скриптов при каждом рефакторинге, а оптимизатор СУБД захлёбывается на простейших запросах (не будем говорить кто, но постгре в этом смысле ещё ничего), то да — наличие / отсутствие ORM меньшая из проблем.
G>А в целом от ОРМ гораздо больше проблем, чем выгоды.
Ну блин. ORM не сами по себе работает, он должен в стеке использоваться. Т.е. плюс локальные транзакции, плюс change tracking плюс сериализация изменений по сети, + обработка в одной транзакции данных, которые объявлены в разных сборках (т.е. общего репо как такового, нет) + умный коммит + автомиграция / авторазвёртывание и тыды и тыпы.
Это как бумажные чертежи vs проектирование в цифре — основной выигрыш получается, когда используется вся цепочка, вплоть до инструментального контроля. Иначе получается "дядя Вася с киянкой цифру не понимает — отстой ваша цифра".
G>>>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? HBM>>вопрос изначально поставлен так, что вы уже для себя ришили что лучше. пришли найти поддержку своего решения? G>А вы заметили, в наблюдательности вам не откажешь... Это мотивация была.
это аргумент в сторону того, что вести на эту тему дискуссию изначально не имеет смысла, так как вы твёрдо уверены в своей правоте и будете предвзяты
HBM>>только что касается выборки реляционных данных. HBM>>в остальном мы получаем кучу проблем. HBM>>- реализовывать сложную логику в разы сложнее и код становится страшным G>Сказки про страшный код. А что такое красивый код? По мне так это высокопроизводительный код.
красивый код понятие конечно относительно.
я вкладываю в это часто лаконичность.
если я могу при помощи F# решить задачу 3мя строками, а на SQL — 25 строк, то выбор для меня очевиден
HBM>>- сложности с переиспользованием кода. либо просядет по перфомансу, либо копипастить G>Не понятен аргумент.
что тут непонятного? переиспользовать имеющийся код в SQL крайне сложно. модульности нет.
HBM>>- плохая тестируемость G>Отличная тестируемость, просто тестируйте сквозные сценарии и все будет ОК.
Здравствуйте, Vasiliy2, Вы писали:
V>Пара столбцов для отчета — это для ОРМ не сильно проблема. Проблема — когда надо из большой группы сущностей превратить общий отчетный документ. Здесь, конечно, намного проще (лично для меня) составить процедуру, которая бы последовательно формировала нужную выборку. А вот для обычного crud все-таки ОРМ очень удобен. Я бы не бросался в крайности и использовал совместное решение: ОРМ + сложные выборки на sql
Разделяю вашу точку зрения полностью. Вот только она как раз и идет в разрез с мнением, что бизнес логика в хранимках это плохо. Это я про те самые сложные выборки.
HBM>>есть задачи, где этот метод решения будет лучшим. HBM>>есть задачи, когда проще поднять какой-нить кластер из 100 машинок и распараллелить вычисления. G>Таких задач нет.
нет вообще или нет конкретно в вашей компании?
HBM>>это может быть проще и дешевле, чем искать крутого спеца в сиквеле. G>Часто сторонники таких решений с открытым ртом наблюдают как старенький ноутбук возит ораву из 10 серверов... Главное какие алгоритмы вы используете, а не сколько у вас машин. Которые, кстати, еще и админить надо.
машины админить не надо, всё на автомате.
горизонтальное масштабирование всегда дешевле вертикального.
HBM>>проще говоря, нужно использовать подходящий инструмент в каждом конкретном случае. HBM>>а у нас сферический конь в вакууме. G>Ну давайте конкретный пример разберем — предлагайте. Просто сам по себе форум архитектура подразумевает абстрактыне рассуждения.
да у меня нет сейчас таких задач. у меня всё хорошо
HBM>>плохой код сложно поддерживать. HBM>>когда из-за плохого кода реализация новой фичи начнет занимать не 1 неделю, а месяц, бизнесс это заметит куда быстрее, из-за возврастающих дополнительных расходов, нежели разницу в отклике rest api между 400мс и 600мс. G>Они попробуют нанять еще несколько программистов. Долго слушать сказки про рефакторинг никто не будет.
и это приведет в итоге к еще большим проблемам и временным затратам. ведь плохой код, а в частности, технический долг, он как снежный ком.
HBM>>а вы не тестируйте моки в юнит-тестах и всё будет хорошо. G>Ну так я и предлагаю делать сквозное тестирование.
еще раз — это сложно.
когда нужно протестировать простой кейс, вам нужно нагнать кучу связных данных, иначе оно работать не будет.
и каждый тест будет огромным и тяжело поддерживаемым. а значит, что скорее всего их никто писать и поддерживать не будет.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>нет вообще или нет конкретно в вашей компании?
Философского камня нет вообще или конкретно вы его не видели?
HBM>>>это может быть проще и дешевле, чем искать крутого спеца в сиквеле. G>>Часто сторонники таких решений с открытым ртом наблюдают как старенький ноутбук возит ораву из 10 серверов... Главное какие алгоритмы вы используете, а не сколько у вас машин. Которые, кстати, еще и админить надо.
HBM>машины админить не надо, всё на автомате. HBM>горизонтальное масштабирование всегда дешевле вертикального.
Объясняю на пальцах. У одного из компьютеров посыпался диск, у второго винда начала обновление. Что тогда?
HBM>>машины админить не надо, всё на автомате. HBM>>горизонтальное масштабирование всегда дешевле вертикального. G>Объясняю на пальцах. У одного из компьютеров посыпался диск, у второго винда начала обновление. Что тогда?
в случае с одним сервером таких проблем быть не может?
а если он один вышел из строя, что тогда?
шанс что ляжет весь кластер минимален.
да, и по секрету, винда на серваках сама обновления не начинает
HBM>>горизонтальное масштабирование всегда дешевле вертикального.
G>На практике вертикальное дешевле. G>И даже банальные расчеты это показывают. Предположим что цена железа растет линейно мощности. G>Чтобы масштабировать сервер горизонтально вам надо его продублировать. То есть купить такой же набор дисков, такой же процессор, такую же мать, столько же памяти. То есть понести двойные затраты. G>Чтобы масштабировать вертикально не надо все покупать в двойном размере, потому что упирается система во что-то одно. Например диск или процессор, изредка в память. Поэтому вертикальное масштабирование обходится дешевле.
тут мы упремся либо в ограничения по железу, либо в софтину, которая просто не сможет загрузить всю железку и будет продолжать тормозить
G>Кроме того при горизонтальном масштабировании растут издержки на запуск системного ПО. ОС всегда кушает часть ресурсов и чем больше у вас компов, тем больше кушает ОС.
ось можно всегда подтюнить, и она будет кушать почти незначительную часть ресурсов. я бы вообще это не брал в расчет.
G>На практике же цена на железо растет медленнее, чем линейно. 16ГБ оп стоят дешевле, чем 2 по 8, диск на ТБ стоит дешевле двух на 500 ГБ. Процессор в 2 раза быстрее не в два раза дороже медленного. G>Конечно такая зависимость сохраняется не всегда, если мы возьмем масштабы гугла или ФБ, то там вертикальное масштаирование или физически невозможно или стоит сильно больше горизонтального. Но это гугл и ФБ, таких масштабов в жизни мало кто сможет увидеть. А для обычных приложений вертикальное масштабирование выгоднее.
мы же тут не конкретный пример рассматриваем, а мечтаем, так сказать
G>Почитай про stackoverflow, при их нагрузках они масштабируются вертикально в основном.
SO в отличии от других просто пытаются максимально выжать из железа. это круто.
но тем не менее их архитектура изначально расчитана на горизонтальное масштабирование.
Здравствуйте, Gattaka, Вы писали:
HBM>>кто мешает вытащить только те данные, которые нужно и не тащить лишних? G>ОРМ мешает. У меня, допустим замеплина таблица на сущность, а из этой сущности нужно пару столбцов для отчета. ОРМ всю сущность вытащит и дальше вы будете работать с теми полями что вам нужно.
Что у тебя за ORM?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, gandjustas, Вы писали:
G>Типичное приложение — это CRUD. В простом приложении CRUD это 100% всей работы с базой, в сложном — не менее 60% по моим подсчетам.
Даже CRUD для ORM не такое простое дело, например в том же NHibernte при этом нужно открывать именно его транзакции обязательно. Иначе текут коннекшены, он их в таком случае не освобождает.
G>Высокопроизводительное чтение данных строится на простом принципе — делаем максимально точный предикат (без всяких t.Field = @p or @p is null) для выборки и минимальную проекцию. G>Это значит что тебе нужно будет или 100500 процедур для чтения данных, или клеить строки в SQL. G>Естественно никто в здравом уме так не делает и использует запросы общего вида, с неоптимальными проекциями и предикатами, получая быстродействие заведомо хуже того, что могло бы быть.
G>Что касается изменения данных, то картина тоже не в пользу процедур. В случае изменения\добавления\удаления одной строки никакой разницы с запросами из приложения нет. G>Зато если надо сразу несколько строк добавить\обновить\удалить, то ORMы просто формируют батч и отправляют на сервер, а для процедуры нужно данные сериализовать в какой-нибудь xml\json или на крайняк в табличный параметр. Потом распарсить параметр в процедуре и только после этого выполнить операции. Естественно подход с процедурами также заведомо неоптимален.
Погодите. Наиболее типичный пример у меня в базе лежит 10 ГБ записей в табличке, мне нужно найти в этой куче удовлетворящие моему критерию умножить поле на два и вставить записи в две другие таблички. Решение с хранимой процедурой очевидно. Решение с ORM — нужно вытащить данные из БД, матерелиазовать их в сущности, сделать бизнес логику и поехали опять в базу на вставку. Вот за счет таких переливаний ORM и проигрывает, причем сильно. Но зато бизнес логика не в хранимках и "красиво"
G>Код на SQL который более лаконичный и лучше читается не имеет никакого отношения к высокой производительности.
Ну это был второй аргумент: 1. производительность, 2. локаничность и выразительность. Все таки SQL очень мощный он позволяет вам запросить что вам нужно, а не как извлеч данные.
G>Если вы на начальной стадии подошли грамотно к проектированию системы структура табличек — редко меняется. Если часто, то что-то не то в консерватории... Чтобы оптимизатор не захлёбывался используйте SQL Server у него очень хороший оптимизатор, говорю на собственном опыте. Вот эти ребята подтвердят: https://www.youtube.com/watch?v=AW87RzZJ0Z0
А вот это классическая ошибка программиста, от которых всех аналитиков приходится отучать в первую очередь (от них и наслушался). Aka "я здесь один" или "работает на малом — будет работать и на большом".
Ну вот представьте себе средних размеров биз-отдел под любую erp: команда из 10 человек (собственно разработчики — половина, из них с опытом — человека три в лучшем случае), число сущностей в модели данных — эдак с полторы тысячи (это ещё немного), команда должна дорабатывать продукт под новых клиентов и под изменения законодательства (очередной нежданчик минимум пару раз в квартал), а не тратить своё время на "поправил — всё поломалось".
Вот на таких маштабах все благоглупости типа "всё можно сделать вручную", "скрипты легко поддерживать" или "всё можно запроектировать заранее" выветривается через неделю максимум.
G>Чтобы оптимизатор не захлёбывался используйте SQL Server у него очень хороший оптимизатор, говорю на собственном опыте.
О, тож распространённое заблужление aka "бесплатного sql express достаточно"/"все клиенты готовы покупать взрослый MS SQL". Таки нет, как минимум поддержка postgre нужна. А там, увы, страх и боль, причём со всем стеком, от средств администрирования и до косяков в провайдере ADO.Net. Впрочем, нормальный orm тут спасает, да.
G>Change tracking — зло. Вы должны сами отслеживать изменения, а не завязываться на конкретную ORM.
Бугагашенька.
Когда типовой бизнес-сценарий — документик в 40 листов A4 и в нём только биз-логика, без деталей реализации, последнее, что тебе захочется делать — раздувать код в n раз только чтобы отслеживать все исправления вручную. Напоминаю, код ещё должен быть написан так, чтобы правился влёт после очередной корректировки ТЗ совместно с заказчиком. Т.е. чем ближе он будет к исходному тексту — тем лучше.
Если что, цена ошибки "упс, строчка потерялась" в лучшем случае — бесплатный фикс (считай, нагрел родную контору эдак на 5 мифических человекодней) в худшем — потеря клиентов (про прелести работы с госслужбами таки не будем).
G> Аналогично с ORM зачем читать про SQL и базы данных
Вы точно с ORM работали? Фэнтази какая-то, чесслово. Человек, неспособный самостоятельно воспроизвести/диагностировать/починить проблему, в разработчиках живёт недолго. И знание SQL / умение копаться в существующем коде тут — самые базовые навыки.
Здравствуйте, Gattaka, Вы писали:
G>Даже CRUD для ORM не такое простое дело, например в том же NHibernte при этом нужно открывать именно его транзакции обязательно. Иначе текут коннекшены, он их в таком случае не освобождает.
Это такая шутка? CRUD в исполнении linq2db
G>Погодите. Наиболее типичный пример у меня в базе лежит 10 ГБ записей в табличке, мне нужно найти в этой куче удовлетворящие моему критерию умножить поле на два и вставить записи в две другие таблички. Решение с хранимой процедурой очевидно. Решение с ORM — нужно вытащить данные из БД, матерелиазовать их в сущности, сделать бизнес логику и поехали опять в базу на вставку. Вот за счет таких переливаний ORM и проигрывает, причем сильно. Но зато бизнес логика не в хранимках и "красиво"
Тот же linq2db поддерживает не только DML, но и DDL, а это, например, работа с временными таблицами, что в свою очередь означает, что на LINQ можно написать код, которые перелапатит пол базы и при этом не поднимет на клиента ни одной записи.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали:
G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах?
Мне однажды пришлось поддерживать такой проект.
Я не припомню чтобы за всю мою карьеру мне попадался другой проект, который было бы настолько же сложно поддерживать.
Субъективное, конечно. Но я был не одинок в таких оценках того проекта.
Основная сложность поддержки того проекта была именно в хранимых процедурах, дело усугублялось тем, что процедуры писались профессиональным SQL-разработчиком, а поддерживалось это потом C# full-stack разработчиками, которым было непросто читать многостраничные cross-DB запросы с кучей JOIN'ов, CROSS-APPLY, MERGE и прочими прелестями.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
HBM>>>горизонтальное масштабирование всегда дешевле вертикального.
G>>На практике вертикальное дешевле. G>>И даже банальные расчеты это показывают. Предположим что цена железа растет линейно мощности. G>>Чтобы масштабировать сервер горизонтально вам надо его продублировать. То есть купить такой же набор дисков, такой же процессор, такую же мать, столько же памяти. То есть понести двойные затраты. G>>Чтобы масштабировать вертикально не надо все покупать в двойном размере, потому что упирается система во что-то одно. Например диск или процессор, изредка в память. Поэтому вертикальное масштабирование обходится дешевле.
HBM>тут мы упремся либо в ограничения по железу, либо в софтину, которая просто не сможет загрузить всю железку и будет продолжать тормозить
Если упремся в то, что наращивать ресурсы одного сервера нельзя или слишком дорого, тогда и можно рассмотреть горизонтальное масштабирование. Не раньше.
Если упираемся в софт, то надо переделывать софт или брать другой.
G>>Кроме того при горизонтальном масштабировании растут издержки на запуск системного ПО. ОС всегда кушает часть ресурсов и чем больше у вас компов, тем больше кушает ОС. HBM>ось можно всегда подтюнить, и она будет кушать почти незначительную часть ресурсов. я бы вообще это не брал в расчет.
Дело не только в оси. У любого экземпляра приложения есть фиксированные накладные расходы.
G>>На практике же цена на железо растет медленнее, чем линейно. 16ГБ оп стоят дешевле, чем 2 по 8, диск на ТБ стоит дешевле двух на 500 ГБ. Процессор в 2 раза быстрее не в два раза дороже медленного. G>>Конечно такая зависимость сохраняется не всегда, если мы возьмем масштабы гугла или ФБ, то там вертикальное масштаирование или физически невозможно или стоит сильно больше горизонтального. Но это гугл и ФБ, таких масштабов в жизни мало кто сможет увидеть. А для обычных приложений вертикальное масштабирование выгоднее.
HBM>мы же тут не конкретный пример рассматриваем, а мечтаем, так сказать
Надо не мечтать, а считать.
G>>Почитай про stackoverflow, при их нагрузках они масштабируются вертикально в основном.
HBM>SO в отличии от других просто пытаются максимально выжать из железа. это круто. HBM>но тем не менее их архитектура изначально расчитана на горизонтальное масштабирование.
Прости, в каком месте? Они изначально сделали ASP.NET веб-приложение с базой на SQL Server. Первое очевидно масштабируется горизонтально, второе очевидно нет. Такую же архитектуру имеют чуть менее, чем все веб-приложения.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>Типичное приложение — это CRUD. В простом приложении CRUD это 100% всей работы с базой, в сложном — не менее 60% по моим подсчетам. G>Даже CRUD для ORM не такое простое дело, например в том же NHibernte при этом нужно открывать именно его транзакции обязательно. Иначе текут коннекшены, он их в таком случае не освобождает.
Выкини NHibernate, возьми EF или linq2db, там нет таких проблем.
G>Погодите. Наиболее типичный пример у меня в базе лежит 10 ГБ записей в табличке, мне нужно найти в этой куче удовлетворящие моему критерию умножить поле на два и вставить записи в две другие таблички. Решение с хранимой процедурой очевидно.
Это реальный сценарий? Можешь описать его в терминах пользователя?
Или ты нафантазировал и считаешь, что ради такого стоит весь crud в хранимках делать?
В реальности у тебя будет сценарий такой:
1) Выбор строк по критерию и отображение на экране
2) Вызов этой самой бизнес-логики указанных пользователем строк.
Тут логика в приложении на два порядка лучше отработает.
G>Решение с ORM — нужно вытащить данные из БД, матерелиазовать их в сущности, сделать бизнес логику и поехали опять в базу на вставку. Вот за счет таких переливаний ORM и проигрывает, причем сильно. Но зато бизнес логика не в хранимках и "красиво"
Только в жизни подобный сценарий встречается в OLAP-системах, где вообще нет "приложения".
G>>Код на SQL который более лаконичный и лучше читается не имеет никакого отношения к высокой производительности. G>Ну это был второй аргумент: 1. производительность, 2. локаничность и выразительность. Все таки SQL очень мощный он позволяет вам запросить что вам нужно, а не как извлеч данные.
Ок, напиши запрос, удовлетворяющий следующим условиям:
1) Если пользователь админ, то ему показать все записи
2) Если пользователь не админ, то
— показать ему записи, где пользователь = автор
— или автор записи входит в группу друзей текущего пользователя
— и записи не должны быть скрыты
3) Записи должны быть отфильтрованы по категории, которую выбрал пользователь (id категории — параметр запроса, может быть пусто)
При этом запрос должен быть максимально лаконичным и быстрым.
Здравствуйте, Gattaka, Вы писали:
G>А на клиенте эти изменения как отслеживаются? Я про INotifyPropertyChanged ну там валидация всякая, подсветка красным невалидных полей?
Ну у кода здорового человека оно работает из коробки. У нас для этого используется самописный стек, причём он уже существовал к моменту, когда я пришёл в команду.
Проблема в том, что традиционный биндинг уже не особо актуален из-за моды на веб / rest api. И я очень и очень сомневаюсь, что что-то подобное выйдет в опенсорс, целевой аудитории ноль.
Здравствуйте, gandjustas, Вы писали:
G>Выкини NHibernate, возьми EF или linq2db, там нет таких проблем.
Вы будете смеятся, но мы до этого выкинули EF. Был болезненный переход на NH... У EF тоже куча недостатков, например он не позволяется получить вам чистые доменные объекты. То есть в классах доменных сущностей у вас присутсвуют ссылки на типы из EF, а это не правильно. Я, например, должен свободно использовать доменную сущность на клиенте не потащив туда ORM... Да и запросы у EF просто жесткач некоторые, NH в этом плане получше, хотя тоже косячит.
G>>Погодите. Наиболее типичный пример у меня в базе лежит 10 ГБ записей в табличке, мне нужно найти в этой куче удовлетворящие моему критерию умножить поле на два и вставить записи в две другие таблички. Решение с хранимой процедурой очевидно. G>Это реальный сценарий? Можешь описать его в терминах пользователя? G>Или ты нафантазировал и считаешь, что ради такого стоит весь crud в хранимках делать?
Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить.
Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId)
В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
G>>>Код на SQL который более лаконичный и лучше читается не имеет никакого отношения к высокой производительности. G>>Ну это был второй аргумент: 1. производительность, 2. локаничность и выразительность. Все таки SQL очень мощный он позволяет вам запросить что вам нужно, а не как извлеч данные. G>Ок, напиши запрос, удовлетворяющий следующим условиям: G>1) Если пользователь админ, то ему показать все записи G>2) Если пользователь не админ, то G> — показать ему записи, где пользователь = автор G> — или автор записи входит в группу друзей текущего пользователя G> — и записи не должны быть скрыты G>3) Записи должны быть отфильтрованы по категории, которую выбрал пользователь (id категории — параметр запроса, может быть пусто)
G>При этом запрос должен быть максимально лаконичным и быстрым.
Я считаю — прекрасно, практически на естественном языке
select r.id, r.name
from rows as r
where r.IsHidden = false and
(exists(select * from Administrators a where a.User = r.User) or
exist(select * from UserFriends uf where uf.User = r.User and r.Friend = @currentUser ))
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Gattaka, Вы писали:
S>Ну у кода здорового человека оно работает из коробки. У нас для этого используется самописный стек, причём он уже существовал к моменту, когда я пришёл в команду.
То есть логика валидации на клиенте и на сервере у вас продублирована? На сервере это ОРМ с ее change tracking, а на клиенте это самописный стек, написанный к моменту когда вы пришли в команду? Окккккей...
G>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
очень похоже на единоразовую задачу, которая решается при помощни миграции. а там пофик что да как. будет обычный SQL.
Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
G>>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
HBM>очень похоже на единоразовую задачу, которая решается при помощни миграции. а там пофик что да как. будет обычный SQL.
Что за единоворазовая задача. Признак добавлятеся — удалятся, в этом суть администрирования и системы. Соответсвенно вместе с признаком и связи ходят... Это требование бизнеса.
Здравствуйте, Gattaka, Вы писали:
HBM>>кто мешает вытащить только те данные, которые нужно и не тащить лишних? G>ОРМ мешает. У меня, допустим замеплина таблица на сущность, а из этой сущности нужно пару столбцов для отчета. ОРМ всю сущность вытащит и дальше вы будете работать с теми полями что вам нужно.
В .NET (EF) можно создать анонимный тип, а котором указать нужные свойства/поля. Запрос вытащит только их.
Например, если есть в EF сущность вида
public class SomeEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public float Code { get; set; }
}
тогда запрос
context.SomeEntities
.Select(x => new { x.Id, x.Code })
.FirstOrDefault();
превратится в
SELECT TOP (1)
[c].[Id] AS [Id],
[c].[Code] AS [Code]
FROM [dbo].[SomeEntities] AS [c]
go
Здравствуйте, gandjustas, Вы писали:
G>Это человек следуя подходу "потом оптимизируем" написал вместо сложного запроса пару foreach циклов. Мало того, что получил select n+1, так еще и код представления ориентировался на объекты модели, каждый из которых требовал поднятия по несколько КБ текста. В итоге навигация строилась полсекунды.
Это не "предварительная оптимизация", т.к. сразу было видно что код не тянет. Да еще "а потом кэш добавим" это вообще откровенное предложение изначально ориентироваться на костыль. Так что это просто оправдание плохого кода мотивом "ну все знают про вред преждевременной оптимизации".
Предлагаю считать что это пример того, что нельзя писать фиговый код, прикрываясь тезисом о преждевременной оптимизации.
Здравствуйте, Gattaka, Вы писали:
S>>Ну у кода здорового человека оно работает из коробки. У нас для этого используется самописный стек, причём он уже существовал к моменту, когда я пришёл в команду. G>То есть логика валидации на клиенте и на сервере у вас продублирована? На сервере это ОРМ с ее change tracking, а на клиенте это самописный стек, написанный к моменту когда вы пришли в команду? Окккккей...
Ну блин, спрашивал про биндинг, делаешь вывод про валидацию
Дублирования нет — переиспользование кода никто не запрещал
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, Gattaka, Вы писали:
Это все здорово, но только разница между таким использованием ОРМ и написанием запросов на SQL сводится к тому какой язык мы используем. Ну по факту C#, т.к. многие не знают толком SQL. Суть ОРМ ведь не в этом. Идея такая, что мы работаем с нашими доменными объектами как обычно, будто базы данных нет. Эти доменные объекты содержакт в себе данные и методы по их обработке, бизнес логику. Если нужно я эти доменные сущности не только на сервере могу использовать, но и на клиенте, да где угодно. Т.к. они не зависят от БД, от ОРМ и чего бы то ни было еще. А вот сохранение и вытаскивание объектов из БД берет на себя ОРМ и если она достаточна умная — все ок.
Здравствуйте, gandjustas, Вы писали:
G>>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторых (приблизительно половины) узлов админисстратором был проставлен признак Prorepry = true. Теперь нужно добавить связи для таких узлов (Для узлов у которых есть зарегестрированные связанные пользователи) . Особенно посчитайте размер таблицы User_User, это приятный момент G>И какую задачу решить надо?
Пофиксил описание и выделил что нужно сделать. Что скажите? Нужно еще пояснить?
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
G>>>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
HBM>>очень похоже на единоразовую задачу, которая решается при помощни миграции. а там пофик что да как. будет обычный SQL. G>Что за единоворазовая задача. Признак добавлятеся — удалятся, в этом суть администрирования и системы. Соответсвенно вместе с признаком и связи ходят... Это требование бизнеса.
Правильно он говорит. Запрос такой будет выполнен один раз и вообще не попадет в код программы.
Ты приведи сценарий, который выполняется из программы и более одного раза.
Здравствуйте, gandjustas, Вы писали:
G>Это так и есть. Но это хорошо видно в ретроспективе. G>А как ты докажешь в каждом конкретном случае, что это просто фиговый код, а не попытка "преждевременной оптимизации", не зная к каким последствиям это приведет ?
G>Даже в этой теме высказываются мнения, что можно написать абы-какой sql, а потом "оптимизировать". Хотя в большинстве случаев "оптимизировать" нельзя, надо переписывать, причем сильно.
Никак кроме как техлид с достаточным опытом наступания на грабли...
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>Это так и есть. Но это хорошо видно в ретроспективе. G>>А как ты докажешь в каждом конкретном случае, что это просто фиговый код, а не попытка "преждевременной оптимизации", не зная к каким последствиям это приведет ?
G>>Даже в этой теме высказываются мнения, что можно написать абы-какой sql, а потом "оптимизировать". Хотя в большинстве случаев "оптимизировать" нельзя, надо переписывать, причем сильно.
G>Никак кроме как техлид с достаточным опытом наступания на грабли...
То есть исключительно субъективное мнение?
Тогда считайте, что у меня достаточный опыт и я говорю, что SQL всегда надо делать хорошо, а не "оптимизировать потом".
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторых (приблизительно половины) узлов админисстратором был проставлен признак Prorepry = true. Теперь нужно добавить связи для таких узлов (Для узлов у которых есть зарегестрированные связанные пользователи) . Особенно посчитайте размер таблицы User_User, это приятный момент G>>И какую задачу решить надо?
G>Пофиксил описание и выделил что нужно сделать. Что скажите? Нужно еще пояснить?
Скажу что админ откроет консоль, напишет запрос, выполнит и забудет. Не будет ни процедуры, ни ORM.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали:
G>>Здравствуйте, HeBpuMHeCkaTuHa, Вы писали:
G>>>>Не верите. Окей, вот вам реальный бизнес сценарий. Есть компьютерная сеть в которой есть сетевые узлы и пользователи. Они имеют связи между собой, мы это все храним в базе и даем возможность через наше приложение админить. G>>>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>>>В нашей базе 70000 узлов по одному пользователю на узле (для простоты). Пользователи связаны все со всеми. Для некоторый (приблизительно половины) узлов был проставлен признак Prorepry — теперь нужно добавить для таких узлов на которых есть зарегестрированные связанные пользователи . Особенно посчитайте размер таблицы User_User, это приятный момент
G>Ты приведи сценарий, который выполняется из программы и более одного раза.
Нет не правильно он говорит. У вас есть софт по администрированию сети. Суть софта выставлять вот такие вот признаки на узлах. Вы как администратор их периодически выставляете и снимаете у разных узлов.
Я максимально упростил вам задачу. На самом деле это не булевый признак и т.д. и т.п.
Здравствуйте, gandjustas, Вы писали:
G>Скажу что админ откроет консоль, напишет запрос, выполнит и забудет. Не будет ни процедуры, ни ORM.
Какую консоль? Он о существовании БД не подозревает.
Здравствуйте, Gattaka, Вы писали:
G>Коллеги, G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах? Либо сейчас модно использовать кодогенераторы типа ОРМ, которые генерируют ужасные sql запросы? Учитывая, что код на sql как правило более локаничный и лучше читается.
Конечно можно, потому что ни хранимки, ни ORM не имеют никакого отношения к бизнес-логике. Они решают задачи хранения данных программы между её запусками.
Многие и не подозревают, что в программах, которые они пишут, нет никакой бизнес-логики. Пользователи хотят вводить данные и потом их искать, вот и вся логика. Это нормально, главное не тратить время на призрачную бизнес-логику — она там и не нужна.
Если программа без БД не имеет смысла, то это просто альтернативный интерфейс к БД.
Здравствуйте, Gattaka, Вы писали:
G>>>>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>Нет не правильно он говорит. У вас есть софт по администрированию сети. Суть софта выставлять вот такие вот признаки на узлах. Вы как администратор их периодически выставляете и снимаете у разных узлов. G>Я максимально упростил вам задачу. На самом деле это не булевый признак и т.д. и т.п.
Давай подробный сценарий с точки зрения пользователя.
Админ заходит, выбирает node, а дальше что?
Здравствуйте, Gattaka, Вы писали:
G>Это все здорово, но только разница между таким использованием ОРМ и написанием запросов на SQL сводится к тому какой язык мы используем. Ну по факту C#, т.к. многие не знают толком SQL. Суть ОРМ ведь не в этом. Идея такая, что мы работаем с нашими доменными объектами как обычно, будто базы данных нет. Эти доменные объекты содержакт в себе данные и методы по их обработке, бизнес логику. Если нужно я эти доменные сущности не только на сервере могу использовать, но и на клиенте, да где угодно. Т.к. они не зависят от БД, от ОРМ и чего бы то ни было еще.
А кто сказал что в хранилище должны храниться сущности BL именно в виде объектов BL? У нас есть DataAccess который возвращает объекты BL, а как там внутри DA — это проблемы DA.
G>А вот сохранение и вытаскивание объектов из БД берет на себя ОРМ и если она достаточна умная — все ок.
Ну вот тут не ясно. А как она должна догадаться что вы хотите только 2 поля из объекта. Вы как-то сами себя загоняете в круг "хочу чтобы ORM сама все вытаскивала — ORM плохая потому что вытаскивает все".
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали:
G>>>>>>Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId) G>>Нет не правильно он говорит. У вас есть софт по администрированию сети. Суть софта выставлять вот такие вот признаки на узлах. Вы как администратор их периодически выставляете и снимаете у разных узлов. G>>Я максимально упростил вам задачу. На самом деле это не булевый признак и т.д. и т.п. G>Давай подробный сценарий с точки зрения пользователя. G>Админ заходит, выбирает node, а дальше что?
Только вы учитывайте, что я вам из 140 таблиц и кучи бизнес-правил сделал упрощенный сценарий для простоты объяснения.
Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей.
Роль это некоторое свойство узла. Их существует порядка 1000 ранзых.
Здравствуйте, gandjustas, Вы писали:
G>Тогда считайте, что у меня достаточный опыт и я говорю, что SQL всегда надо делать хорошо, а не "оптимизировать потом".
Теперь учтем что "хорошо" тоже субъективное понятие. Далее тоже все субъективно — каждый программист IMHO должен стараться делать хорошо, соблюдая при этом баланс качества решения и времени, затраченного на него. Тут скажется и опыт, и "соображалка" и т.д. А разница результатах, будет тем, что отличает новичка от специалиста (и их оплату).
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, Gattaka, Вы писали:
G>>Это все здорово, но только разница между таким использованием ОРМ и написанием запросов на SQL сводится к тому какой язык мы используем. Ну по факту C#, т.к. многие не знают толком SQL. Суть ОРМ ведь не в этом. Идея такая, что мы работаем с нашими доменными объектами как обычно, будто базы данных нет. Эти доменные объекты содержакт в себе данные и методы по их обработке, бизнес логику. Если нужно я эти доменные сущности не только на сервере могу использовать, но и на клиенте, да где угодно. Т.к. они не зависят от БД, от ОРМ и чего бы то ни было еще.
Doc>А кто сказал что в хранилище должны храниться сущности BL именно в виде объектов BL? У нас есть DataAccess который возвращает объекты BL, а как там внутри DA — это проблемы DA.
Действительно, кто? Я не говорил... В БД может хранится как угодно, смысл ОРМ в том чтобы замепить доменную сущность на таблицу или таблицы не доставляя головной боли.
G>>А вот сохранение и вытаскивание объектов из БД берет на себя ОРМ и если она достаточна умная — все ок.
Doc>Ну вот тут не ясно. А как она должна догадаться что вы хотите только 2 поля из объекта. Вы как-то сами себя загоняете в круг "хочу чтобы ORM сама все вытаскивала — ORM плохая потому что вытаскивает все".
Вот собственно несостоятельность этой идеалистической картины я и пытаюсь донести. Причем идею работы с доменными объектами без привязки к БД я одобряю. Она здравая и интересная, но не реализованная еще...
И есть еще подход типа Dapper или linq2db — это вобще не ORM, это всего лишь способ писать на C# запросы к базе данных. Эдакий SQL без SQL... По мне так лучше сразу писать на SQL и не парится, к чему эта прослойка, которая лишь добавляет сложности, как правило не все умеет. Аргумент интеллисенс и упрощенного рефакторинга ИМХО того не оправдывает...
Здравствуйте, Gattaka, Вы писали:
G> G>Давай подробный сценарий с точки зрения пользователя. G> G>Админ заходит, выбирает node, а дальше что?
G> Только вы учитывайте, что я вам из 140 таблиц и кучи бизнес-правил сделал упрощенный сценарий для простоты объяснения. G> Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей. G> Роль это некоторое свойство узла. Их существует порядка 1000 ранзых.
Здравствуйте, Gattaka, Вы писали:
Doc>>А кто сказал что в хранилище должны храниться сущности BL именно в виде объектов BL? У нас есть DataAccess который возвращает объекты BL, а как там внутри DA — это проблемы DA. G>Действительно, кто? Я не говорил... В БД может хранится как угодно, смысл ОРМ в том чтобы замепить доменную сущность на таблицу или таблицы не доставляя головной боли.
А чем не угодили, например, анонимные типы?
Собственно, с точки зрения кода BL я не вижу разницы между запросом на LINQ и вызовом хранимки. В итоге вы получите некий объект с 2мя полями (или их массив). Что дальше? Куда вы поместите результат?
G>И есть еще подход типа Dapper или linq2db — это вобще не ORM, это всего лишь способ писать на C# запросы к базе данных. Эдакий SQL без SQL... По мне так лучше сразу писать на SQL и не парится, к чему эта прослойка, которая лишь добавляет сложности, как правило не все умеет. Аргумент интеллисенс и упрощенного рефакторинга ИМХО того не оправдывает...
Ну смотря кому и какой проект. Intellisense, рефакторинг, возможности написания относительно простых запросов C# программистом без привлечения SQL программиста, выше скорость разработки... Но разумеется все зависит от команды.
Здравствуйте, Dziman, Вы писали:
D>Пока звучит как бездумная денормализация.
Ну я выше приводил уже список таблиц. Продублирую: "Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId)"
Что здесь бездумно денормализовано и как бы вы нормализовали? Какой у вас получился бы список таблиц?
Здравствуйте, Doc, Вы писали:
Doc>А чем не угодили, например, анонимные типы?
А в рамках предметной области анонимные типы это что? Они по определению отсутсвуют в доменном языке. Doc>Собственно, с точки зрения кода BL я не вижу разницы между запросом на LINQ и вызовом хранимки. В итоге вы получите некий объект с 2мя полями (или их массив). Что дальше? Куда вы поместите результат?
В доменный объект, я ведь доменными сущностями оперирую. Ровно как и заказчики с аналитиком.
Здравствуйте, Gattaka, Вы писали:
G> D>Пока звучит как бездумная денормализация.
G> Ну я выше приводил уже список таблиц. Продублирую: "Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId)" G> Что здесь бездумно денормализовано и как бы вы нормализовали? Какой у вас получился бы список таблиц?
Начнем с того что в твоем описании фигурируют какие-то роли
Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей.
Далее, property у нода и пользователя-это что за зверь? Для каждого свойства своя колонка? Они пересекаются между user, node? Как могут существать 2 таблицы Network_Node? Что такое User_User, UserOnNode(а почему тут вдруг отказались от underscore нейминга?)? А как запрещаются связи? И еще 100500 вопросов и потенциальных ответов на них из которых я сделал вывод что тут присутствует денормализация(вероятно не к месту) и как следствие попытки добиться консистентности данных через ХП.
Здравствуйте, Gattaka, Вы писали:
Doc>>Собственно, с точки зрения кода BL я не вижу разницы между запросом на LINQ и вызовом хранимки. В итоге вы получите некий объект с 2мя полями (или их массив). Что дальше? Куда вы поместите результат? G>В доменный объект, я ведь доменными сущностями оперирую. Ровно как и заказчики с аналитиком.
Т.е. на выходе у вас получаются объекты, у которых какие-то несколько свойств несут реальные значения, а остальные дефолтные? Мне кажется это неправильное состояние объекта.
Ведь как потом угадать что в свойстве лежит: реальное значение или дефолтное? Ну можно сделать флаги или ввести предопределённые значения вне диапазонов или сделать все свойства Nullable. Но ведь все это приведет к раздуванию кода BL еще больше, чем введение доп. сущностей.
Здравствуйте, Dziman, Вы писали:
D>Здравствуйте, Gattaka, Вы писали:
G>> D>Пока звучит как бездумная денормализация.
G>> Ну я выше приводил уже список таблиц. Продублирую: "Таблицы User(Id, Name, Property), Network_Node(Id, Name, Property), User_User(User1Id, User2Id), Network_Node(Node1Id, Node2Id), UserOnNode(NodeId, UserId)" G>> Что здесь бездумно денормализовано и как бы вы нормализовали? Какой у вас получился бы список таблиц?
D>Начнем с того что в твоем описании фигурируют какие-то роли D>
Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей.
D>Далее, property у нода и пользователя-это что за зверь? Для каждого свойства своя колонка? Они пересекаются между user, node? Как могут существать 2 таблицы Network_Node? Что такое User_User, UserOnNode(а почему тут вдруг отказались от underscore нейминга?)? А как запрещаются связи? И еще 100500 вопросов и потенциальных ответов на них из которых я сделал вывод что тут присутствует денормализация(вероятно не к месту) и как следствие попытки добиться консистентности данных через ХП.
Сначала я роли не вводил, ввел просто флаг property — считайте что это и есть роли. То есть свойство Property типа int и выставление туда значения — это есть назначение роли на узел. Таблицу User упростим до User(Id, Name).
Очевдно, что вторая таблица называется не Network_Node, а Node_Node. User_User — таблица связей пользователей, я не знаю а вы что подумали? Отказались от underscore нейминга потому что это нифига не важно сейчас. Мы ведь тестовую ситуацию разбираем.
Теперь еще раз что требуется сделать. Т.к. уже много раз переписывалось. В таблице User(Id, Name) 70000 записей. В таблице Network_Node(Id, Name, Property) 70000 записей. В Таблице UserOnNode(NodeId, UserId) — 70000 записей по одному пользователю на узле. В таблице User_User(User1Id, User2Id) — связи пользователей, все польователи связаны со всеми — это 70000*70000=4900000000 записей. На каждую из записей по 8 байт, т.е. приблизительно 36 гигабайт табличка, не учитывая индексов. Теперь в таблице Network_Node(Node1Id, Node2Id) — связи узлов. Допустим кто-то с кем-то уже был связан в случайном порядке 900000000 записей каких-то.
Нам нужно сделать: У узлов (35000 каких-то) Network_Node меняется свойство Property — бизнес логика такова, что нужно найти зарегестрированные на этих узлах пользовати и если они связаны — добавить связи по узлам.
На самом деле у связи тоже есть свойства, но давайте не будем сейчас усложнять. Вот задача просто такая как я выше описал сейчас.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали:
G>>Итак, админ запускает приложение. Выбирает список узлов, правой кнопкой — назначить роль. Роль назначается на узлы, а также если на узле есть зарегестрированные пользователи (их может быть несколько, предположим что один) и если эти пользователи имеют связи между собой — нужно установить связи между сетевыми узлами, только если эти связи не были запрещены админом до этого, если нет запретов со стороны других ролей и эти связи еще не существуют. Плюс у связи может быть статус, но это опустим — нужно назначать в только для определенных статусов связей.
G>Проблема в том, чтобы получить транзитивное замыкание? Тогда и процедура не сильно поможет. Лучше замыкание делать не в момент запроса, а строить заранее.
Нет, я попробовал еще раз описать исходя из вопросов которые возникают: http://rsdn.ru/forum/design/6482289.1
Здравствуйте, Artem Korneev, Вы писали:
G>>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах?
AK>Мне однажды пришлось поддерживать такой проект. AK>Я не припомню чтобы за всю мою карьеру мне попадался другой проект, который было бы настолько же сложно поддерживать.
А у тебя была возможность сравнить с таким же проектом, но с логикой на клиенте? Может там было бы еще хуже.
AK>Основная сложность поддержки того проекта была именно в хранимых процедурах, дело усугублялось тем, что процедуры писались профессиональным SQL-разработчиком, а поддерживалось это потом C# full-stack разработчиками, которым было непросто читать многостраничные cross-DB запросы с кучей JOIN'ов, CROSS-APPLY, MERGE и прочими прелестями.
Это не проблема подхода, это проблема управления проектом. Был профессиональный SQL-разработчик, потом на нем решили сэкономить. Вот и результат.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали: G>>>Проблема в том, чтобы получить транзитивное замыкание? Тогда и процедура не сильно поможет. Лучше замыкание делать не в момент запроса, а строить заранее. G>>Нет, я попробовал еще раз описать исходя из вопросов которые возникают: http://rsdn.ru/forum/design/6482289.1
G>>Проблема в том, что данных дофига — 36 ГБ и проекция вам не поможет.
G>Проблема в том, что ты проблему не можешь объяснить, ты уже четвертый пост ходишь вокруг и даже запрос не привел и user flow объяснить не можешь нормально. У тебя есть только отмазка "данных много".
Ну раз так много человек плюсуют... Я вобще расчитывал что вы сами напишите этот запрос. Так было бы гораздо интереснее (тут без сарказма, действительно считаю что так было бы интереснее вам)
Вот запрос:
-- 1. Простовляем списку узлов новую рольupdate Network_Node
set Property = true -- Некий признак, флаг, роль и т.п. не вдаемся в подробности. Нам пока не важно.where Id in (select Id from @selectedNodes) --В хранимку приходит список идентификаторов выбранных пользователем узлов - табличная переменная @selectedNodes
-- 2. В соответсвии с бизнес логикой нужно найти связать сетевые узлы, которым мы проставили роль по связям зарегестрированных на них пользователей
-- insert Node_Node(Node1Id, Node2Id)
select distinct n.Id, un2.NodeId
from Network_Node n
join UserOnNode un1 on un1.NodeId = n.Id -- Берем пользователей зарегестрированных на узлеjoin User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователяjoin UserOnNode un2 on un1.UserId = uu.User2Id -- Берем узлы на котором зарегестрирован связанный пользовательwhere n.Property = true
and not exists(select * from Node_Node nn3 where nn3.Node1Id = n.Id and nn3.Node2Id = un2.NodeId) -- такая связь еще не сущетвует
Итак, напомню
В таблице User(Id, Name) 70000 записей. В таблице Network_Node(Id, Name, Property) 70000 записей. В Таблице UserOnNode(NodeId, UserId) — 70000 записей по одному пользователю на узле. В таблице User_User(User1Id, User2Id) — связи пользователей, все польователи связаны со всеми — это 70000*70000=4900000000 записей. На каждую из записей по 8 байт, т.е. приблизительно 36 гигабайт табличка, не учитывая индексов. Теперь в таблице Network_Node(Node1Id, Node2Id) — связи узлов. Допустим кто-то с кем-то уже был связан в случайном порядке 900000000 записей каких-то.
Нам нужно сделать: У узлов (35000 каких-то) Network_Node меняется свойство Property — бизнес логика такова, что нужно найти зарегестрированные на этих узлах пользовати и если они связаны — добавить связи по узлам.
G> -- 1. Простовляем списку узлов новую роль
G> update Network_Node
G> set Property = true -- Некий признак, флаг, роль и т.п. не вдаемся в подробности. Нам пока не важно.
G> where Id in (select Id from @selectedNodes) --В хранимку приходит список идентификаторов выбранных пользователем узлов - табличная переменная @selectedNodes
G> -- 2. В соответсвии с бизнес логикой нужно найти связать сетевые узлы, которым мы проставили роль по связям зарегестрированных на них пользователей
G> --
G> insert Node_Node(Node1Id, Node2Id)
G> select distinct n.Id, un2.NodeId
G> from Network_Node n
G> join UserOnNode un1 on un1.NodeId = n.Id -- Берем пользователей зарегестрированных на узле
G> join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G> join UserOnNode un2 on un1.UserId = uu.User2Id -- Берем узлы на котором зарегестрирован связанный пользователь
G> where n.Property = true
G> and not exists(select * from Node_Node nn3 where nn3.Node1Id = n.Id and nn3.Node2Id = un2.NodeId) -- такая связь еще не сущетвует
G>
Кстати в запросе ошибка, смотри третий джоин
G>Итак, напомню G>
G>В таблице User(Id, Name) 70000 записей. В таблице Network_Node(Id, Name, Property) 70000 записей. В Таблице UserOnNode(NodeId, UserId) — 70000 записей по одному пользователю на узле. В таблице User_User(User1Id, User2Id) — связи пользователей, все польователи связаны со всеми — это 70000*70000=4900000000 записей. На каждую из записей по 8 байт, т.е. приблизительно 36 гигабайт табличка, не учитывая индексов. Теперь в таблице Network_Node(Node1Id, Node2Id) — связи узлов. Допустим кто-то с кем-то уже был связан в случайном порядке 900000000 записей каких-то.
G>Нам нужно сделать: У узлов (35000 каких-то) Network_Node меняется свойство Property — бизнес логика такова, что нужно найти зарегестрированные на этих узлах пользовати и если они связаны — добавить связи по узлам.
Если все пользователи связаны со всеми, то установка property на любом узле приведет к добавлению Node_Node всех узлов в поле Node2Id. Что для дальнейшего использования не имеет смысла, так как просто можно запрашивать n.Property вместо джоина к Node_Node.
А если ты приведешь реальные средние значения количества связей User_Node и User_User, то перемножив два числа получим количество считываемых\добавляемых строк. Например для User_Node примерное соотношение 10, а для User_User — 150, то надо будет получить максимум 1500 записей для каждого ID. Размер записи — 8 байт (два ID по 4 байта). То есть 12кб. Если это операция обновления, которая не выполняется 10 раз в секунду, то можно не беспокоиться о скорости.
А вообще я бы лучше сделал материализованную view вида
create view UserNodeJunction with schemabinding as
select
un1.NodeId as Node1Id,
un1.UserId as User1Id,
un2.NodeId as Node2Id,
un2.UserId as User2Id
from UserOnNode un1
join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователяjoin UserOnNode un2 on un2.UserId = uu.User2Id
create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
Тогда твой запрос вообще можно было бы во view превратить:
create view NodeWithProperty as
select distinct n.Id, j.Node2Id
from Network_Node n
join UserNodeJunction j on n.Id = j.Node1Id
where n.Property = true
И если это view индексировать (вытащив distinct на уровень выше), то вовсе пропадет необходимость что-то куда-то вставлять.
ЗЫ. Тем не менее я понял твою идею — тяжелую логику держать на уровне SQL, а не тащит в приложение. И я с этим согласен. Но средства ты кардинально не те выбрал, как на уровне базы, так и на уровне приложения. Поэтому ничего толком обосновать не можешь.
G>Здравствуйте, Gattaka, Вы писали: G>>Вот запрос: G>
Запрос
G>>
G>> -- 1. Простовляем списку узлов новую роль
G>> update Network_Node
G>> set Property = true -- Некий признак, флаг, роль и т.п. не вдаемся в подробности. Нам пока не важно.
G>> where Id in (select Id from @selectedNodes) --В хранимку приходит список идентификаторов выбранных пользователем узлов - табличная переменная @selectedNodes
G>> -- 2. В соответсвии с бизнес логикой нужно найти связать сетевые узлы, которым мы проставили роль по связям зарегестрированных на них пользователей
G>> --
G>> insert Node_Node(Node1Id, Node2Id)
G>> select distinct n.Id, un2.NodeId
G>> from Network_Node n
G>> join UserOnNode un1 on un1.NodeId = n.Id -- Берем пользователей зарегестрированных на узле
G>> join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>> join UserOnNode un2 on un1.UserId = uu.User2Id -- Берем узлы на котором зарегестрирован связанный пользователь
G>> where n.Property = true
G>> and not exists(select * from Node_Node nn3 where nn3.Node1Id = n.Id and nn3.Node2Id = un2.NodeId) -- такая связь еще не сущетвует
G>>
G>Кстати в запросе ошибка, смотри третий джоин
G>>Итак, напомню G>>
G>>В таблице User(Id, Name) 70000 записей. В таблице Network_Node(Id, Name, Property) 70000 записей. В Таблице UserOnNode(NodeId, UserId) — 70000 записей по одному пользователю на узле. В таблице User_User(User1Id, User2Id) — связи пользователей, все польователи связаны со всеми — это 70000*70000=4900000000 записей. На каждую из записей по 8 байт, т.е. приблизительно 36 гигабайт табличка, не учитывая индексов. Теперь в таблице Network_Node(Node1Id, Node2Id) — связи узлов. Допустим кто-то с кем-то уже был связан в случайном порядке 900000000 записей каких-то.
G>>Нам нужно сделать: У узлов (35000 каких-то) Network_Node меняется свойство Property — бизнес логика такова, что нужно найти зарегестрированные на этих узлах пользовати и если они связаны — добавить связи по узлам.
G>Если все пользователи связаны со всеми, то установка property на любом узле приведет к добавлению Node_Node всех узлов в поле Node2Id. Что для дальнейшего использования не имеет смысла, так как просто можно запрашивать n.Property вместо джоина к Node_Node. G>А если ты приведешь реальные средние значения количества связей User_Node и User_User, то перемножив два числа получим количество считываемых\добавляемых строк. Например для User_Node примерное соотношение 10, а для User_User — 150, то надо будет получить максимум 1500 записей для каждого ID. Размер записи — 8 байт (два ID по 4 байта). То есть 12кб. Если это операция обновления, которая не выполняется 10 раз в секунду, то можно не беспокоиться о скорости.
Считайте, что все со всеми минус 1000000 каких-то случайных связей между пользователями. Вот такие объемы. Те расчеты, что вы привели — оч. мало. G>А вообще я бы лучше сделал материализованную view вида G>
G>create view UserNodeJunction with schemabinding as
G>select
G> un1.NodeId as Node1Id,
G> un1.UserId as User1Id,
G> un2.NodeId as Node2Id,
G> un2.UserId as User2Id
G>from UserOnNode un1
G>join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>join UserOnNode un2 on un2.UserId = uu.User2Id
Ерунда :(, будет тормозить добавление и удаление связей.
G>create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
G>create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
G>
G>Тогда твой запрос вообще можно было бы во view превратить: G>
G>create view NodeWithProperty as
G>select distinct n.Id, j.Node2Id
G>from Network_Node n
G>join UserNodeJunction j on n.Id = j.Node1Id
G>where n.Property = true
G>
Так джойн и так быстро делается, только при вставке связей вы получите дополнительные расходы. Плюс размер базы разратется в 2 раза. Если сейчас это 36 ГБ табличка — будет 72 ГБ. G>И если это view индексировать (вытащив distinct на уровень выше), то вовсе пропадет необходимость что-то куда-то вставлять.
Нет, я вам просто привел упрощенный пример. Вставлять нужно железно, от этого к сожалению никуда не уйти. Было бы супер не вставлять, но печаль... Во-первых тут не одно свойство может приводить к такому, во вторых даже если в результате бага связи попали — исчизать не должны. И масса еще другого... G>ЗЫ. Тем не менее я понял твою идею — тяжелую логику держать на уровне SQL, а не тащит в приложение. И я с этим согласен. Но средства ты кардинально не те выбрал, как на уровне базы, так и на уровне приложения. Поэтому ничего толком обосновать не можешь.
Ну супер! На самом деле я ожидал что вы dapper предложете или linq2db, но тоже не катит, т.к. работу с блокировками в многопользовательском режиме лучше возложить на SQL, в частности READPAST и т.п. подобные штуки понадобятся...
Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
Здравствуйте, Gattaka, Вы писали:
G>>Если все пользователи связаны со всеми, то установка property на любом узле приведет к добавлению Node_Node всех узлов в поле Node2Id. Что для дальнейшего использования не имеет смысла, так как просто можно запрашивать n.Property вместо джоина к Node_Node.
G>>А если ты приведешь реальные средние значения количества связей User_Node и User_User, то перемножив два числа получим количество считываемых\добавляемых строк. Например для User_Node примерное соотношение 10, а для User_User — 150, то надо будет получить максимум 1500 записей для каждого ID. Размер записи — 8 байт (два ID по 4 байта). То есть 12кб. Если это операция обновления, которая не выполняется 10 раз в секунду, то можно не беспокоиться о скорости. G>Считайте, что все со всеми минус 1000000 каких-то случайных связей между пользователями. Вот такие объемы. Те расчеты, что вы привели — оч. мало.
Тогда нужно логику разворачивать — сохранять каких связей НЕТ. Тогда просто два селекта и except. И добавить\удалить надо будет несколько строк.
G>>А вообще я бы лучше сделал материализованную view вида
G>>
G>>create view UserNodeJunction with schemabinding as
G>>select
G>> un1.NodeId as Node1Id,
G>> un1.UserId as User1Id,
G>> un2.NodeId as Node2Id,
G>> un2.UserId as User2Id
G>>from UserOnNode un1
G>>join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>>join UserOnNode un2 on un2.UserId = uu.User2Id
G>>create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
G>>create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
G>>
G>Ерунда , будет тормозить добавление и удаление связей.
Я не вижу в этом проблемы, ты же и так предлагаешь писать по 70 000 строк на клик мышкой, я предлагаю то же самое.
Добавление связей как часто происходит?
G>>Тогда твой запрос вообще можно было бы во view превратить: G>>
G>>create view NodeWithProperty as
G>>select distinct n.Id, j.Node2Id
G>>from Network_Node n
G>>join UserNodeJunction j on n.Id = j.Node1Id
G>>where n.Property = true
G>>
G>Так джойн и так быстро делается, только при вставке связей вы получите дополнительные расходы.
Ты что-то не договариваешь. У тебя в 4 900 000 000 в таблице User_User и 70 000 в UserOnNode. То есть внутренний джоин дает 2e19 записей, которые потом надо отсортировать, чтобы сделать distinct. На это не хватит памяти никакого сервера. Так что не может этот джоин быстро работать ни при каких раскладах.
Поэтому indexed view выгоднее, хранятся уже отсортированные значения в индексе и distinct будет быстрее.
G>Плюс размер базы разратется в 2 раза. Если сейчас это 36 ГБ табличка — будет 72 ГБ.
И? Важно не то, сколько ты хранишь, а сколько читаешь. Ведь 98% операция чтения.
G>>И если это view индексировать (вытащив distinct на уровень выше), то вовсе пропадет необходимость что-то куда-то вставлять.
G>Нет, я вам просто привел упрощенный пример. Вставлять нужно железно, от этого к сожалению никуда не уйти.
Ты так и не объяснил почему, проблема выглядит надуманной.
G>Было бы супер не вставлять, но печаль... Во-первых тут не одно свойство может приводить к такому, во вторых даже если в результате бага связи попали — исчизать не должны. И масса еще другого...
Как за с материализованными view было бы проще, там меньше чего может сломаться.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, Vladek, Вы писали:
G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
Какбы некорректно рассматривать недостатки без преимуществ.
Также некорректно рассматривать фичи без сценариев использования.
Я вот даже представить не могу зачем readpast может понадобиться.
G>>>create view UserNodeJunction with schemabinding as
G>>>select
G>>> un1.NodeId as Node1Id,
G>>> un1.UserId as User1Id,
G>>> un2.NodeId as Node2Id,
G>>> un2.UserId as User2Id
G>>>from UserOnNode un1
G>>>join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G>>>join UserOnNode un2 on un2.UserId = uu.User2Id
G>>>create clustered index PK_UserNodeJunction on UserNodeJunction (Node1Id, User1Id, Node2Id, User2Id)
G>>>create index IX_NodeLink on UserNodeJunction (Node1Id, Node2Id)
G>>>
я что-то поторопился.
Нужно такой запрос делать:
create view Node_Node with schemabinding as
select
un1.NodeId as Node1Id,
un2.NodeId as Node2Id,
count_big(*) as cnt
from UserOnNode un1
join User_User uu on uu.User1Id = un1.UserId
join UserOnNode un2 on un2.UserId = uu.User2Id
group by un1.NodeId, un2.NodeId
create clustered index PK_UserNodeJunction on Node_Node (Node1Id, Node2Id)
Здравствуйте, Gattaka, Вы писали:
G>Но у большинства разработчиков делаются глаза круглыми при словах Table Spool, Nested Loop, кластерный и некластерный индекс чем отличаются.
Про кластерный индекс я знаю, про остальное нет.
G>И не знание этих простых вещей [...]
Ваш вопрос был о минусах переноса логики в хранимые процедуры. Я вам привёл пример минуса — "обычным" fullstack-разработчикам трудно поддерживать такой код. Нужны будут люди с хорошим знанием SQL.
Юниттестов для SQL-кода в том проекте, разумеется, не было.
Здравствуйте, wildwind, Вы писали:
W>А у тебя была возможность сравнить с таким же проектом, но с логикой на клиенте? Может там было бы еще хуже.
Нет, других подобных проектов я пока не видел. Вполне может быть что было бы и ещё хуже.
W>Это не проблема подхода, это проблема управления проектом. Был профессиональный SQL-разработчик, потом на нем решили сэкономить. Вот и результат.
Может и так. Не знаю. Я озвучил один из минусов такого подхода — для поддержки проекта потребуется профессиональный SQL-разработчик, у обычных C#-разработчиков поддержка такого проекта займёт куда больше времени и усилий.
Здравствуйте, gandjustas, Вы писали:
G>>Проблема в том, что данных дофига — 36 ГБ и проекция вам не поможет. G>Проблема в том, что ты проблему не можешь объяснить, ты уже четвертый пост ходишь вокруг и даже запрос не привел и user flow объяснить не можешь нормально. У тебя есть только отмазка "данных много".
36 ГБ это не много. Это ерунда. На моём текущем проекте столько данных генерируется дня за 3-4. При этом LINQ используется тотально.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали:
G>Ну супер! На самом деле я ожидал что вы dapper предложете или linq2db, но тоже не катит, т.к. работу с блокировками в многопользовательском режиме лучше возложить на SQL, в частности READPAST и т.п. подобные штуки понадобятся...
linq2db вполне себе поддерживает table hints.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали:
G>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ?
from p in db.Parent.With("READPAST")
select p;
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Gattaka, Вы писали: G>Это все здорово, но только разница между таким использованием ОРМ и написанием запросов на SQL сводится к тому какой язык мы используем. Ну по факту C#, т.к. многие не знают толком SQL. Суть ОРМ ведь не в этом. Идея такая, что мы работаем с нашими доменными объектами как обычно, будто базы данных нет.
Вы неправильно поняли идею ОРМ. Вот конкретно эта идея — порочная по самой своей природе. Она гарантирует хреновую производительность и адское неудобство в разработке.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали: G>>Дело в другом, что ОРМ вам дает некотрые удобства по рефакторингу, но при этом много чего заберает. READPAST тот же как сделать? У СУБД куча возможностей и плюшек которые попросту обрезаются... Так может вобще отказаться от плюшек ОРМ? S>То, что вы называете "ОРМ" — это ублюдочное порождение воспалённого воображения теоретиков девяностых. Выбросьте его немедленно! По сравнению с ним даже логика в ХП выглядит прилично. S>Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений.
Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db?
Здравствуйте, Gattaka, Вы писали: S>>Но в 2016 есть ещё более хорошие средства написания логики, чем попытки превратить SQL Server в сервер приложений. G>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db?
Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
G>>Вы про что конкретно? Может быть еще эти варианты не озвучивали? linq2db? S>Ага. Там и с table hint никаких проблем нет, и вообще в целом всё хорошо — нет навязанных услуг типа rich domain model и прочей наркомании.
То есть вы сам подход rich domain model не одобряете? Вроде бы там все стройно и гладко получается — многим нравится в сравнении с анемичной моделью. Лично по мне так, все бы хорошо если бы была хоть одна нормальная ОРМка, а написать такую ОРМ мне даже не представляется возможным... настолько это сложно.
Со всем согласен.
W>Ты имел дело с базами, используемыми несколькими приложениями, да еше написанными на разных языках? Наверняка имел. Там не было ни одной хранимки?
Сейчас вам возразят, что для интеграции приложений шаринг базы данных не самый удачный подход. Лучше либо API аля wcf сервися, а еще лучше message based подход поверх какого-нибудь rabbitmq
Здравствуйте, Gattaka, Вы писали:
G>Сейчас вам возразят, что для интеграции приложений шаринг базы данных не самый удачный подход. Лучше либо API аля wcf сервися, а еще лучше message based подход поверх какого-нибудь rabbitmq
Я говорю не про интеграцию, а про накопленные данные, которые нужны разным приложениям. А другие приложения их пополняют. Против API и message based ничего не имею, но ситуации разные бывают.
Здравствуйте, wildwind, Вы писали:
W>Я говорю не про интеграцию, а про накопленные данные, которые нужны разным приложениям. А другие приложения их пополняют. Против API и message based ничего не имею, но ситуации разные бывают.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали: G>>Сейчас вам возразят, что для интеграции приложений шаринг базы данных не самый удачный подход. S>Конечно. Даже с одним приложением всегда есть риск разъехаться в версиях базы и клиента. А когда у нас N приложений, да ещё и на разных языках, то обязательно нужен App Server между данными и клиентами, который гарантирует консистентность бизнес-логики.
Ну так проблема версий сервера везде существует. Разве не может получится разных версий API и клиентов? Запросто... В БД вы просто делаете таблицу с текущим номером версии схемы и остальные приложения в эту таблицу смотрят. Делов то... В случае с сервером с АPI используется аналогичный подход, так что это не то чем уж так разительно подходы отличаются.
S>В таком сценарии использование ХП — это попытка превратить RDBMS в тот самый апп-сервер. Т.е. клиентам закрывают доступ к таблицам; в лучшем случае даём read-only, хотя даже это адски опасно (потому что всегда есть риск, что мы добавили колоночку типа IsDeleted, а где-то в углу завалялся клиент на дельфи, который ничего про неё не знает, и продолжает хреначить репорты с неуловимо отличающимися от официальных цифр данными). S>Пусть фигачат хранимки.
DML — нужно запрещать... У вас ведь с App сервером тоже никто контракт внезапно поменять не может. Добавить поле в DTO IsDeleted?
S>Тут мы быстро приезжаем в то, что SQL Server — это плохой AppServer. Во-первых, у него неудачный протокол взаимодействия. Он категорически не может работать с недоверенными клиентами — достаточно сделать begin tran, выполнить процедурку, и оставить соединение открытым, чтобы все остальные ушли курить. S>Во-вторых, стандартные ХП написаны в соответствии с парадигмой RPC, неработоспособность которой в сетях уже не обсосал только ленивый. S>Банальная штука — ну вот есть у вас процедура, которая должна помечать узлы (или что вы там рассказывали про 700000 записей в своей задаче?...). S>Вот я её с клиента позвал. А в ответ мне прилетает connection reset by peer. Что-то там в сеточке взглюкнуло. Что мне делать? Транзакция сработала или таки нет? S>Для борьбы с этим 15 лет назад придумали REST, где можно обеспечить идемпотентность. S>Вот вы сходу сможете написать свою процедуру идемпотентной? А как отличить идемпотентную от безопасной, и обе от небезопасной? S>В итоге, написание клиента, который имеет гарантированную семантику, превращается из лёгкого и приятного занятия в титанический труд по оборачиванию вызовов во всяческие стратегии. Либо имеем приложение, которое работает кое-как — то есть периодически его надо перезапускать, и руками/глазами смотреть, что из последних действий надо повторять. S>Чтобы сделать к SQL Server RESTful API на ХП, надо много и упорно трудиться. И всё равно получится не так хорошо, как поверх существующих платформ. Не обязательно WCF — выбор сейчас богатый.
Зато есть преимущества, которые никто больше не может перекрыть. Это скорость. И огромная куча функционала под капотом. Мне, например, нравится пример с коллекцией в памяти, которую куча потоков модифицирует в памяти. Как организоывать блокировки? Для каждой строчки read-write lock, а блокировки на диапозоны, а что насчет эскалации блокировок. В SQL Server этот код уже написан, отлажен и работает максимально эффективно. Если вы будете делать что-то аналогичное, то либо потратите кучу времени и напишите кучу кода, либо получите неэффективное решение.
В случае с SQL Server у вас данные в едином месте и управляются единым кодом — там все максимально эффективно, нет лишних сериализаций, переливаний из сущностей в DTO и т.п.
Здравствуйте, wildwind, Вы писали:
W>Странно слышать столь категоричные утверждения от человека с большим опытом. Хранимки это API к базе, один из видов.
Задача базы данных надёжно хранить данные и обеспечивать к ним быстрый доступ, а не представлять собой кривеньский API безнеслогики.
W>Ты имел дело с базами, используемыми несколькими приложениями, да еше написанными на разных языках? Наверняка имел. Там не было ни одной хранимки?
Были. Уже нету. Для нескольких приложений всегда можно написать общий модуль. Для разнородной системы соответственно какой-ниубдь веб-сервер или сервер приложений.
W>А масштабируемость вот при чем. В процессе масштабирования обязательно наступает период, когда нужно выжать из СУБД максимум, чтобы оттянуть апгрейд железа. Начинается поиск вещей, которые эффективнее выполнить внутри базы, чем гонять данные по сети и растягивать транзакции. И ради эффективности приходится жертвовать читаемостью, сопровождаемостью и еще чем-то из чеклиста "идеальный проект". А хранимки позволяют это смягчить. Если их не использовать, то получишь те же "хранимки", но обфусцированные, хранящиеся в коде и создаваемые каждый раз на время выполнения. Что еще хуже.
Толи в этой, толи в соседней теме я уже говорил, что прекрасно справляюсь с такими задачами без всяких сохранённых процедур. Без затягивания данных на клиента, эффективно внутри базы и без жертв типа читаемость и сопровождаемость.
W>Их как, тоже нельзя?
Это кто будет поддерживать? DBA или девелопер?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Sinclair, Вы писали:
S>Тут мы быстро приезжаем в то, что SQL Server — это плохой AppServer. Во-первых, у него неудачный протокол взаимодействия. Он категорически не может работать с недоверенными клиентами
Стоп, а в какой момент наш клиент вдруг стал недоверенным? Тот же клиент, не использующий хранимки, будет доверенным? Что-то я теряю твою мысль.
S>достаточно сделать begin tran, выполнить процедурку, и оставить соединение открытым, чтобы все остальные ушли курить.
Это и с аппсервером может случиться из-за бага.
S>Вот я её с клиента позвал. А в ответ мне прилетает connection reset by peer. Что-то там в сеточке взглюкнуло. Что мне делать? Транзакция сработала или таки нет?
Если явный коммит не подтвержден, значит не сработала. Есть другие варианты?
И опять же, в чем разница между хранимкой, батчем и серией простых SQL операторов?
Здравствуйте, IT, Вы писали:
W>>Ты имел дело с базами, используемыми несколькими приложениями, да еше написанными на разных языках? Наверняка имел. Там не было ни одной хранимки? IT>Были. Уже нету. Для нескольких приложений всегда можно написать общий модуль. Для разнородной системы соответственно какой-ниубдь веб-сервер или сервер приложений.
Можно. Только стоит ли?
IT>Толи в этой, толи в соседней теме я уже говорил, что прекрасно справляюсь с такими задачами без всяких сохранённых процедур. Без затягивания данных на клиента, эффективно внутри базы и без жертв типа читаемость и сопровождаемость.
Ты, как автор linq2db, не показатель.
IT>Это кто будет поддерживать? DBA или девелопер?
И тот и другой может, по обстоятельствам. А какая разница?
G>Так ли плохо реализовывать бизнес логику в высокопроизводительных хранимых процедурах?
Бизнес-логика имеет свойство часто меняться, и кто должен изменения вносить в ХП?
В итоге все выльется в то, что пользователи будут выгружать плоские таблицы сырых данных простым запросом, типа select * from ..., в Excel и уже в Excel'е выкручивать из них, что им нужно, а хранимки так и останутся никому не нужные.
Здравствуйте, wildwind, Вы писали:
W>Можно. Только стоит ли?
Если единственная альтернатива — ХП, то вне всякого сомнения.
IT>>Толи в этой, толи в соседней теме я уже говорил, что прекрасно справляюсь с такими задачами без всяких сохранённых процедур. Без затягивания данных на клиента, эффективно внутри базы и без жертв типа читаемость и сопровождаемость. W>Ты, как автор linq2db, не показатель.
Я это всё делал за долго до линка.
IT>>Это кто будет поддерживать? DBA или девелопер? W>И тот и другой может, по обстоятельствам.
А отвечать кто будет? Тоже по обстоятельствам?
W>А какая разница?
Разница такая. Если DBA мне не мешает, то пусть занимается своими DBA'скими штучками на чём угодно. Если же поддержкой этого кода буду заниматься я, то никаких ХП.
Если нам не помогут, то мы тоже никого не пощадим.
S>foreach(var employee in Db.GetAllEmployees())
S>{
S> if (employee.CurrentPosition = position)
S> {
S> var c = Db.FindContractByEmployee(employee);
S> c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
S> }
S>}
S>
Подозрительная у вас rich model... В частности вызов метод Db.FindContractByEmployee(employee)
foreach(var employee in Db.GetAllEmployees())
{
if (employee.CurrentPosition = position)
{
var c = employee.Contracts.FirstOrDefault();// Либо Where... все обеспечивается ORM. С linq2db такой код не получится писать я так понимаю. Есть там LazyLoad?
c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
}
}
Здравствуйте, Gattaka, Вы писали:
G>Подозрительная у вас rich model... В частности вызов метод Db.FindContractByEmployee(employee) G>
G>foreach(var employee in Db.GetAllEmployees())
G>{
G> if (employee.CurrentPosition = position)
G> {
G> var c = employee.Contracts.FirstOrDefault();// Либо Where... все обеспечивается ORM. С linq2db такой код не получится писать я так понимаю. Есть там LazyLoad?
G> c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
G> }
G>}
G>
Спасибо за исправление. Lazy Load в linq2db нет, и слава богу. Вы понимаете, чем ужасен этот код?
Если посмотреть на его исполнение под SQL Profiler, то волосы зашевелятся даже там, где их нет.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали:
G>>Подозрительная у вас rich model... В частности вызов метод Db.FindContractByEmployee(employee) G>>
G>>foreach(var employee in Db.GetAllEmployees())
G>>{
G>> if (employee.CurrentPosition = position)
G>> {
G>> var c = employee.Contracts.FirstOrDefault();// Либо Where... все обеспечивается ORM. С linq2db такой код не получится писать я так понимаю. Есть там LazyLoad?
G>> c.AdjustSalary(c.CurrentSalary + c.CurrentSalary * 0.10, effectiveDate);
G>> }
G>>}
G>>
S>Спасибо за исправление. Lazy Load в linq2db нет, и слава богу. Вы понимаете, чем ужасен этот код? S>Если посмотреть на его исполнение под SQL Profiler, то волосы зашевелятся даже там, где их нет.
Ну да... у меня может и шевелятся Но есть сторонники, которые начнут доказывать, что это база данных виновата... Давайте на MongoDB переходить, там тормозов нет.
Здравствуйте, wildwind, Вы писали: W>Стоп, а в какой момент наш клиент вдруг стал недоверенным? Тот же клиент, не использующий хранимки, будет доверенным? Что-то я теряю твою мысль.
Никакому удалённому клиенту доверять нельзя. Доверенным для RDBMS может быть AppServer, потому что разрабатываем и развёртываем его мы, вместе с базой.
Все остальные клиенты — неизвестное зло.
Они будут выполнять delete * from users where 1=1 и прочее, если им это всё не запретить.
S>>достаточно сделать begin tran, выполнить процедурку, и оставить соединение открытым, чтобы все остальные ушли курить. W>Это и с аппсервером может случиться из-за бага.
Из-за бага в клиенте — нет. Мы полностью читаем запрос перед началом его исполнения. Ситуации типа "сидим, курим, ждём клиента" у нас в принципе быть не может (ну, если архитектор апп.сервера не полный идиот).
Если такое случилось в апп-сервере — мы его чиним. Он под нашим контролем.
W>Если явный коммит не подтвержден, значит не сработала. Есть другие варианты?
Конечно есть. Во-первых, мы вызвали процедуру с автокоммитом. Во-вторых, как вы отличите ситуацию "сервер не подтвердил транзакцию" от "клиент не получил отправленное подтверждение"? W>И опять же, в чем разница между хранимкой, батчем и серией простых SQL операторов?
Речь про разницу между SQL и HTTP как протоколами взаимодействия клиент-сервер.
С точки зрения попытки реализовать сервер приложений на SQL хранимки — это практически единственный способ хоть как-то инкапсулировать бизнес-логику. Выставлять внешним клиентам интерфейс в виде голых таблиц для вызова "серий простых SQL операторов" — это настолько плохая идея, что её вообще обсуждать не стоит.
Единственная альтернатива хранимкам — это публиковать rest-like интерфейс на основе view с instead of триггерами.
Но это, имхо, слишком радикально, чтобы стать мейнстримом
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
G>>Зато есть преимущества, которые никто больше не может перекрыть. Это скорость. S>Скорость чего? И по сравнению с чем? Скорость исполнения? Я вас разочарую — ХП исполняется настолько же быстро, сколько и эквивалентный ей SQL батч. S>Скорость написания — в разы хуже, потому что язык хреновый. С# по скорости написания корректного кода уделывает любой диалект SQL. G>>И огромная куча функционала под капотом. Мне, например, нравится пример с коллекцией в памяти, которую куча потоков модифицирует в памяти. Как организоывать блокировки? Для каждой строчки read-write lock, а блокировки на диапозоны, а что насчет эскалации блокировок. В SQL Server этот код уже написан, отлажен и работает максимально эффективно. Если вы будете делать что-то аналогичное, то либо потратите кучу времени и напишите кучу кода, либо получите неэффективное решение. S>Если честно, то это плохой пример. Для коллекции в памяти иметь словарик rw-локов безо всякой иерархии окажется на несколько порядков эффективнее SQL Server. Просто потому, что он упирается в IOPS диска, а коллекция в памяти — в ширину шины.
Итак у вас уже словарик rw-локов нужно написать, не много пока. Ну а как вы будете делать блокировки диапозонов? Это еще придумать надо и написать... А будут ли у вас блокировки о намереньях? А эскалации блокировок у вас будут или все будет по простому и как следствие неэффективно? А средства диагностики у вас какие на коллекции какие будут? Дедлоки как ловить? А что если ваша коллекция не влазит в оперативную память целиком, но вы активно работаете с некоторой частью, то есть имеется активная и пасивная части. Пасивная спокойно никому не мешая может лежать на диске. SQL Server это предоставляет. А фрагментация памяти? Если у вас в середине удаляются записи, сколько коллекция памяти будет занимать? Одним словом, написать что-то похожее на SQL Server сложно и в итоге вы получите SQL Server, только на C# и с багами...
Здравствуйте, Gattaka, Вы писали:
G>Итак у вас уже словарик rw-локов нужно написать, не много пока. Ну а как вы будете делать блокировки диапозонов? Это еще придумать надо и написать... G>А будут ли у вас блокировки о намереньях? А эскалации блокировок у вас будут или все будет по простому и как следствие неэффективно? А средства диагностики у вас какие на коллекции какие будут? Дедлоки как ловить? А что если ваша коллекция не влазит в оперативную память целиком, но вы активно работаете с некоторой частью, то есть имеется активная и пасивная части. Пасивная спокойно никому не мешая может лежать на диске. SQL Server это предоставляет. А фрагментация памяти? Если у вас в середине удаляются записи, сколько коллекция памяти будет занимать? Одним словом, написать что-то похожее на SQL Server сложно и в итоге вы получите SQL Server, только на C# и с багами...
Вы это к чему? Я не предлагаю заиенить SQL Server, вы спорите о чём-то не том. Для того, что делает SQL Server, альтернативы, в общем-то нет. Только другие RDBMS того же класса.
А вот для того, чему хватит коллекции в памяти, SQL Server будет оверкиллом. С этим лучше заранее смириться, иначе вся жизнь уйдёт на борьбу с ветряными мельницами.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Gattaka, Вы писали: HBM>>>только что касается выборки реляционных данных. HBM>>>в остальном мы получаем кучу проблем. HBM>>>- реализовывать сложную логику в разы сложнее и код становится страшным G>>Сказки про страшный код. S>К сожалению, это не сказки. В SQL отвратительные возможности по декомпозиции. Появление table-valued функций немножко улучшило ситуацию, но недостаточно радикально. S>Поэтому типичная бизнес-логика на SQL — это бесконечные повторы однообразных оборотов типа "where (t1.OwnerID = @userID or t1.GroupIP = @userGroupId) inner join ... ... where (t2.OwnerID = @userID or t2.GroupIP = @userGroupId)". Это тяжело отлаживать (к примеру, для SQL Server интерактивный отладчик появился уже после того, как я перестал на нём программировать, а для остальных СУБД, как я понимаю, такого до сих пор нет), легко ошибиться, и нет статической проверки корректности компилятором.
S>Поэтому при прочих равных я предпочту эту бизнес-логику видеть написанной на Linq, где можно с одной стороны писать декларативные стейтменты в стиле SQL, не задуряясь порядком обхода джойнов, а с другой стороны есть все плюшки декомпозиции современного ЯП.
Это все вопросы привычки. У меня коллеги тоже не могут читать код на C# if(isValid), нужно писать if(isValid == true). Я нормально читаю, как и другие мои некоторые коллеги. Если посмотреть на C++, так там вобще жесть... Я уже очень давно на С++ не писал и меня он уже пугает. Что касается SQL, то когда то и я думал как вы, но после нескольких лет работы фактически SQL программистом, спокойно читаю код. Меня не смущают повторяющиеся условия, наоборот это удобно — ты сразу все видишь.
Привычка не более...
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, wildwind, Вы писали: W>>Стоп, а в какой момент наш клиент вдруг стал недоверенным? Тот же клиент, не использующий хранимки, будет доверенным? Что-то я теряю твою мысль. S>Никакому удалённому клиенту доверять нельзя. Доверенным для RDBMS может быть AppServer, потому что разрабатываем и развёртываем его мы, вместе с базой. S>Все остальные клиенты — неизвестное зло. S>Они будут выполнять delete * from users where 1=1 и прочее, если им это всё не запретить.
Можно правами и ролями пользователей базы разрулить если очень хочется...
S>>Поэтому при прочих равных я предпочту эту бизнес-логику видеть написанной на Linq, где можно с одной стороны писать декларативные стейтменты в стиле SQL, не задуряясь порядком обхода джойнов, а с другой стороны есть все плюшки декомпозиции современного ЯП. G>Это все вопросы привычки. У меня коллеги тоже не могут читать код на C# if(isValid), нужно писать if(isValid == true). Я нормально читаю, как и другие мои некоторые коллеги. Если посмотреть на C++, так там вобще жесть... Я уже очень давно на С++ не писал и меня он уже пугает. Что касается SQL, то когда то и я думал как вы, но после нескольких лет работы фактически SQL программистом, спокойно читаю код. Меня не смущают повторяющиеся условия, наоборот это удобно — ты сразу все видишь. G>Привычка не более...
Это не вопросы привычки. Это вопросы эффективности работы. Зачем заставлять себя писать полсотни килобайт там, где можно написать пять килобайт?
Я когда начинал осваивать SQL, то думал как вы. А после нескольких лет работы фактически SQL программистом начал видеть его недостатки.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали: G>Можно правами и ролями пользователей базы разрулить если очень хочется...
Не получится. Потому что между пользователем и базой стоит код. И сколько в этом коде багов — никто не знает. Если отобрать у пользователя права, то он не сможет работать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Никакому удалённому клиенту доверять нельзя. Доверенным для RDBMS может быть AppServer, потому что разрабатываем и развёртываем его мы, вместе с базой. S>Все остальные клиенты — неизвестное зло.
S>Если такое случилось в апп-сервере — мы его чиним. Он под нашим контролем.
Мы выше начали с того, что у нас большая база и несколько приложений, с ней живущих. В общем случае каждое со своим аппсервером и своими клиентами. Все они наши, только возможно поддерживаются разными командами. Зоопарк, в общем. В такой ситуации дублирование логики неизбежно, и, как следствие, рассинхронизация и нестыковки. Тут хранимки ИМХО полезны. Всю логику в них запихивать глупо, но часть можно. Хотя бы базовую валидацию данных, идущих в базу, чтобы ловить эти нестыковки.
W>>Если явный коммит не подтвержден, значит не сработала. Есть другие варианты? S>Конечно есть. Во-первых, мы вызвали процедуру с автокоммитом.
А не надо с автокоммитом. Хотя если это единственный вызов в транзакции, почему нет.
S>Во-вторых, как вы отличите ситуацию "сервер не подтвердил транзакцию" от "клиент не получил отправленное подтверждение"?
Если это аппсервер, то отличать их нужды нет, так как ситуация "connection reset" нештатная и реакция будет одинаковой: отмена выполняемой задачи (и выкидывание всех промежуточных резульатов), отправка "упс" клиенту и сигнал тревоги админам для ручного разбирательства.
А с удаленным клиентом да, это проблема. Но я сейчас удаленных клиентов не рассматриваю.
W>>И опять же, в чем разница между хранимкой, батчем и серией простых SQL операторов? S>Речь про разницу между SQL и HTTP как протоколами взаимодействия клиент-сервер.
Понятно, мы немного каждый о своем
Здравствуйте, Sinclair, Вы писали:
S>ХП исполняется настолько же быстро, сколько и эквивалентный ей SQL батч.
Вот это немного странно для меня. В других СУБД разница бывает хорошо заметной. Ведь затраты на парсинг и семантический контроль никуда не деваются. Особенно если это "горячее" место.
W>Мы выше начали с того, что у нас большая база и несколько приложений, с ней живущих. В общем случае каждое со своим аппсервером и своими клиентами. Все они наши, только возможно поддерживаются разными командами. Зоопарк, в общем. В такой ситуации дублирование логики неизбежно, и, как следствие, рассинхронизация и нестыковки.
В такой ситуации команда, отвечающая за базу, не может доверять командам приложений. Поэтому их надо от базы изолировать путём внедрения своего аппсервера.
Простой пример: bing maps. Команда bing никому не даст прямого доступа к своей геобазе. Все остальные команды MS ходят к bing maps через апп.сервер с хорошо определённым API.
Это позволяет команде перетряхивать внутреннее хранение как им угодно — синхронизовываться надо только со своим же апп.сервером.
W>А с удаленным клиентом да, это проблема. Но я сейчас удаленных клиентов не рассматриваю.
А я как раз рассматриваю. Речь же о нескольких приложениях, которые пишутся разными командами и на разных языках. Если у нас два апп.сервера над базой, и каждый на своём языке, то это ошибка природы.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, wildwind, Вы писали: W>Вот это немного странно для меня. В других СУБД разница бывает хорошо заметной. Ведь затраты на парсинг и семантический контроль никуда не деваются. Особенно если это "горячее" место.
Типичная хранимка размером в полсотни килобайт исполняется десятки секунд. Микросекунды парсинга тут незаметны даже в микроскоп. Даже затраты на интерпретацию и те виднее.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Это не вопросы привычки. Это вопросы эффективности работы. Зачем заставлять себя писать полсотни килобайт там, где можно написать пять килобайт? S>Я когда начинал осваивать SQL, то думал как вы. А после нескольких лет работы фактически SQL программистом начал видеть его недостатки.
Почему вы говорите, что на SQL кода получается больше? Хотя на самом деле с точность до наоброт кода колучается меньше причем сильно меньше. Я вам привел в одной из веток пример со списком. Вам чтобы аналог двух строчек на SQL написать нужно написать колосальное количество кода, либо смирится с худшей реализацией.
Здравствуйте, Gattaka, Вы писали: G>Почему вы говорите, что на SQL кода получается больше? Хотя на самом деле с точность до наоброт кода колучается меньше причем сильно меньше.
Это какое-то недопонимание. G>Я вам привел в одной из веток пример со списком. Вам чтобы аналог двух строчек на SQL написать нужно написать колосальное количество кода, либо смирится с худшей реализацией.
Можно ещё раз этот пример? Я не вижу кода, о котором вы говорите.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали:
G>>И даже банальные расчеты это показывают. Предположим что цена железа растет линейно мощности. I>Это верно в весьма небольшом диапазоне.
Это смотря что считать "небольшим". Например на проектах, на которых работают 99% пишущих в этой теме, это верно.
G>>Но это гугл и ФБ, таких масштабов в жизни мало кто сможет увидеть. А для обычных приложений вертикальное масштабирование выгоднее. I>На самом деле упомянутый порог достаточно низок и достигается на раз-2 на сколько нибудь нагруженом ресурсе. А если и статистику считать самостоятельно, и контекстные хинты показывать — то все становистя еще веселей.
На SO они достигали несколько лет, и по факту пока еще не достигли. В блоге написано, что один веб-сервер может выдержать весь SO. Проектов масшаба SO нет ни у кого из присутствующих, в том числе у тебя.
G>>Почитай про stackoverflow, при их нагрузках они масштабируются вертикально в основном. I>А почитай про стоимость и время разработки stackoverflow. В обычном же девелопменте квалификация девелоперов сильно ниже отобранных лично джоелом боевых пидарасов на зп сильно выше средней в нуерке, и тайм прессинг сильнее.
И как это связано? Из-за плохих программистов горизонтальное масштабирование становится дешевле?
I>А бывают еще требования по high-availability, которые в принципе невозможно выполнить на одной железяке. Например 99.95% аптайма, которые хотят наверное с половину серьезных заказчиков, прямиком ведет к zero-downtime update. Как ты это заимплементишь на одной железяке?
А кто говорил про одну железку? Ты думаешь, что вертикальное масштабирование это когда все на одной железке работает?
И в чем проблема с zero-downtime update? На любой взрослой базе даже проблемы такой нет.
Здравствуйте, gandjustas, Вы писали:
Например на проектах, на которых работают 99% пишущих в этой теме, это верно.
имха цифра в 99% необоснованно завышена.
G>На SO они достигали несколько лет, и по факту пока еще не достигли. В блоге написано, что один веб-сервер может выдержать весь SO. Проектов масшаба SO нет ни у кого из присутствующих, в том числе у тебя.
Смотря что считать проектом масштаба ссо
Если по нагрузке — то у меня был проект в пике нагрузки выходящий на десятки лямов конкурентых сессий. Есснаж команды анадогичной стоимости у меня наверное никогда не будет. Но твоя уверенность в собственном опыте как единственно верном улыбает
G>И как это связано? Из-за плохих программистов горизонтальное масштабирование становится дешевле?
Именно. Можно взять середнячков и зарядить их педалить бизнес логику по спеке, не особо задумываясь над тем как оно работает изнутри. И ессна ж они налажают, и тупо дешевле поставить еще пару серваков, чем вылизывать код.
G>И в чем проблема с zero-downtime update? На любой взрослой базе даже проблемы такой нет
Ну давай не будем голословными. Вводная: у тебя пару лямов активных сессий, которые упираются в одну бд в десятки терабайт и необходходимо залить новую версию софта, которая интродюсит изменение схемы данных, которая в свою очередь связана с миграцией данных которая выполняется часы.
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали: I> Например на проектах, на которых работают 99% пишущих в этой теме, это верно. I>имха цифра в 99% необоснованно завышена.
Только почему-то за все время не объявился никто, у кого проект по нагрузке превосходил бы SO.
Есть отдельные личности с data-intensive задачами, но трафика SO даже близко ни у кого нет.
G>>На SO они достигали несколько лет, и по факту пока еще не достигли. В блоге написано, что один веб-сервер может выдержать весь SO. Проектов масшаба SO нет ни у кого из присутствующих, в том числе у тебя. I>Смотря что считать проектом масштаба ссо I>Если по нагрузке — то у меня был проект в пике нагрузки выходящий на десятки лямов конкурентых сессий. Есснаж команды анадогичной стоимости у меня наверное никогда не будет. Но твоя уверенность в собственном опыте как единственно верном улыбает
Что такое "конкурентных сессий" ? Сколько запросов приходилось на фронт-енд?
G>>И как это связано? Из-за плохих программистов горизонтальное масштабирование становится дешевле? I>Именно. Можно взять середнячков и зарядить их педалить бизнес логику по спеке, не особо задумываясь над тем как оно работает изнутри. И ессна ж они налажают, и тупо дешевле поставить еще пару серваков, чем вылизывать код.
Это на стоимость scale up vs scale out не влияет.
G>>И в чем проблема с zero-downtime update? На любой взрослой базе даже проблемы такой нет I>Ну давай не будем голословными. Вводная: у тебя пару лямов активных сессий, которые упираются в одну бд в десятки терабайт и необходходимо залить новую версию софта, которая интродюсит изменение схемы данных, которая в свою очередь связана с миграцией данных которая выполняется часы.
Я уже рассказывал, возможно прямо в этой теме.
Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет.
Здравствуйте, gandjustas, Вы писали:
G>Только почему-то за все время не объявился никто, у кого проект по нагрузке превосходил бы SO. G>Есть отдельные личности с data-intensive задачами, но трафика SO даже близко ни у кого нет.
Ну они канечно же тебе были обязаны отрепортать в личку — и писатели топика, и читатели
G>Что такое "конкурентных сессий" ? Сколько запросов приходилось на фронт-енд?
Количество уникальных юзверей, зашедших на сайт. Которым необходимо права доступа разграничивать и все такое.
G>>>И как это связано? Из-за плохих программистов горизонтальное масштабирование становится дешевле? I>>Именно. Можно взять середнячков и зарядить их педалить бизнес логику по спеке, не особо задумываясь над тем как оно работает изнутри. И ессна ж они налажают, и тупо дешевле поставить еще пару серваков, чем вылизывать код. G>Это на стоимость scale up vs scale out не влияет.
Влияет прямиком. Потому что для scale up квалификация команды критична, один кривой запрос положит сервак. А в sсale out все закончится масштабированием.
G>>>И в чем проблема с zero-downtime update? На любой взрослой базе даже проблемы такой нет I>>Ну давай не будем голословными. Вводная: у тебя пару лямов активных сессий, которые упираются в одну бд в десятки терабайт и необходходимо залить новую версию софта, которая интродюсит изменение схемы данных, которая в свою очередь связана с миграцией данных которая выполняется часы. G>Я уже рассказывал, возможно прямо в этой теме. G>Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет.
Ну вот, на время апдейта схемы и кода система лежит. Какой же это 0-downtime?
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали:
G>>Только почему-то за все время не объявился никто, у кого проект по нагрузке превосходил бы SO. G>>Есть отдельные личности с data-intensive задачами, но трафика SO даже близко ни у кого нет. I>Ну они канечно же тебе были обязаны отрепортать в личку — и писатели топика, и читатели
Ну конечно же я обязан считать что у всех пипец какие масштабные проекты и все знают о чем пишут
G>>Что такое "конкурентных сессий" ? Сколько запросов приходилось на фронт-енд? I>Количество уникальных юзверей, зашедших на сайт. Которым необходимо права доступа разграничивать и все такое.
Это за какой период времени? Что за сайт? Сколько запросов на фронтэнд сервер приходится в секунду?
G>>>>И как это связано? Из-за плохих программистов горизонтальное масштабирование становится дешевле? I>>>Именно. Можно взять середнячков и зарядить их педалить бизнес логику по спеке, не особо задумываясь над тем как оно работает изнутри. И ессна ж они налажают, и тупо дешевле поставить еще пару серваков, чем вылизывать код. G>>Это на стоимость scale up vs scale out не влияет. I>Влияет прямиком. Потому что для scale up квалификация команды критична, один кривой запрос положит сервак. А в sсale out все закончится масштабированием.
Если один кривой запрос положит сервак, то он с таким же успехом положит и два, и три, и десять.
Или ты думаешь что действие говнокода ограничено одним сервером?
G>>Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет. I>Ну вот, на время апдейта схемы и кода система лежит.
С чего ты взял?
Здравствуйте, wildwind, Вы писали:
IT>>Я бы сказал так. Увидели хранимку — отбейте железной ленейкой руки тому, кто это сделал. Не существует ни одного реального аргумента, почему ХП должны ещё жить. И уж точно масштабируемость тут ни при чём.
W>Странно слышать столь категоричные утверждения от человека с большим опытом.
Вот как раз большой реальный опыт и позволяет делать такие утверждения.
У нас, кстати, linq2db используется на базе размером около терабайта, если что. Полёт — отличный.
Здравствуйте, gandjustas, Вы писали: G>Ну конечно же я обязан считать что у всех пипец какие масштабные проекты и все знают о чем пишут
Ну как минимум предположить это ты вполне в состоянии
G>Это за какой период времени? Что за сайт? Сколько запросов на фронтэнд сервер приходится в секунду?
Это одновременно. Количество реквестов в секунду того же порядка. Сайт — это NDA.
G>Если один кривой запрос положит сервак, то он с таким же успехом положит и два, и три, и десять. G>Или ты думаешь что действие говнокода ограничено одним сервером?
Я думаю что большинство говнокода(кроме особо запущенных случаев) можно перебороть дополнительными ресурсами. А шардирование позволит не допускать особо идиотских кейсов.
G>>>Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет. I>>Ну вот, на время апдейта схемы и кода система лежит. G>С чего ты взял?
Ну вот расскажи(или кинь линку), как сделать по другому.
Здравствуйте, itslave, Вы писали:
I>Здравствуйте, gandjustas, Вы писали: G>>Ну конечно же я обязан считать что у всех пипец какие масштабные проекты и все знают о чем пишут I>Ну как минимум предположить это ты вполне в состоянии
Я стараюсь использовать научный метод — подвергать сомнению все, что не доказано. За несколько лет холиваров не нашлось ни одного человека с проектами масштаба SO. Поэтому я сомневаюсь в компетентности всех пишуших про нагрузки.
G>>Это за какой период времени? Что за сайт? Сколько запросов на фронтэнд сервер приходится в секунду? I>Это одновременно. Количество реквестов в секунду того же порядка. Сайт — это NDA.
Что такое "одновременно" ? Ключевая входящая метрика нагрузки — RPS, "того же порядка" это не число, количество фронтэндов тоже неизвестно.
Это и дает основание сомневаться.
G>>Если один кривой запрос положит сервак, то он с таким же успехом положит и два, и три, и десять. G>>Или ты думаешь что действие говнокода ограничено одним сервером? I>Я думаю что большинство говнокода(кроме особо запущенных случаев) можно перебороть дополнительными ресурсами. А шардирование позволит не допускать особо идиотских кейсов.
Приведи пример чтоли. Не понимаю как шардирование связано с говнокодом.
Например если из-за говнокода применяется линейный поиск по таблице\коллекции, то шардирование потребует на порядок больше денег, чем исправление говнокода. Но на scale up vs scale out не повлияет.
G>>>>Сначала делается апдейт схемы, а потом изменяется код, который со схемой работает. Если отказаться от идеи "одного большого апдейта", то и проблем не будет. I>>>Ну вот, на время апдейта схемы и кода система лежит. G>>С чего ты взял? I>Ну вот расскажи(или кинь линку), как сделать по другому.
Не пойму что сделать по-другому? Что ты делаешь из-за чего у тебя система останавливается?
Здравствуйте, itslave, Вы писали: I>Допустим версия 1 софтины работает с БД, в которой есть табличка Orders с колонкой Comments. В версии 2 той же софтины появляется табличка Comments, связанная N:1 c табличкой Orders. Также в табличке Comments есть поле интовое Kind, которое характеризует тип комментария: жалоба, похвала и тд. Также надо построить полнотекстовый индекс по каменту, ключам и Kind-у. I>Необходимо проапдейтать БД и смигрировать данные в новую таблицу. Каждый имеющейся камент необходимо проверить полдесятком регэкспов и в зависимости от результата выставить коле Kind. I>В таблице — десятки лямов записей и вся процедура занимает около часа. I>Твой solution?
Решение — примитивное:
1. Первым делом меняем схему
— добавляем табличку Comments
— добавляем view Orders, которое заменит таблицу Оrders, которую мы переименуем в OrdersData. С очевидным left outer join и coalesce для чтения комментов.
— добавляем instead of триггеры для нашего нового View, которые отправляют комментарий в comments, а всё остальное — в OrdersData
2. Теперь мы можем инциировать процесс ленивого выдвижения данных. Нам неважно сколько времени он займет, т.к. это не влияет на работу системы. Пусть займёт хоть 1000 часов.
3. когда все данные уже на месте, можно подменить код и метаданные за секунды.
Это самая сложная из задач, или есть ещё что-то?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Решение — примитивное: S>1. Первым делом меняем схему S>- добавляем табличку Comments S>- добавляем view Orders, которое заменит таблицу Оrders, которую мы переименуем в OrdersData. С очевидным left outer join и coalesce для чтения комментов.
Только не зыбываем, что на таблицу Orders могут быть завязаны другие таблицы, индексы, jobs, репликация. Так что даже переименование не такая уж простая операция.
S>- добавляем instead of триггеры для нашего нового View, которые отправляют комментарий в comments, а всё остальное — в OrdersData S>2. Теперь мы можем инциировать процесс ленивого выдвижения данных. Нам неважно сколько времени он займет, т.к. это не влияет на работу системы. Пусть займёт хоть 1000 часов.
Важно, хотя бы потому, что общая производительность сервера просядет. А если поплывет статистика по индексам, то тем более.
S>3. когда все данные уже на месте, можно подменить код и метаданные за секунды. S>Это самая сложная из задач, или есть ещё что-то?
Это красиво выглядит только на примитивнейшей базе.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, Sinclair, Вы писали:
S>>Решение — примитивное: S>>1. Первым делом меняем схему S>>- добавляем табличку Comments S>>- добавляем view Orders, которое заменит таблицу Оrders, которую мы переименуем в OrdersData. С очевидным left outer join и coalesce для чтения комментов. AJD>Только не зыбываем, что на таблицу Orders могут быть завязаны другие таблицы, индексы, jobs, репликация. Так что даже переименование не такая уж простая операция.
И что? Остановка сервера все равно не требуется.
S>>- добавляем instead of триггеры для нашего нового View, которые отправляют комментарий в comments, а всё остальное — в OrdersData S>>2. Теперь мы можем инциировать процесс ленивого выдвижения данных. Нам неважно сколько времени он займет, т.к. это не влияет на работу системы. Пусть займёт хоть 1000 часов. AJD>Важно, хотя бы потому, что общая производительность сервера просядет. А если поплывет статистика по индексам, то тем более.
1) с чего бы статистике поехать?
2) как ты хочешь сделать аналогичную операцию без просадки производительности?
S>>3. когда все данные уже на месте, можно подменить код и метаданные за секунды. S>>Это самая сложная из задач, или есть ещё что-то? AJD>Это красиво выглядит только на примитивнейшей базе.
Это нормально работает на любой базе. Я так структуру таблицы менял на лету, таблица 500 гб занимала. Там даже online clustered index rebuild на 16 часов был. Остановок — 0, повышение нагрузки на сервер БД — 30%, но запаса хватало.
Здравствуйте, gandjustas, Вы писали:
AJD>>Только не зыбываем, что на таблицу Orders могут быть завязаны другие таблицы, индексы, jobs, репликация. Так что даже переименование не такая уж простая операция. G>И что? Остановка сервера все равно не требуется.
Тебе прийдется эти джобы и репликацию остановить.
G>1) с чего бы статистике поехать?
Условия меняются.
G>2) как ты хочешь сделать аналогичную операцию без просадки производительности?
Я не знаю как это сделать без просадки производительности.
AJD>>Это красиво выглядит только на примитивнейшей базе. G>Это нормально работает на любой базе. Я так структуру таблицы менял на лету, таблица 500 гб занимала. Там даже online clustered index rebuild на 16 часов был. Остановок — 0, повышение нагрузки на сервер БД — 30%, но запаса хватало.
Это просто говорит о том, что у тебя низкая нагрузка на базу и замедление не вызывало лавинообразный рост времени обработки запросов.
Понятно, что если клиент готов мириться с многосекундным ожиданием — это не проблема. Для других клиентов рост времени обработки с 10-15мс даже до 500мс будет не приемлемо.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, gandjustas, Вы писали:
AJD>>>Только не зыбываем, что на таблицу Orders могут быть завязаны другие таблицы, индексы, jobs, репликация. Так что даже переименование не такая уж простая операция. G>>И что? Остановка сервера все равно не требуется. AJD>Тебе прийдется эти джобы и репликацию остановить.
Это одна транзакция, которая отрабатывает за долю секунды.
G>>1) с чего бы статистике поехать? AJD>Условия меняются.
И с чего бы статистике поехать?
G>>2) как ты хочешь сделать аналогичную операцию без просадки производительности? AJD>Я не знаю как это сделать без просадки производительности.
Тогда к чему ты написал?
AJD>>>Это красиво выглядит только на примитивнейшей базе. G>>Это нормально работает на любой базе. Я так структуру таблицы менял на лету, таблица 500 гб занимала. Там даже online clustered index rebuild на 16 часов был. Остановок — 0, повышение нагрузки на сервер БД — 30%, но запаса хватало. AJD>Это просто говорит о том, что у тебя низкая нагрузка на базу и замедление не вызывало лавинообразный рост времени обработки запросов.
Там база с огромным запасом была.
AJD>Понятно, что если клиент готов мириться с многосекундным ожиданием — это не проблема. Для других клиентов рост времени обработки с 10-15мс даже до 500мс будет не приемлемо.
С чего будет такой рост? Ты сказки рассказываешь какие-то.
Здравствуйте, gandjustas, Вы писали: G>Я говорю про поиск по всей коллекции, при чем тут шард? Переписать SQL запрос и добавить индекс попроще будет, чем плодить шарды и ограничивать запросы.
А я тебе говорю про то, что шардирование тебя заставляет думать правильно и не волевым усилием отрубить поиск по всей коллекции. А переписывать проще пока ты один, а если у тебя в команде десяток-полтора архаровцев разной квалификации и мотивации, то тут проще и дешевле плодить шарды.
G>За 10 часов тебе нападает еще комментов, переливание которых займет уже не 10 часов, а меньше, пусть час. G>Так за несколько итерация время переливания уменьшится практически до нуля. Потом накатить новую версию софта.
рабочий вариант, ты упустил перестроение полнотекстового индекса которое очевидно дело долгое. Если расскажешь как с ним быть без просадки производительности, то можно сказать что нет вопросов
Здравствуйте, gandjustas, Вы писали:
G>>>И что? Остановка сервера все равно не требуется. AJD>>Тебе прийдется эти джобы и репликацию остановить. G>Это одна транзакция, которая отрабатывает за долю секунды.
Во-первых не долю секунды, а во вторых это влияет на функционал.
AJD>>Условия меняются. G>И с чего бы статистике поехать?
с того бы это.
G>>>2) как ты хочешь сделать аналогичную операцию без просадки производительности? AJD>>Я не знаю как это сделать без просадки производительности. G>Тогда к чему ты написал?
Ты еще помнишь о чем топик то был?
AJD>>>>Это красиво выглядит только на примитивнейшей базе. G>>>Это нормально работает на любой базе. Я так структуру таблицы менял на лету, таблица 500 гб занимала. Там даже online clustered index rebuild на 16 часов был. Остановок — 0, повышение нагрузки на сервер БД — 30%, но запаса хватало. AJD>>Это просто говорит о том, что у тебя низкая нагрузка на базу и замедление не вызывало лавинообразный рост времени обработки запросов. G>Там база с огромным запасом была.
Что подверждает тезис что твоя схема работает только при отсуствии сколько нибудь существенной нагрузки.
AJD>>Понятно, что если клиент готов мириться с многосекундным ожиданием — это не проблема. Для других клиентов рост времени обработки с 10-15мс даже до 500мс будет не приемлемо. G>С чего будет такой рост? Ты сказки рассказываешь какие-то.
практика, она такая разная бывает.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, gandjustas, Вы писали:
G>>>>И что? Остановка сервера все равно не требуется. AJD>>>Тебе прийдется эти джобы и репликацию остановить. G>>Это одна транзакция, которая отрабатывает за долю секунды. AJD>Во-первых не долю секунды, а во вторых это влияет на функционал.
Что именно влияет?
AJD>>>Условия меняются. G>>И с чего бы статистике поехать? AJD>с того бы это.
Ты приведи конкретику, а то вообще непонятно чем твое предположение обосновано.
Новые таблицы, новые индексы -> новая статистика. Что куда поедет объясни.
G>>>>2) как ты хочешь сделать аналогичную операцию без просадки производительности? AJD>>>Я не знаю как это сделать без просадки производительности. G>>Тогда к чему ты написал? AJD>Ты еще помнишь о чем топик то был?
Да, топик был о том, что вертикальное масштабирование выгоднее горизонтального. На что коллега сказал, что при горизонтальном масштабировании можно сделать zero downtime и предложил такую проблему. Ты пишешь что нельзя это сделать без просадки производительности.
AJD>>>>>Это красиво выглядит только на примитивнейшей базе. G>>>>Это нормально работает на любой базе. Я так структуру таблицы менял на лету, таблица 500 гб занимала. Там даже online clustered index rebuild на 16 часов был. Остановок — 0, повышение нагрузки на сервер БД — 30%, но запаса хватало. AJD>>>Это просто говорит о том, что у тебя низкая нагрузка на базу и замедление не вызывало лавинообразный рост времени обработки запросов. G>>Там база с огромным запасом была. AJD>Что подверждает тезис что твоя схема работает только при отсуствии сколько нибудь существенной нагрузки.
И что? Ты все равно не можешь предложить альтернативу.
Или предлагай свой вариант, который успешно отработает при несущественной нагрузке в 50 RPS на FE.
AJD>>>Понятно, что если клиент готов мириться с многосекундным ожиданием — это не проблема. Для других клиентов рост времени обработки с 10-15мс даже до 500мс будет не приемлемо. G>>С чего будет такой рост? Ты сказки рассказываешь какие-то. AJD>практика, она такая разная бывает.
То есть ты просто фантазируешь. В мире фантазий можно доказать или опровергнуть что угодно.
Здравствуйте, gandjustas, Вы писали:
AJD>>>>Тебе прийдется эти джобы и репликацию остановить. G>>>Это одна транзакция, которая отрабатывает за долю секунды. AJD>>Во-первых не долю секунды, а во вторых это влияет на функционал. G>Что именно влияет?
Если ты отключил репликацию — у тебя изменился функционал
G>Да, топик был о том, что вертикальное масштабирование выгоднее горизонтального. На что коллега сказал, что при горизонтальном масштабировании можно сделать zero downtime и предложил такую проблему. Ты пишешь что нельзя это сделать без просадки производительности.
Я пишу что при вертикальном масштабировании просадка производительности недопустимо велика и выгоднее положить серевер, чтобы выкатить новую версию.
AJD>>Что подверждает тезис что твоя схема работает только при отсуствии сколько нибудь существенной нагрузки. G>И что? Ты все равно не можешь предложить альтернативу.
Нет. Я не могу предложить альтернативу. Мне наоборот интересно как это сделать на существующей архитектуре. Просто предложеный подход добавиим тут на лету пару табличик и вюьх и все будет пучком плохо работает.
G>>>С чего будет такой рост? Ты сказки рассказываешь какие-то. AJD>>практика, она такая разная бывает. G>То есть ты просто фантазируешь. В мире фантазий можно доказать или опровергнуть что угодно.
Я смотрю на online графики выполнения запросов на продакшене и ежедневные performance отчеты. И когда возникают спайки всегда выясняется почему они возникли и что их вызвало.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
G> -- 1. Простовляем списку узлов новую роль
G> update Network_Node
G> set Property = true -- Некий признак, флаг, роль и т.п. не вдаемся в подробности. Нам пока не важно.
G> where Id in (select Id from @selectedNodes) --В хранимку приходит список идентификаторов выбранных пользователем узлов - табличная переменная @selectedNodes
G> -- 2. В соответсвии с бизнес логикой нужно найти связать сетевые узлы, которым мы проставили роль по связям зарегестрированных на них пользователей
G> --
G> insert Node_Node(Node1Id, Node2Id)
G> select distinct n.Id, un2.NodeId
G> from Network_Node n
G> join UserOnNode un1 on un1.NodeId = n.Id -- Берем пользователей зарегестрированных на узле
G> join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователя
G> join UserOnNode un2 on un1.UserId = uu.User2Id -- Берем узлы на котором зарегестрирован связанный пользователь
G> where n.Property = true
G> and not exists(select * from Node_Node nn3 where nn3.Node1Id = n.Id and nn3.Node2Id = un2.NodeId) -- такая связь еще не сущетвует
G>
1. Запрос можно оптимизировать: у вас тут алгоритм Шлемиеля. Предположим, у меня Property=true у 34999 нод, и я передаю в @selectedNodes ровно одну ноду, причём у неё Property до этого было false.
Ваш запрос where n.Property = true найдёт 35000 нод, и начнёт шерстить полбазы. В то время, как достаточно просканировать только пользователей этой одной ноды:
insert Node_Node(Node1Id, Node2Id)
select distinct un1.NodeId, un2.NodeId
from UserOnNode un1
join User_User uu on uu.User1Id = un1.UserId -- Берем связи пользователяjoin UserOnNode un2 on un1.UserId = uu.User2Id -- Берем узлы на котором зарегестрирован связанный пользовательwhere un1.NodeId in (select Id from @selectedNodes) -- избегаем лишнего сканирования индекса по Network_Nodeand not exists(select * from Node_Node nn3 where nn3.Node1Id = n.Id and nn3.Node2Id = un2.NodeId) -- такая связь еще не сущетвует
2. Архитектура выбрана не вполне удачно. Чую проблемы при установке property в false — так просто удалить записи из Node_Node не удастся, ведь вы хотели связать между собой все ноды, у которых есть хотя бы один пользователь ноды со свойством равным true. Т.е. там, где мы при "вставке" обошлись distinct, при удалении надо смотреть, нет ли других связей. Потенциально сканируя все 36GB.
3. А теперь собственно к главному: почему вы думаете, что хранимка — это единственный и правильный способ записать этот запрос? По мне так надо просто записать его в виде Linq выражения, возможно получив более понатный код.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.