LINUX.ORG.RU

[Python] Нужен ORM наоборот

 


0

0

А точнее, должно быть, такая реализация ORM, которая должна была быть изначально. Вот ее суть: приложение вообще никогда не заморачивается тем, что его объекты мапятся на какие-то там таблицы в РБД. Кроме того, транзакции по обновлению/удалению/вставке объектов должны быть отложенными, а работа с памятью — максимально прозрачной (у меня есть коллекция, и мне пофигу, вся ли она в памяти, или какая-то ее часть «свопится» в БД).

Интерфейс же должен быть настолько отвязан от конкретной СУБД, чтобы можно было заменить реализацию на полностью in-memory, и приложение ничего не должно заметить.

(Предполагается, что никто, кроме этого приложения, с БД не работает.)

Ну и главное — интеграция с Twisted. Нужно очень (adbapi/Deferred, да).

Storm не подходит, потому что позволяет fallback на сырой SQL и за каждым чихом будет таки лезть в БД, даже если это не нужно. Axiom же не подходит потому, что SQLite там прибит гвоздями, а мне нужно PostgreSQL будет.

Так как гугл я уже перерыл, но ничего вменяемого не нашел, то хочу спросить у коммьюнити, а вдруг ведь не надо будет свой велосипед изобретать.

★★★★★

> Storm не подходит, потому что позволяет fallback на сырой SQL и за каждым чихом будет таки лезть в БД, даже если это не нужно.

Ага, сырой SQL низзя... тогда как будет записываться аналог select * from t where v=123 ? В виде цикла? Значит, за каждым значением лезть в базу и выполнять SQL запрос?

www_linux_org_ru ★★★★★
()

Хотя да, альтернатива в принципе возможна -- создать свой аналог SQL поверх питона, но очень велика вероятность, что он будет такой, что от него плеваться захочется. Если же кто-то сделал что-то удобоваримое, интересно взглянуть на такое чудо.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> тогда как будет записываться аналог select * from t where v=123 ? В виде цикла?

Не-а, скорее как

subset = t_collection.find(v == 123)

(как такое реализовать, подсматриваем в Axiom.)

Мне как раз надо решить задачу: множество клиентов, один сервер приложений, минимизация запросов. Нынешние решения ориентируются на то, что надо делать запросы для каждого клиента, а также на PHP-подход, когда считается, что каждое подключение — своя песочница. В Twisted.web это немного не так.

shimon ★★★★★
() автор топика

Общая задача, которую я пытаюсь решить — это:

есть веб-сервер, который отдает динамические данные. Предполагается, что подавляющая часть запросов происходит от поисковых ботов и анонимусов, которые не всегда и не особо кастомизируют под себя вид страниц, хотя, тем не менее, динамические элементы присутствуют (потому весь HTML кешировать нельзя). Типичное решение а-ля PHP предполагает, что на каждый запрос делается SQL; иногда это можно оптимизировать при помощи memcache, иногда — не особо. Я же хочу сделать так, чтобы слаборазличающиеся коллекции объектов гарантированно шарились в памяти между всеми участниками, а SQL для выборки выполнялся только для первого из спросивших, или когда запрашивается новое подмножество.

shimon ★★★★★
() автор топика
Ответ на: комментарий от shimon

> Типичное решение а-ля PHP предполагает, что на каждый запрос делается SQL

типичное решение для быдлокодеров, ага. нормальные поцаны делают еще кэширование средствами PHP.

isden ★★★★★
()
Ответ на: комментарий от isden

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

shimon ★★★★★
() автор топика
Ответ на: комментарий от shimon

> Не-а, скорее как subset = t_collection.find(v == 123)

Сходил на http://divmod.org/trac/browser/trunk/Axiom/axiom/examples/bucket.py

Да, идея интересная, но как у тебя получится сделать t_collection.find(v == 123) ??? Максимум, что я знаю, это t_collection.find( tItem.v == 123)

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от shimon

> чтобы слаборазличающиеся коллекции объектов гарантированно шарились в памяти между всеми участниками

Это как? Типа система должна видить что "похожий" запрос уже выполнялся и подсовывать результат выполнения похожего запроса? :)

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

> Это как? Типа система должна видить что "похожий" запрос уже выполнялся и подсовывать результат выполнения похожего запроса? :)

Дополнять уже имеющиеся результаты результатами из похожего запроса. Или, если все данные уже есть в памяти, вообще запрос не делать.

shimon ★★★★★
() автор топика

Django ORM + кеширование queryset'ов?

dccp
()
Ответ на: комментарий от www_linux_org_ru

>тогда как будет записываться аналог select * from t where v=123

У меня так (правда в некошерном языке):
foreach(objects_array('my_class', array('t' => 123)) as $obj)
  $obj->...

KRoN73 ★★★★★
()

Пока еще ни один ORM не портировали на libastral. Типичный случай, когда хочется и рыбку съесть и жопу побаловать.

Ты же понимаешь, что любой ORM в данной задаче будет неким трейдофом. Между количеством кода и гибкостью.

> приложение вообще никогда не заморачивается тем, что его объекты мапятся на какие-то там таблицы в РБД

> Интерфейс же должен быть настолько отвязан от конкретной СУБД, чтобы можно было заменить реализацию на полностью in-memory, и приложение ничего не должно заметить.

Для этого нужен отдельный слой для доступа к данным. Из любого ORM будут торчать нунужные для приложения детали. Которыми непременно воспользуешься.

> Кроме того, транзакции по обновлению/удалению/вставке объектов должны быть отложенными

Замечательно, а откатывать тоже должен ORM? А как показать обновленную страницу, после сабмита формы? Все это ньюансы приложения.

> работа с памятью — максимально прозрачной (у меня есть коллекция, и мне пофигу, вся ли она в памяти, или какая-то ее часть «свопится» в БД)

Уверяю, обязательно будут случаи, когда будет не пофигу.

baverman ★★★
()

Короче, все это блажь и лень. Делай минимальные интерфейсы для доступа к данным, в качестве бэкенда ЛЮБОЙ ORM. Где нужно используй кеширующий декоратор. Плюс кеш на отдельные части страниц. Отложенные операции через очередь. Развели тут сопли, понимаешь!

baverman ★★★
()
Ответ на: комментарий от KRoN73

> У меня так (правда в некошерном языке): foreach(objects_array('my_class', array('t' => 123)) as $obj) $obj->...

РНР в отличие от питона не поддерживает перегрузку операторов, хотя, по причине нестатической типизации питона, эта разница не так уж и существенна... ну подумаешь чуть дольше парсить...

Я бы эту вещь записал как-нибудь попроще, например строкой ".t==123", потом бы регекспом поправил в "$something->t==123" и eval-ил.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Или даже может быть не поленился препроцессировать РНР-шный исходник, генеря пхп-шный код из $collection->select("f1,f2,f3")->where("f1==123")->group_by("f2 desc,f3")->having("f2>f3")

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

И в конченом итоге получаем, что на с++ с препроцессингом перлом все равно это проще написать и быстрее работать будет... и главное, кто-нить пишет аппликухи на пхп, которые работают постоянно, слушают сокет, форкаются или poll/select, а не рождаются и умирают? На питоне вроде как пишут, так что там идея "держать объекты в памяти" имеет смысл, а нафига это на однозапросном php?

www_linux_org_ru ★★★★★
()

Если так, то банально сериализацией: некоторые поля выделять для целей индексирования, плюс объект во BLOB'е. А так хоть in-memory, хоть через BerkeleyDB.

Вроде бы в питоне есть достаточно извращенный модуль pickle.

Macil ★★★★★
()
Ответ на: комментарий от shimon

> Я же хочу сделать так, чтобы слаборазличающиеся коллекции объектов гарантированно шарились в памяти между всеми участниками, а SQL для выборки выполнялся только для первого из спросивших, или когда запрашивается новое подмножество.

Я сейчас на тикле пишу нечто подобное c SQL-like интерфейсом. Правда никакой генерации SQL - только заполнение шаблонов. ОРМами наелся уже - чуть сложнее схема и такой гемор с ними, что проще сырым SQL кидаться. Когда допинаю, могу показать. Правда вряд ли кому пригодится, тикль нынче не в моде.

Hjorn
()
Ответ на: комментарий от Hjorn

>ОРМами наелся уже - чуть сложнее схема и такой гемор с ними, что проще сырым SQL кидаться.

1. Значит ORM плохой. Мне в моей практике (а практикую я очень часто не свою разработку структур БД, а подхват уже имеющихся, а уж там бывают просто тихие ужасы со структурой) _очень_ редко приходится обращаться к прямым SQL-запросам. И это при том, что я не фанатик и не стараюсь избегать их всеми силами, а предпочитаю использовать более простые решения.

2. Сырой SQL - зло. Поймёшь сам, когда придётся у одного и того же объекта несколько раз бэкэнд менять :)

3. Сырой SQL нередко приводит к снижению производительности. В самом деле, если один объект или одна серия объектов обычно насыщается данными одним запросом, то что в объекте ещё могут SQL делать? Только дёргать какие-то дополнительные данные. Значит - каждое [первое, если проектирование было грамотное] обращение к таким данным - лишний SQL-запрос. А если объектов сто? Тысяча? Сто или тысяча запросов. Нет уж, объект лучше проектировать так, чтобы всё нужное доставалось с одного раза. Понятно, что без грустных исключений, порой, не обойтись, но, тем не менее, чаще использование сырого SQL в нормальной ORM-системе - признак ошибки проектирования.

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

> Значит ORM плохой

Может быть, но рубиевский ActiveRecord меня совершенно не устроил. В конце концов приходишь там к тому, что пишешь жуткие конструкции на уродском языке, отдаленно похожем на SQL. И при этом не контролируешь оптимальность запросов. Может ещё попробую питоновские, когда буду в следующий раз делать что то для веба.

> Сырой SQL - зло. Поймёшь сам, когда придётся у одного и того же объекта несколько раз бэкэнд менять.

Необязательно совать SQL повсюду в коде. Можно вынести в отдельный модуль и дёргать через простой API. Можно пойти дальше, и сделать DAL на хранимых процедурах. В этом подходе есть одно большое преимущество - базы можно полностью отдать на откуп DBA. Минус конечно в том, что теряешь возможности ORM - как то удобные объекты, кэширование и т.п. Поэтому я и пытаюсь сделать некий компромиссный вариант между ORM и хранимыми процедурами.

> Нет уж, объект лучше проектировать так, чтобы всё нужное доставалось с одного раза.

Вот тут не согласен в корне. Доставать нужно только то, что нужно. На то и придумали реляционные БД и SQL. Но я понял вашу мысль, сырой SQL вместе с ORM - зло. Тут согласен, либо ORM, либо какой-то другой DAL.

Hjorn
()
Ответ на: комментарий от Hjorn

>Необязательно совать SQL повсюду в коде. Можно вынести в отдельный модуль и дёргать через простой API.

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

>Можно пойти дальше, и сделать DAL на хранимых процедурах.


И мы ещё сильнее привяжем объект к бэкенду. От ORM совсем уже ничего не останется.

>Поэтому я и пытаюсь сделать некий компромиссный вариант между ORM и хранимыми процедурами.


Ну, это как компромисс между кораблём и деревом. ORM - это ORM. Хранимые процедуры - это бэкенд. При нормальном проектировании объект и бэкенд должны быть развязаны и связь скрыта от программиста.

>Вот тут не согласен в корне. Доставать нужно только то, что нужно.


Да. Но если у тебя параметры объекта разделяются - то это сигнал о том, что объект нужно рефакторить. Правильный объект - атомарный. И вопроса о «частичных доставаниях» данных стоять не должен.

>На то и придумали реляционные БД и SQL.


Одно другому не помеха. Правильный ORM при потребности в сложных запросах сам сформирует хитроподвывернутый запрос. Естественно, не без участия программиста. Но без влезания шаловливыми ручками в потроха SQL :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

> Ну, это как компромисс между кораблём и деревом. ORM - это ORM. Хранимые процедуры - это бэкенд. При нормальном проектировании объект и бэкенд должны быть развязаны и связь скрыта от программиста.

Верно, потому и избегаю хранимых процедур. Из-за завязанности на бэкенд. Но ORM тоже не устраивает, кроме простейших схем.

> Правильный объект - атомарный. И вопроса о «частичных доставаниях» данных стоять не должен.

Спорно. Что же теперь блобы тоже тянуть сразу ради атомарности? Ничего страшного в таких ленивых объектах не вижу. Кроме того, совсем не обязательно отображать только таблицы. Можно отображать вьюсы например, или даже просто параметризуемые запросы в read-only объекты.

> Правильный ORM при потребности в сложных запросах сам сформирует хитроподвывернутый запрос. Естественно, не без участия программиста. Но без влезания шаловливыми ручками в потроха SQL

Чем вам так не угодил SQL? Реляционного языка лучше ещё не придумали (несмотря на огульную критику со стороны Дейта). А когда начинаешь жонглировать объектами, пытаясь сэмулировать реляционный язык - получается сложно и по-уродски. И ещё нужна стойкая вера в "правильный" ORM.

Hjorn
()
Ответ на: комментарий от Hjorn

>Что же теперь блобы тоже тянуть сразу ради атомарности?

Вопрос, конечно, спорный, но я не считаю, что блобам - место в БД :)

>Чем вам так не угодил SQL?


Тем, что это далеко не единственный бэкенд в моих проектах :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

>> Чем вам так не угодил SQL?

> Тем, что это далеко не единственный бэкенд в моих проектах

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

Hjorn
()
Ответ на: комментарий от Hjorn

>Разве что в простых случаях, где РСУБД вообще ни к месту.

Естественно. Далеко не все компоненты проектов - сложные :) Но иногда бэкенды приходится менять. Скажем, довольно часто xml/txt/sqlite -> sql. Реже, что-нить типа Oracle -> MySQL (при прямом использовании SQL менять пришлось бы дофига). У части компонентов бэкенд может вообще отсутствовать. Или данные могут вообще получаться парсингом чужих HTML-страниц :) Случаи разные бывают...

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Мне вот всегда было интересно. Твой ORM это api который использует приложение или все таки есть еще одна прослойка?

И позволяет ли ORM использовать разные бэкенды для разных классов?

Поддерживаються ли отношения между классами?

Поддерживаются ли отношения между классами с разными бэкендами?

baverman ★★★
()
Ответ на: комментарий от baverman

>Твой ORM это api который использует приложение или все таки есть еще одна прослойка?

Можно сказать, что прослойка. Каждый класс имеет метод storage_engine, который возвращает класс, занимающийся наполнением данных. У этого класса, соответственно, есть методы load/save/create, которые вызываются необъектными частями системы (при загрузке объектов или их массивов, создании новых объектов, автосохранении модифицированных и т.п.)

Поскольку пишу под себя, то реально развитый функционал (о котором и пишу выше в основном), реализован только у класса storage_db_mysql_smart. Скажем, OCI/xml/flat-file/sqlite/etc. у меня работает только как тупая читалка/писалка данных (больше не требовалось). mysql-ORM работает напрямую с mysql_* вызовами, но, понятно, легко адаптируется под любой абстрактный драйвер, но это приведёт к снижению производительности, так что нафиг. Это, как раз, не тот уровень, где требуется абстракция, это делается уровнем выше. Более того, всё не соберусь сделать альтернативный, скажем, storage_db_mysql_fast, который будет наполнять объекты данными не через функции сеттеры, как сейчас, а прямо через поля объекта, что тоже будет быстрее. В имеющемся бэкенде так сделать нельзя, так как ряд классов у меня требуют инициализации именно через сеттеры, т.к. сеттеры кроме записи данных ещё и обработку их делают.

>И позволяет ли ORM использовать разные бэкенды для разных классов?


Да, поскольку у каждого класса storage_engine - это метод даже, а не свойство, то вплоть до того, что разные бэкенды могут быть у разных объектов одного класса, например, вычисляемые. На практике такой подход нигде не используется, но потенциально - вполне возможно.

>Поддерживаються ли отношения между классами?


Уточни что конкретно ты имеешь в виду. Если реляционность в смысле загрузки разнородных классов, связанных на уровне БД одним запросом - то нет. Было немало мыслей на тему реализации такого поведения, но по раздумью от них отказался. Например, если у некоего класса, пусть forum_topic есть поле ->user_id, указывающее на ID отдельного объекта пользователя, то было бы интересно сразу джойнить и таблицу юзеров, создавая в ->user() соответствующий объект, но для MySQL это чересчур тяжело. Быстрее (и проще для программиста) после загрузки пройти циклом по результату выборки, собрать все user_id и одним запросом загрузить массив объектов пользователей. Два простых запроса почти всегда в случае mysql лучше, чем один с джойном. Однако это не отменяет того, что загрузка полей объекта возможна из произвольного числа таблиц, и джойны могут входить в условие запроса списка объектов.

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

>> Поддерживаються ли отношения между классами?

> Уточни что конкретно ты имеешь в виду

Уже не важно. Я понял основную идею -- предоставить достаточно простые (в смысле не перегруженные функционалом) обертки для доступа к данным, чтоб каждый раз не заморачиваться с низкоуровневой реализацией.

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

Как правило, в нашем проекте, такие части заменяются потом на сырой SQL. Тут кстати бабка надвое сказала -- я вообще пишу сначала in-memory провайдер, который тщательно тестируется, затем файловый, который отдается клиенту на растерзание. А когда наконец-то устаканятся все поля и список критериев для выборок, только тогда пишется sql-ная реализация. Без всяких ORM'ов. Cурово? Может быть чуть-чуть. Зато клиентский код прекрасно тестируется.

baverman ★★★
()
Ответ на: комментарий от baverman

>Я понял основную идею -- предоставить достаточно простые (в смысле не перегруженные функционалом) обертки для доступа к данным, чтоб каждый раз не заморачиваться с низкоуровневой реализацией.

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

>Просто некоторые товарищи сильно молятся на всякие волшебные штуки


Молиться не на что нельзя :)

>Cурово?


Сурово. Ибо это дорога в один конец. В случае подхода «сделал проект и не трогай его больше» - замечательно работает. Но когда через год-другой приходится что-то менять кардинально - становится проще написать всё с нуля. С моим же подходом я трачу сколько-то [десятков] часов [если они есть] на продумывание общей идеи, которая бы позволила без недопустимого оверхеда отказаться от низкоуровневых реализаций, зато через год на оставленном на это время проекте могу ковырять старый код также, как и в разгаре разработки.

KRoN73 ★★★★★
()
Ответ на: комментарий от baverman

> Делай минимальные интерфейсы для доступа к данным, в качестве бэкенда ЛЮБОЙ ORM.

«Любой» ORM не возвращает тебе Deferred. И это, «любой» ORM не позволит оттягивать общение с БД до последнего, а потом накопившиеся 100 операций по обновлению инфы всунуть в один interaction, interaction всунуть в тред и не париться до его окончания.

shimon ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.