Обновить
47
Всеволод Новиков@nnseva

Пользователь

13
Подписчики
Отправить сообщение
Исходя из обычной арифметики, можно отметить [...] Конкретные цифры вы можете получить самостоятельно на любом движке SQL.


Это означает, что вы пока не столкнулись с реальными проблемами, связанными с этим оверхедом. Я их тоже, знаете ли, никогда не видел.

Это означает, что мне лениво поднимать соответствующий тест и проводить формальные замеры. Я не только столкнулся с этой проблемой лоб в лоб, мне пришлось ее решать способом даже более кардинальным, чем я здесь описываю. В частности, мне вовсе пришлось отказаться от попытки как-либо использовать ORM непосредственно при первичной обработке данных, а при анализе — прикручивать сбоку наляпки разного рода. Именно поэтому в частности, я начал думать о чем-то таком, что я здесь описал.
В данной статье у меня не было желания подробно разбирать ORM с точки зрения производительности например. Если желаете, можете ознакомиться со статьей вот здесь, где я подробно описываю, с конкретными замерами и цифрами, как именно отдельные ORMы сами по себе, просто в силу своего применения, тормозят исполнение кода и расходуют ресурсы, а также вот здесь, где я предлагаю наляпку на Django ORM для прикручивания и кеширования параметризованных запросов, ускоряющую выполнение кода в несколько раз. Правда там нет того конкретного юзкейза, который мы обсуждаем здесь (запись-владелец и набор записей-деталей). На написание и проведение тестов, которые приведены в каждой из статей, я потратил не менее одного рабочего дня и еще немножко. Вы при желании, можете потратить примерно столько же, чтобы убедиться, прав я или не прав. Но прежде чем заявлять здесь что я неправ, неплохо бы было именно Вам провести подобные тесты.

Я, вроде бы, явно написал: Entity Framework.

Это вот этот что-ли? msdn.microsoft.com/ru-ru/library/bb399572.aspx

В таком случае вот здесь msdn.microsoft.com/ru-ru/data/jj591617 описаны юзкейзы различного использования маппинга:
— Specifying That a Class Is a Complex Type
— Specifying Not to Map a CLR Entity Type to a Table in the Database
— Mapping a CLR Entity Type to a Specific Table in the Database
— Mapping the Table-Per-Hierarchy (TPH) Inheritance
— Mapping the Table-Per-Type (TPT) Inheritance
— Mapping the Table-Per-Concrete Class (TPC) Inheritance
— Mapping CLR Properties of an Entity Type to Multiple Tables in the Database (Entity Splitting)
— Mapping Multiple Entity Types to One Table in the Database (Table Splitting)

Я что-то не вижу здесь возможности отобразить один и тот же класс так, чтобы он хранил часть объектов — в одной таблице, а часть — в другой (Entity Splitting как я понимаю, позволяет разделять один объект на два, мапируя их на разные таблицы, это не то, ну а остальные варианты только сокращают количество коллекций), а это и было одним из описанных преимуществ CoRM.

Таким образом, мое предыдущее утверждение о том что <<«любой» ORM почему-то склонен считать, что в программе будет ровно по одной коллекции для хранения состояния объектов каждого из классов, входящих в отображение>>, остается верным, с точностью до «ровно», которое надо разумеется, заменить на «не более чем» (последнее верно и для других ORM). Ну и конечно же, «склонен» формально не означает, что так оно и есть во всех случаях.

Прямое сравнение MongoDB и CoRM по этим параметрам — не совсем корректно, поскольку во-первых, они решают сходные, но не идентичные задачи

И в чем же различие?

Своей невнимательностью, Вы во второй раз заставляете меня цитировать самого себя, что (цитирование) вообще говоря, является дурным тоном:
Различий, тем не менее, есть здесь несколько.

Во-вторых, в отличие от MongoDB, CoRM не пытается воспроизвести собственный движок СУБД, а опирается на существующие СУБД…
В-третьих, документ-ориентированные СУБД занимаются хранением состояния объекта и запросами к этому состоянию, в то время как у CoRM хранение состояния поручено нижележащему движку SQL, а сам CoRM осуществляет сокрытие деталей хранения, обеспечивая корректное преобразование хранимого состояния в объект заданного класса и обратно.

В этом и состоит различие. CoRM — не движок датабазы, а лишь маппинг, а MongoDB — движок, поэтому производительность движка и производительность CoRM — разные, хотя и взаимосвязанные вещи. Кроме того, результатом работы самой MongoDB является в чистом виде структура данных, что требует навешивания сверху дополнительных слоев, реализующих поведение объекта, см здесь. Поэтому корректное сравнение должно сопоставлять производительность ORM-ов, представленных на указанной ссылке, с производительностью библиотеки CoRM на различных движках SQL, буде таковая библиотека разработана.
Я помню ее, когда-то — лет 10 назад — очень внимательно разглядывал, очень интересная концепция была, хорошо что она столь успешно развивается.
В любой организации есть люди, думающие по разному и имеющие разные интересы, причем корреляция этих интересов с интересами самой организации тоже довольно таки мала.

Я сообщил о своей готовности потратить некоторое количество собственного времени на то, чтобы попытаться разобраться в проблеме, выявленной сообществом. Вдруг есть кто-то в РОИ, кому действительно интересно сделать полезную и нужную программу, которой будут доверять? Кстати, с точки зрения функциональности и дизайна она имхо очень и очень неплоха — а учитывая весьма вероятное давление со стороны госадминистраторов с их идиотскими требованиями и наверняка распиленный имхо процентов на 70 бюджет, сайт выполнен просто блестяще. Значит, кому-то таки было дело то того, чтобы сконструировать не просто УГ для оправдания потраченных денег.

Нужно искать именно этих людей.
сам использую СУБД, нативно поддерживающую ООП (не только для Python), SQL и NoSQL, поэтому и вопросов с ORM не возникает.

Какую именно, если не секрет?
Инкапсуляция заставляет существующие реализации получать весь кортеж простых атрибутов состояния объекта всякий раз при доступе к объекту (в некоторых случаях — еще и атрибуты из связанных таблиц).

Не заставляет. Как раз наоборот, инкапсуляция позволяет объекту получать свое состояние из БД таким образом, каким ему нужно.

По крайней мере, на примере конкретно Django ORM, SQLAlchemy или Elixir — еще как заставляет. Я не говорил и не говорю о том, что это обязательно должно случаться, поэтому в частности, в исходном посте отметил, что это обстоятельство, наблюдаемое на существующих ORM, с которыми мне пришлось иметь дело (см. выше), возможно, исходит из теоретических противоречий. Данный вопрос требует отдельной серьезной проработки. Тем не менее, исходя из того, что ООП подходит к объекту в целом, как к монолитному конструкту, в то время как «запись» или «кортеж» в реляционной СУБД — это открытый и гибкий по составу набор значений, можно отметить некоторое противоречие при прямом отображении объекта и кортежа друг на друга.

Реальные цифры есть? Насколько БД «утяжеляет» лишняя таблица (по сравнению с размещением этих данных в какой-нибудь другой таблице).

Исходя из обычной арифметики, можно отметить, что поле ссылки на родителя и индекс на этом поле в дочерней таблице будут представлять из себя совершенно лишние сущности и «утяжелять» базу данных, при условии, что дочерняя таблица не используется ни в каких запросах, кроме запросов на получение списка дочерних элементов для родительской записи. Конкретные цифры вы можете получить самостоятельно на любом движке SQL.

«Любой» ORM почему-то склонен считать, что в программе будет ровно по одной коллекции для хранения состояния объектов каждого из классов, входящих в отображение.

Это утверждение неверное. Как минимум есть Entity Framework, который так не считает.

Именно поэтому «любой» я поставил в кавычки. Впрочем, я все равно попрошу ссылку на то, какой именно фреймворк Вы имеете в виду.

Вы уверены, что это достоинства, а не недостатки? В частности, что у вас нет потерь производительности на лишнем уровне преобразований?

Прямое сравнение MongoDB и CoRM по этим параметрам — не совсем корректно, поскольку во-первых, они решают сходные, но не идентичные задачи, а во-вторых — MongoDB имеет место быть, в то время как CoRM только еще проектируется.
Так нету кода, это проект только…
инкапсуляция, прямо противоречащая принципиальной открытости и произвольности комбинирования значений в кортежах SQL, создает на самом деле существенные (возможно, исходящие из теоретических противоречий) препятствия эффективному использованию реляционной СУБД, как долговременного хранилища состояний объектов класса

Каким образом?

(не исчерпывающе, но в частности). Инкапсуляция заставляет существующие реализации получать весь кортеж простых атрибутов состояния объекта всякий раз при доступе к объекту (в некоторых случаях — еще и атрибуты из связанных таблиц). При обширном количестве атрибутов, это существенно увеличивает трафик данных между БД и приложением, заметно снижая общую эффективность приложения по сравнению с «чистым» SQL.

Одна из проблем при прямом отображении классов в таблицы: отображение атрибутов, имеющих множественный характер, требует отдельных таблиц.

А почему это проблема?

Это оказывается проблемой, когда природа приложения такова, что данные множественного атрибута не имеют самостоятельной ценности без владеющего ими объекта. Например — документ и его строки.
В этом случае, вторая таблица становится ненужной с той точки зрения, что по ней приложение не осуществляет никаких глобальных запросов, поэтому ее наличие в базе только утяжеляет ее, с точки зрения наличия индексов на таблице строк например.

таблица, как долговременное хранилище состояний объекта, имеет вполне адекватный образ в любой развитой объектно-ориентированной среде, а именно — коллекцию (Collection).

Ага, на этом факте основано представление в любом ORM. Так что не понятно, почему вы умудряетесь на этот факт «опирать» другую парадигму.

Не совсем. «Любой» ORM почему-то склонен считать, что в программе будет ровно по одной коллекции для хранения состояния объектов каждого из классов, входящих в отображение.

Рисунок 3


… и тут мы видим нормальную такую документо-ориентированную БД. Иии?

Документ-ориентированные СУБД, исходя из их общего описания, например MongoDB, опираются на «монолитное» хранение набора хранимых атрибутов, как простых, так и имеющих множественный характер, допускают хранение произвольных наборов простых и сложных атрибутов в любых коллекциях, индексируют отдельные атрибуты. Это действительно, очень похоже на CoRM в той части, которая отображена на рисунке. Вероятно, я должен был в исходном посте сослаться на них, как на один из прототипов.

Различий, тем не менее, есть здесь несколько.
Во-первых, в отличие от MongoDB и других документ-ориентированных СУБД, гордо относящих себя к NoSQL, CoRM не отказывается от использования некоторой разновидности SQL. Более того, полностью наследуются все преимущества использования реляционных отношений из нижележащего хранилища SQL.
Во-вторых, в отличие от MongoDB, CoRM не пытается воспроизвести собственный движок СУБД, а опирается на существующие СУБД, что дает значительно более обширные возможности, связанные например с совместимостью в гетерогенной среде разработки.
В-третьих, документ-ориентированные СУБД занимаются хранением состояния объекта и запросами к этому состоянию, в то время как у CoRM хранение состояния поручено нижележащему движку SQL, а сам CoRM осуществляет сокрытие деталей хранения, обеспечивая корректное преобразование хранимого состояния в объект заданного класса и обратно.
Готов поучаствовать в аудите. Есть мысли по аудиту данных и логов, но так или иначе, сначала нужно взглянуть на то, что там есть, конкретно.
Впечатление, что уязвимость работала только для самых свежих версий skype. Например в скайп на линуксе, который отстает с версией, после выполнения шага 3 тикет на смену пароля (шаг 4) не пришел. Поснифить протокол не догадался, впрочем там кажется, он шифрованный.

Пока закачивал и запускал скайп под виртуалбоксом, дырку login.skype.com/account/password-reset-request похоже залепили пластилином — редиректит снова на логин.

Думаю, данный пост выполнил свою задачу — стукнуть по голове мелкософту, чтобы шевелился. Автору респект за предупреждение и комплейн за подсказку уродам, радостно пожравшим чужие акки.
Ога, но когда я обнаружил что такого еще не было, стало очень грустно. Пришлось смастрячить и поделиться.

Реально я думаю, потребность в таком костыле у каждого программера наверно возникнет два-три раза за всю карьеру. Тем не менее, считаю 4 потраченных дня — потраченными недаром. Список обнаруженных в проекте строк для локализации — 495, я как минимум потратил времени ровно столько, сколько потратил бы, собирая их вручную. Не учитывая внесенных в проект ошибок при ручной правке. Если хотя бы один человек успешно воспользуется моей тулой на достаточно обширном проекте — буду считать, что свою задачу я выполнил.
manage.py makemessages собирает только сообщения, помеченные обращениями к подсистеме i18n (в исходниках — *gettext, в шаблонах — trans и blocktrans).

Данный скрипт собирает сообщения в таком проекте django, в котором нет обращений к подсистеме i18n (не было изначально предусмотрено при разработке), и помечает их обращениями к подсистеме i18n. При этом, собираются только те сообщения, которые содержат локальный язык (распознаются регекспами).

Тем самым, выполняется задача интернационализации такого проекта, который изначально не был предусмотрен к интернационализации. Приведенный скрипт не заменяет собой makemessages, а дополняет его функции.

Это достаточно узкая, одноразовая задача, выполнять которую, тем не менее, вручную на большом проекте достаточно муторно и долго. Обо всем этом написано в преамбуле статьи.

django-rosetta дает лишь графицский интерфейс к редактированию файлов .po, никак не помогая решать поставленную задачу сбора локальных строк.
Не получится.

Во-первых, строки с форматированием будут содержать placeholders (как ето будет по-рюсски?) вида %(member)s или %s, на которых транслятор сойдет с ума.

Во-вторых, даже на нормальных фразах с чуть более сложной грамматикой, любой известный мне автотранслятор тоже сходит с ума. Я все-таки предпочитаю честно отдать перевод на откуп человеку, чем создавать необоснованную иллюзию сделанной работы. А то получится например, как у китайцев, пытающихся перевести названия своих поделок для андроида (далее цитата из А-Маркета, топ поиска «Игры для детей»):

===========

«Плоды памяти игру для детей»

Описание

«Яблоко, апельсин, клубника… Есть очень много фруктов здесь! И ваша задача найти эти же фрукты и сопоставить их. Эта забавная игра памяти также помогает Вам обучение памяти способности. Удачи!»

===========

Похоже на русский язык? Да, немного :)
Да, отличное решение!
Согласно документации по django, вам нужно иметь по одному файлу django.po для каждого из языков (ну кроме английского, который используется для идентификации сообщений).

Вы размещаете файлы django.po в подкаталогах каталога locale (либо в проекте — для всего проекта, либо в приложении — для конкретного приложения), соответствующих поддерживаемым языкам. Например, для русского — locale/ru/LC_MESSAGES/django.po, для французского locale/fr/LC_MESSAGES/django.po, и так далее.

Подсистема интернационализации django определяет текущий язык по параметрам запроса и отдает содержимое, соответствующее фразам, заготовленным в файле django.po в каталоге для языка пользователя. Если каталог с описанием фраз для данного языка не найден, по умолчанию используются строки msgid.

В контексте статьи, вы можете заготовить файл django.po для русского языка с помощью django-make-i18n, а затем скопировать его в каталоги для других языков и вставить фразы на этих языках.
В классической конфигурации django-wsgi — клиентов (БД) будет ровно столько, сколько WSGI исполнителей. Количество препарированных выражений будет пропорционально количеству строк кода, таким образом нагрузка легко измерима и контролируема. Впрочем конечно же, тестировать надо всегда.

Из питонячьих дриверов упоминание о prepared statements мне удалось найти только в pyodbc. Структура интерфейса «стандартного клиента СУБД» к сожалению, провоцирует разработчиков игнорировать prepared statements — поскольку не поддерживает явного обращения к ним.
Потери на 50% на values это плохо конечно. Но меня лично прикалывает объем потерь именно на коде, который мог бы быть вынесен за пределы рабочего цикла вообще — компиляция запроса sql.
u.groups.all() не возвращает данные. Он возвращает QuerySet. Правильно мерять надо было так:

%time t=[list(u.groups.all()) for i in range(1000)]
%time t=[list(u.groups.values('name')) for i in range(1000)]

1) values_list погоды не делает, тормозит практически так же
2,3) user__id — это обращение к полю id объекта user, в то время как user_id — это вообще говоря произвольное имя параметра параметризованного запроса, там мог бы стоять например vasq_pupkin:

>>> p = ParametrizedQuery(q,vasq_pupkin=12345)
>>> [g['name'] for g in p.execute(vasq_pupkin=u.id)]


Идея заключается в том, что значение 12345 в исходном запросе QuerySet является отметкой того места, куда должно попасть значение соответствующего параметра ParametrizedQuery при его фактическом использовании. То есть последовательность следующая:
  • В исходном запросе мы отмечаем фейковыми значениями (12345, «QQ», datetime.datetime(2345,11,11) и т.д.) те места, которые будут заполняться фактическими значениями параметров при выполнении запроса.
  • Формируя параметризованный запрос, мы даем имена параметрам запроса и связываем эти имена с теми местами, в которые должны попасть фактически переданные значения параметров. Связывание происходит через те фейковые значения, которые переданы в исходный запрос.
  • При использовании параметризованного запроса, мы передаем фактические значения через те параметры, которые были поименованы при формировании параметризованного запроса. Эти значения заменяют собой фейковые значения, переданные при формировании исходного запроса

Это в некотором смысле извращение, которое продиктовано тем, что django не имеет синтаксиса, подходящего для передачи в запрос формальных параметров.

3) Про многопоточность — да, я заметил эту проблему и собираюсь как раз сейчас сделать апдейт поста с поправками по поводу многопоточности и еще несколько мелких недочетов.
Сильно подозреваю (но еще не проверял), что одним из основных мест торможения при рендеринге шаблонов может стать как раз доступ к объектам через ORM. Если вы используете в шаблоне например тот же пресловутый список групп, получая его допустим через выражение наподобие
<ul>
{% for u in user.groups.all %}
  <li>{{ u.name }}</li>
{% endfor %}
</ul>

то при рендеринге такого шаблона как раз и случится описанный в топике тормоз.
a = q.filter(user=u) возвращает подготовленный QuerySet, а вовсе не список значений. Попробуйте a=list(q.filter(user=u)) и вы увидите разницу

Информация

В рейтинге
Не участвует
Откуда
Нижний Новгород, Нижегородская обл., Россия
Дата рождения
Зарегистрирован
Активность