Pull to refresh

Comments 113

Спасибо, как ньюфагу в области очень любопытно :). HL штук никогда не писал, и поэтому использовал ORM как выяснилось бездумно. О том, что ORM может быть не эффективен слышал не раз, но конкретных примеров не видел, просветили.
В добавок с статье хотелось бы поделиться полезнейшим инструментом, который позволяет анализировать, какие запросы к базе выполняются. Называется он django-debug-toolbar. Этот инструмент прямо на отрендеренной странице добавляет меню, через которое можно смотреть некоторую отладочную информацию; кроме того, у него есть еще и режим командной строки, в котором для каждого запроса ORM выводится соответствующая SQL-конструкция. Подробнее — смотрите страницу проекта.
По «Не всегда нужные функции и обоснованные выборки» и FK — для этого придуман select_related.

В чем смысл этого когда?
regions_id = RegionSite.objects.filter(id__lte=10).values_list("id", flat=True)
for n in News.objects.filter(region__id__in=regions_id):
    print n.region_id

если вам нужно:
for n in News.objects.select_related('region').filter(region__id__lte=10):
    print n.region_id


Зачем делать запросы c __id__in? Если вы будете использовать sqlite, то вы быстро привысите максимальную длину запроса.
И с такими запросами вы придумываете join'ы на стороне python'а.
И почему n.region_id, а не n.region.id?
Рассмотрен и тот и тот пример. А так же разница между ними. Внимательней, пожалуйста, читайте… внимательней :(
Рассмотрен и тот и тот пример. А так же разница между ними. Внимательней, пожалуйста, читайте… внимательней :(

Сорри, упустил.

Но если вы сделаете select_related, то вызовы с .id не будут создавать дополнительных запросов =)
Не люблю я select_related, не знаю почему… Внутреннее какое-то отвращение к этой «команде». Но все же ее уважаю, тоже не знаю почему.
В практике почти не использовал. Возьму на более глубокое понимание данной штуки, скорее всего будет полезна в арсенале. Спасибо.
Не люблю я select_related, не знаю почему… Внутреннее какое-то отвращение к этой «команде»

А зря, в большинстве проблемных мест помогает.
Я, чтобы про него не забывать, для часто используемых во вьюхах моделей, создаю кастомный менеджер с методом for_views, где прописан select_related.
В статье написано:
Подготавливаем список интересующих нас id-шников регионов по какому-то абстрактному условию.

ничего личного, лишь пример. Вы верно переделали запрос. Но сломали мысль абзаца.
Вся информация взята по мотивам Django версии 1.3.4

Это же было очень давно =)
В 1.4 и 1.5 довольно таки много изменений касательно orm.
По 1.5 смогу где-то через пол года написать особенности… не раньше. Думаю будет уже достаточной практика. А 1.3.4 не так уж и стар :) Гляньте, например, на bitbucket.org — снизу страницы версии ПО
Гляньте, например, на bitbucket.org — снизу страницы версии ПО

Они по своей сути большая интерпрайзная штука, им можно =)

Как минимум на 1.4 из-за staticfiles перейти стоит =)
а с 1.4 на 1.5 перейти вообще не проблема
Да вот нет, пользователей то перелопатили… Это всё-таки добавляет головняка при переходе.
Если не избавляться от профайлов, то на 1.5 еще можно по старому жить (будет ворнинг просто в стдауте, что профили — депрекейтед), вроди, только в 1.6 профайлы будут до конца выпилены.

Ну а избавиться от профилей — довольно легко, я это через саус миграции за пол часа сделал.
А если уже написана не одна сотня тысяч строк?
Говорим про HL проект, который долго делали и развиваем. А не про мелкие штамповки на пару тысяч строк кода.

Как правило это останавливает к «быстрому» переходу на новую версию.
Говорим про HL проект, который долго делали и развиваем.

Дык всё-равно нужно обновлять, либо быть большими как atlasian и самим делать секьюрити фиксы на используемую старую версию.

А не про мелкие штамповки на пару тысяч строк кода.

Такие проекты как раз обычно никогда не обновляются =)

Как правило это останавливает к «быстрому» переходу на новую версию.

Как правило останавливает лень, непокрытость проекта тестами и использование непубличных api.
UFO just landed and posted this here
Спасибо за разъяснение.
Спасибо за статью. У меня есть несколько замечаний.

Тот факт, что запросы в Django выполняются только тогда, когда мы начинаем итерацию по QuerySet — это особенность Django. С одной стороны удобно, с другой неявно (а мы все знаем, что явное лучше неявного). В общем спорное решение, да и ортогональное ОРМ.vs.SQL.

Приведенная проблема «N+1 запроса» может решаться и средствами ОРМ. Вот, например, в SQLAlchemy есть вариант subqueryload. Т.е. в базу идет 2 запроса — по одному на кажду сущность. Фактически ОРМ за нас делает примерно то же самое, что приведено в статье. А можно прямо в модели указать, что отношение между таблицами должно быть eager, и по умолчанию нужно использовать ровно 2 запроса при выборке серии объектов с зависимостями. Разумеется можно использовать и joinload, получив при этом поведение «как в Django».

В статье я совершенно не увидел ответа на свой вопрос. Так почему же именно Django-ORM? Почему не SQLAlchemy? Т.е. понятно, что Django может быть продуктивнее PHP, но это разве связано с проблемой ОРМ.vs.SQL. Да и в PHP Вы не использовали никаких ОРМ?

А вообще, статья точно про HL-проекты? Вышеназванные проблемы могут подпортить жизнь для очень небольших БД, где до HL еще ого-го как далеко. Вытащить из БД 10К сущностей — нынче это уже HL?
Большое спасибо за замечания. Приму к сведению, буду знать в какую сторону развивать свои навыки.

Не смогу полностью ответить на Ваш вопрос. Я практик. Сейчас все задачи решаются средствами Django ORM. SQLAlchemy для меня пока не является таким явным, как Django ORM. SQLAlchemy я пробую с Pyramid.

По поводу HL — в статье приведены лишь примеры, для понимания тонких мест. Опять же, ничего личного, только хардкор:)
В текущей ситуации максимальная работа с количеством сущностей с иcпользованием ORM — это внутренний инструмент по работе с поставщиками. Там объемы, которые лопатятся — сопоставление 1 млн к 1млн записей…
В продакшене, нужно рассматривать конкретные задачи — для освещения алгоритмов решения
Простите, но как внутренний инструмент может быть HL проектом? Нет, понятно, что база достаточно большая и все такое. Но у Вас что, этим инструментом одновременно пользуется 10К пользователей?
База маленькая — всего около 8Гб инфы (картинки в базе не храню :) )
Нет, тут именно в расчет пускаю скрипты по обработке данных. Задача — как быстро перелопатить данные, чтобы пользователи, сидящие на продакшене не почувствовали что базу выворачивают в текущий момент наизнанку:) Что-то типа того.
Ух ты, мой код попал в статью.

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

Потому что с упрощенный запросом я вижу только решение прогнать всех пользователей через collections.Counter или аналог, не уверен быстрее ли это.

Кстати, в некоторых случаях помогают ещё команды .iterator, .defer и .only. Но последние две могут при неправильном использовании сильно ударить по производительности.
ORM — это часть экосистемы джанги. Её можно легко оторвать, отказавшись в пользу алхимии или чистого SQL, но при этом отказаться и от DRY, тестируемости, мультибазовости, репликаций, миграций и доброго десятка необходимых приложений (south, modeltranslate, tastypie, haystack, taggit, contib.auth, contrib.admin, etc...).

К сожалению увидел в статье больше эмоций, чем глубого понимания ORM как инструмента. А как же select_related, prefetch_related, only?

Проблема с News.__unicode__() решается прописыванием select_related для выборки:
news = News.objects.all().select_related('region').order_by("-date")[:10]

Если не хотите при каджой выборке помнить, что нужно сделать select_related — засуньте его вызов в кастомный метод get_query_set и переопределите менеджер.

Чем плох запрос?
User.objects.values('username', 'email').annotate(cnt=Count('id')).filter(cnt__gt=1).order_by('-cnt')

А например в следующем куске кода есть как минимум 2 проблемы:
regions_id = list(News.objects.all().values_list("region_id", flat=True))
print RegionSite.objects.filter(id__in=regions_id)

Первая — 2 запроса в базу, вторая — если попытаться использовать в качестве ключа для кэширования значение из RegionSite.objects.filter(id__in=regions_id).query, то можно огрести exception, в случае пустого значения regions_id.

Для полноты статье не хватает поругать _clone метод QuerySet'а и пары искуственных тестов с «медленными» джоинами в мускуле на helloworld приложении.
> Её можно легко оторвать, отказавшись в пользу алхимии

— Отрывать не обязательно. Можно совместно юзать. Кроме Алхимии есть еще Storm ORM, Peewee, SQLObject. У последнего легко извлекается SQLBuilder, который можно юзать для построения SQL в Джанге. То же можно сделать и с SQLAlchemy. Впрочем, SQLAlchemy можно использовать и целиком, — с помощью aldjemy библиотеки.
По большому счету во всей джанге только две хорошие вещи: админка и обилие батареек почти на все случаи жизни. Сейчас поднимает голову flask, обрастает сообществом, так сказать. Достойный преемник.
хорошие вещи: админка

Вам просто не приходилось её кастомизировать =(

А orm в джанге хорошая, проще чем sqlalchemy и в большинстве случаев работает отлично.
Смотря что вы подразумеваете под «кастомизировать». Есть несколько хороших батареек, которые позволяют сделать с админкой почти все, что угодно.
ORM в джанге подходит для типовых решений/запросов. Алхимия гораздо более гибкая.
> Вам просто не приходилось её кастомизировать =(

— И апгрейдить эти кастомизации под новые релизы Джанги.
Разве не Pyramid сейчас первый претендент на преемника?
Flask это не преемник, а альтернатива. Преемником он мог бы стать, если бы джанго вдруг прекратила своё развитие и получившуюся нишу должен был бы кто-то заполнить, на данный момент, не думаю, что с джанго идёт миграция пользователей, думаю, многих, очень многих она устраивает. Не вижу причин так не думать т.к. сам пользуюсь Django (и Flask пробовал) и нахожу её прекрасным инструментом для решения своих задач.
Во-первых, про ваши примеры:
list(News.objects.filter(region__in=[1]).values("id")[:10])
list(News.objects.filter(region__id__in=[1]).values("id")[:10])


Смотрим какие запросы генерит этот код:
>>> print News.objects.filter(region__in=[1]).values("id").query
SELECT `news_news`.`id` FROM `news_news` INNER JOIN `news_region` ON (`news_news`.`id` = `news_region`.`news_id`) WHERE `news_region`.`id` IN (1)

>>> print News.objects.filter(region__id__in=[1]).values("id").query
SELECT `news_news`.`id` FROM `news_news` INNER JOIN `news_region` ON (`news_news`.`id` = `news_region`.`news_id`) WHERE `news_region`.`id` IN (1)


Видим, что эти два запроса генерируют одинаковый SQL, поэтому разницы использовать 3 или 4 вариант — нет. Не вводите людей в заблуждение.
Ваш способ расчета времени порадовал. Вы замеряете скорость работы вот так:
Утверждение: Все нечетные числа — простые.
Доказательство: 3 — простое, 5 — простое, 7 — простое и т.д.

И не забудьте, что база умеет кешировать запросы (в том числе и в процессорном кеше).

Во-вторых, мне кажется, что в статье не хватает рассказа о select_related и prefetch_related. Очень полезные штуки.
Спасибо за замечания.
Код одинаковый генерируется во всех 4х случаях. Тут разность именно в том, как быстро он генерируется, а не в том как быстро исполняется SQL.

Обе команды у меня в арсенале, почти, не присутствуют. И понимания по ним у меня нет окончательного. Как говорил выше — будем изучать и есть к чему стремиться. Еще раз спасибо:)
> Тут разность именно в том, как быстро он генерируется
Во-первых, нужно об этом говорить явно. Во-вторых, если это действительно так, зачем вы вообще делает реальные запросы? unicode(queryset.query) было бы достаточно.
Да Вы все верно говорите. Моя ошибка. Сглупил.
Главное не перегнуть с select_related — он очень больно кусается в сочетании с GenericField — происходит JOIN для всех данных в двух таблицах и только потом происходит фильтрация, несмотря на порядок действий. Хотя, после целой грядки граблей из GenericField я максимально сократил использование данного функционала. Вот забавный случай, который смог быстро найти:
Вот такой код (rating_score был related GenericField от модели RatingScore на Book):
book_list.order_by('-rating_score')[:10] # выдаёт 10 книг совершенно не отсортированных по rating_score
book_list.filter(rating_score__gt=-1000).order_by('-rating_score')[:10] # список книг выдаётся ожидаемый

Как показали раскопки — в первом случае из-за того, что таблица rating_score не приджойнена зараннее — сортировка просто игнорируется, но поле всё-таки есть — поэтому Django не выдаёт никакой ошибки.
Ествественно, GenericField в данном случае совершенно некрасивое решение, оно было приемлимо до тех пор, пока мы не стали по нему сортировать, и тут оно стало совершенно вредным решением, но всё-таки ORM тоже неправа.
>>Программист, который ценит свое время

Такой программист как раз и не будет юзать то что даст лишний гемор, ибо больше либ сторонних и софта — больше головной боли и багоправок(а править на HL над 24/7 и быстро).
В статье на самом деле так и не заметил обьяснения почему ОРМ можно юзать для ХЛ. Для сравнения дам вам хороший пример — GAE Datastor API(по сути ОРМ, с очень похожим интерфейсом). НО тот ОРМ и ваш две огромные разницы. Ибо GAE ОРМ написан реально под HL и HA. И суть его сводится не к тому что бы назвать А — Б, а в том что бы спрятать от пользователя всю сложность распределенного хранилища(без которого в HL делать нечего).
А теперь про ваш код. У вас он написан для одного сервера. А теперь представте что у вас 100 серверов бд с распределенными данными, Насколько легко ваш код перейдет от 1 до 100 серверов, от 100 к 10000? Если с напрягом, то какой смысл в абстракциях которые лишь подменяют А на Б, но фактически не делают рост программы простым?
Во вторых вы говорите об экономии времени. А сколько ушло у вас времени на разбор всех нюансов в ОРМ, а сколько уходит когда выходит новая версия?

Теперь что насчет Django, стандартный сайтик на нем дерит около 30-70RPS. Точно такой же сайтик к примеру на Gevent держит 800-1500RPS.
Конечно можно скрестить ежа со слоном, но зачем страдать фигней когда можно изначально писать проще, быстрее и с лучшим результатом.
Django это как wordpress, писать на нем сложные проекты можно конечно, но лучше реально написать с нуля.

Я уже молчу о том что есть куча всяких фигонь которые нужно контролировать в HL. Django это по сути как пхп запрос-ответ, а если надо демон? На Gevent к примеру я могу реализовать все в рамках одной архитектуры без граблей.

Короче Django да еще и со своей ОРМ это и близко не HL тулзы.
Теперь что насчет Django, стандартный сайтик на нем дерит около 30-70RPS. Точно такой же сайтик к примеру на Gevent держит 800-1500RPS.

Можно пруфы? И будет явно не только gevent, а ещё много тулз или велосипедов. На одном http сервере сайтик не сделаешь =) И что эти тулзы и велосипеды будут работать быстрее и лучше, чем dajngo — далеко не факт. Вот tornado, то да =)

Короче Django да еще и со своей ОРМ это и близко не HL тулзы.

instagram не hl?)
На gevente счас крутсятся большинство проектов на python. До этого был крут twisted, но когда сделали гринлеты все начали с него слазить ибо колбеки реально делали спагетти из кода, + твистед медленнее, но правда имеет хорошую протокольную базу.
Вы не путайте Twisted и Gevent с Django. Это абсолютно разные фреймы. Джанго это фрейм как пышный зенд, а твистед и гивент как например пхпДемон. Тоесть на них пишут сервера, а не сайтики.

Вы конечне простите но мой один хттп сервер держит больше чем пачка джанг. Вопрос нахрена платить больше? А еще и заниматся администрированием 10 машин и 200 машин разница существенная.

Instagram не имеет сложной логики работы, отчего фронт можно писать хоть на пыхе. Вся нагрузка ложится на Амазон. Тем более посмотрите парк машин довольно не мало. Это Раз.
Два. HL это не много юзеров как многие считают. Я могу иметь миллион юзеров и миллиард серверов и это не будет HL. HL это когда вместо купить еще 100 серверов делают оптимизацию, и когда на одном сервере крутится без напряга лям юзеров.
Три. Я не видел что и как они юзают. Вот FB говорит что юзает PHP, но их инженеры по секрету грят что это пиздежь полный и PHP там особой роли не играет.
Четыре. Я писал проекты на Django и занимался оптимизацией их… но хорошего в этом мало, много работы на том что обыно вообще делать не надо.
Вы не путайте Twisted и Gevent с Django. Это абсолютно разные фреймы.

Это я знаю =) Просто обычную рендерелку страничек и crud api'шку куда проще сделать на django, чем используя gevent.

Вы конечне простите но мой один хттп сервер держит больше чем пачка джанг. Вопрос нахрена платить больше? А еще и заниматся администрированием 10 машин и 200 машин разница существенная.

Это редко когда нужно, большинство проектов на django и из одной машину никогда не вылезут =) Их нет смысла делать на tornado/gevent/twisted/etc.

Немного оффтопа, а как вы к tornado относитесь? Пробовали его в нагруженных-нагруженных проектах?

Я дописывал торнадо до первой версии, было много траблов с ивентлупом и блокировками. Как счас обстоят дела с проектом хз. Но Gevent в любом случае лучше.

Обычную рендерилку как раз легче делать не на джанго. Ибо я написал 5 методов, запустил обычный хттп сервак гивента и вуаля. А Джанго это дох апи, поднятие гуниеорна или нджинкса с модулями под wsgi.
Я дописывал торнадо до первой версии, было много траблов с ивентлупом и блокировками.

А gevent каким образом поможет избежать локов?

А Джанго это дох апи, поднятие гуниеорна или нджинкса с модулями под wsgi.

Но ведь wsgi, gunicorn и nginx лёгкие и производительные штуки.

А gevent у вас без wsgi и просто смотрит наружу?
Да просто смотрит наружу, если не надо балансировки.

WSGI это прооко связи между тем же nginx, apache с python скриптом. gunicorn отдельный сервак написаный на питоне, является демоном и wsgi не юзает а запускает скриипты в процессах на прямую.

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

Но ведь тогда у вас будет загружено только одно ядро (если не считать нагрузку от базы, очередей и тд)

Торнадовский луп бажил, в версии до 1

Сейчас там с ним всё в порядке.

дофига грязных хаков что бы оно не висло

Но gevent ведь это апогей грязных хаков =)
>> Но gevent ведь это апогей грязных хаков =)
Я тоже так думал. А потом понял, что «it just works». Простой линейный код — это очень ценно.
UFO just landed and posted this here
Вот FB говорит что юзает PHP, но их инженеры по секрету грят что это пиздежь полный и PHP там особой роли не играет
насколько мне известно, они используют hiphop, а это не совсем PHP в его типичном понимании. Да и даже если PHP, то ничего удивительно — такие проекты как Wikipedia или VK тоже крутятся на PHP и нормуль
UFO just landed and posted this here
>>Вопрос. Как вам поможет Gevent в данном случае? Какая разница синхронная или асинхронная модель обработки запроса используется, если все равно нужно ждать порцию данных из базы/кэша?

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

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

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

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

>>У вас же не сразу раз и проект стал высоконагруженным

Ах если бы. Проекты разрабатываются под планируемые нагрузки. Я делаю игру и планирую что перый онлайн будет 5к, это значит что система должна держать эту нагрузку на текущих мощностях. Я не пишу игру под 10 человек, потом переписываю под 20 и т.п.
Любая промашка на момент построения архитектуры стоит очень дорого потом. Ибо к примеру вам нужно полностью изменить модель хранения днных дабы повзолить держать большую нагрузку. Вам нужно провести миграцию юзеров с другой модели на новую в риалтайме при загруженных серверах, без доп мощностей, да еще и модели отображаются далеко не просто. А иногда просто архитектура приложения делает невозможным дальнейший рост.
Поэтому HL это в первую очередь грамотная архитектура которая учитывает рост до хреновой тучи юзеров изначально. Иначе прийдется полностью все переписывать. это факт.

>>Опять же непонятно какая разница 1 сервер или 1000?

Разница в том что работать с 1 сераком не трабл. А вот когда у вас данные наодятся на 50 сервера а всего у вас их 1000 и вы еще не 100% знаете на каком сервере что лежит и один запрос Select * from A; может выоиваться в 20 запросов 3 мап/редюса и обьединения, то вот тогда ОРМ идет лесом, ибо она на это тупо не расчитана.
Я конечно понимаю что все привыкли что есть база и туда хренячим данные, но в реальности модель хранения данных может быть очень сложная, а иногда еще и мультиуровневая например часть в легком кеше, часть в реляционке и часть на ФС.

Я не люблю юзать продукты которые говорят что они везде помогут. Вопервых это вранье, а во вторых качество таких продуктов всегда низкое. Ибо это всеравно что сравнивать всесезонную резнину с шиповками в цепях. Для общей езды вроде всесезонка подходит, но когда начинается хард вам жопа.
UFO just landed and posted this here
> Gevent — это хак, на крайний случай. Его используют для патчинга сокетов и обхода отсуствия в Python асинхронной модели обработки.

А вот это вы зря. Его можно использовать как хак, но совсем необязательно. Если использовать библиотеки, которые сразу написаны на gevent, то никаких манкипатчингов не понадобится. Проблема лишь в том, что таких библиотек мало, но их и для твистеда днём с огнём не сыщешь.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Парсинг запросов, генерацию ответов, куки, сесси и прочее — у вас это вызывает страх?
Куки есть. Сессии в кеше. Генерация ответов как хотите так и шлите любой протокол канает, а не тока хттп. Парсинг запросов вообще смешно, 10 строк кода, если не надо чего то извращенного.
Вообщем все реализуется вами и вашими руками, за счет этого вы не офигиваете от того что в какой то момент весь проект падает потому что какойто идиот в коде джанги написал какуюто муть(а такой хрени я видел много).

WSGI это внутренний протокол между серваком и обработчиком. Гивенту не надо этого он вертится как демон(й меня свой сервак по типу гуникорна, тока смысл немного другой) и все. Это собственно лишает необходимости еще одного звена в софте.

Люди да что вы млин к этим фреймам привязались. Или писать руками нынче уже не в почете. Куда не глянь вместо Программист пишут «Django developer», «Zend developer» и прочее… Это из серии «Если у вас в руках молоток то все проблемы кажутся гвоздями»
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
regions_id = list(News.objects.all().values_list("region_id", flat=True))
print RegionSite.objects.filter(id__in=set(regions_id)))

Зачем использовать set() когда есть .distinct()?

regions_id = list(News.objects.all().distinct().values_list("region_id", flat=True))
print RegionSite.objects.filter(id__in=regions_id))
Лишняя штука, которая переносит выборку данных на плечи базы. Я как раз от такого пытаюсь избавляться.
На стороне базы это пройдёт быстрее, чем на стороне python'а =)
Ну это имеет смысл есть БД сервер у нас 1, а python воркеров много и размазаны они на 3-4 сервера.
Но в обычных ситуациях конечно лучше отдать все на съедение базе.
.distinct() дешевая операция и есть мысли что это выгоднее чем посылать лишние байты по сети, а потом еще их обрабатывать на app машине.
Вопрос то к чему, мне интересно, это реально помогает или просто оптимизация с потолка?
на неделе протестирую, у меня есть много моментов где я сетом пользуюсь чаще чем distinct() и тогда можно будет судить. вообще, поднял инфу в сети — везде все склоняются к distinct().
UFO just landed and posted this here
Есть система с выборкой на основе множества фильтров, простых и не очень. Гораздо проще реализовать это с помощью орм, чем вручную склеивать запросы.

Конечно в определенный момент настанет потолок возможностей орм, но у меня в этом проекте еще на настал.
А никто и не говорил, что нужно «склеивать запросы вручную».
Ну, например, различного рода DSL-и для построения SQL запросов. Для Python сходу не назову, но вот пример для Java. Или для Ruby. Или вот мой собственный велосипед для Clojure.
А чем он лучше написания SQL в строках?
Не нужно заморачиваться с порядком склеивания. Добавляешь условия к объекту запроса в любом удобном месте по коду. При выполнении он сам все склеит как надо.
С другой стороны, он также, как и ORM занимает время на генерацию запроса.
Время на генерацию запроса минимально в сравнении со временем его выполнения. Особенно если учесть, что на сервере БД его надо еще распарсить (в любом случае, даже с применением ОРМ).
Ну, собственно, да. При потолке орм буду использовать DSL. Но в исходном комментарии был выбор только между orm и sql:)
UFO just landed and posted this here
В общем проблема в терминологии. Разобрались, вопросов нет:)
Мне не приходилось использовать SQLAlchemy. Я так понимаю вы про неё говорите как замену джанго-орм?
Если да, то как в джанге работать с формами, админкой, обработкой данных полученых из ОРМ… и всем тем где вызывается класс модели и потом с ним работается по сути средствами ОРМ? Если мы заменяем орм на SQLAlchemy.
UFO just landed and posted this here
Полистаю вечерком. А в двух словах можете подсказать, если есть опыт.
1. Почему тогда не делать прямые запросы в бд, если основным из аспектов SQLAlchemy сложные/более_гибкие запросы(если я правильно понял её направление). То есть на простые и типовые задачи оставить джанго-орм, а на сложные запросы не реализуемые ОРМ, делать прямые запросы в БД в обход ОРМ.
2. Работает ли SQLAlchemy с хранимыми процедурами, триггерами, евентами и тем многообразием функционала самой базы данных, с которыми не работает джанго-орм?
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Спасибо за развёрнутые ответы, буду читать и думать.
Начну с классической ошибки, которая меня преследовала довольно долго. Я очень сильно верил во всемогучесть Django ORM, а именно в Klass.objects.all()

Как же вы могли это пропустить, если это написано в любом туториале и любой документации. Какой хайлоад с таким отношением к делу.

Попробуем поиграться с шаблонами, и любимой некоторыми функцией __unicode__().

При чем тут эта функция. Все описанное к ней напрямую не относится.

Нас так просто не возьмешь — всхлипнули мы и воспользовались следующей отличной возможностью ORM — .values().

Только это уже не возможность ORM, а возможность избавится от ORM. Обычно используется, когда узкое место — именно создание инстансов. Зачем вы решили использовать её против лишних запросов — непостижимо.

Для этого забъем на наши предрассудки по количеству запросов и в срочном порядке увеличиваем количество запросов в 2 раза! А именно, займемся подготовкой данных:

Лишь бы свое, да с квадратными колесами. prefetch_related() же есть.

Получившийся результат вставляем в наш новостной запрос и получаем: Запрос в запросе! Уууух, обожаю :) Вы конечно же понимаете чем это грозит? :) Если нет — то поймите — ничем, совершенно ни чем хорошим!

Не понимаю. Нет правда, объясните. Мне-то казалось, что лучше так, чем гонять список айдишников из базы в Питон, а потом обратно.

так он еще взял откуда-то limit 21. Про лимит все просто — так устроен print большого количества значений массива

Так устроен repr() кверисета.
prefetch_related — идет с версии 1.4
Эта версия не подходит для хайлоада? Или почему вы считаете что её не нужно рассматривать? Ну и 1.3 уже не поддерживается.
подходит, но все же я перескачу до 1.5 или 1.6… там будет (или уже есть) класный патч, который время коннекта до SQL увеличивает.
Сложность перехода, помимо пользователей (1.4->1.5), урлы… вот в чем жесть — такой труд… и ничего не пропустить бы:)
+ структура файлов поменялась (к лучшему).
Переход запланирован. Сейчас только второй проект устаканю на версии 1.5, изучу особенности и буду переводить текущий. Нужно!
урлы… вот в чем жесть

Можно, оставаясь на 1.4, плавно переносить с {% load url from future %} =)
ага, но думаю что возьму себя в руки и сразу на 1.5 перейду, а может и 1.6… Тут подсказали, что в 1.5 пользователи еще не до конца допилены. Так-то, примерно дня 2-3 работы и, думаю, можно перевести проект на 1.5… Возможно, конечно, ошибаюсь — но я оптимист:)
Т.е. вы считаете что применять 1.4 не стоит потому что конкретно вы на нее еще не перешли? Или как воспринимать этот ваш рассказ в ответ на вопрос «что в 1.4 плохого?»?
Я конкретно не перевел проект на версию старше 1.3.4.
И считаю что это не преграда всем остальным проектам использовать более старшие версии.
Дак вы объясните, чем грозит запрос в запросу в условии where?
Ничем, если выборка небольшая…
Сейчас скажу абстрактную вещь, взятую из головы… когда тесты делал, у меня получалось что запрос-в-запросе как-то в разы медленней крутится, чем с подготовленными данными. Заметным отставание становится когда, например, выбираешь 200 тыс записей из первой таблицы, при этом во вложенном запросе у тебя выбирается порядка 60 тыс записей.
Яж практик, больше чем теоретик, делаю выводы из своего опыта.

select id, name from table1 where id2 in (select id from table 2)


про такого рода запросы я говорю. Если я не прав, конечно же подправте
Как выше уже заметили, Ваши способы тестирования производительности вызывают легкое недоверие.
+1 Вам в карму

для меня, если честно, конечный показатель — это время генерации страницы, которое мне отдается в логах uwsgi, например вот:
image

а вот если промелькнет красная строчка — то это ппц… а они промелькивают. Это значит что время генерации страницы более 1,5 сек… и нужно что-то предпринимать, чтобы такого не было :)
Кстати да, 0,91 сек на главную — это тоже довольно много.
select id, name from table1 where id2 in (select id from table 2)


Не знаю про mysql, но postgresql такой запрос «раскроет» и всё будет быстро и на бОльших базах.
Стоит отметить, что у mysql 5.x были проблемы с использованием индекса при подзапросе в условии. И мне тоже приходилось связывать две таблички через приложение, чтобы запрос использовал индекс, а не фулскан.

ps slava-technical.blogspot.ru/2010/06/mysql-indexes-on-subqueries.html
bugs.mysql.com/bug.php?id=9021
JOIN новостей и регионов на стороне питона доставляет :)
Один запрос с JOIN-ом на базе будет быстрее чем 2 запроса и JOIN на стороне питона как минимум из-за того что не надо 2 раза гонять данные.

Такой подход вообще не вяжется с названием топика.
Прочитайте еще раз внимательно… Все хорошо и в меру, пока у вас не появятся более одного джойна. Я рассматриваю небольшие примеры, показывая суть проблемы. Показываю их из личного опыта. А на личном опыте теория очень часто расходится с практикой, за счет небольших, но множественных особенностей
Как уже выше написали, я очень зря не учел еще 2 отличные команды. Спасибо за советы — будем совершенствоваться. (prefetch_related() и select_related() )
Может быть ситуация, что лучше пускай питон в 2 раза дольше работает, чем мускул на 10%. Хотя, конечно, оговаривать такое нужно.
такое тоже бывает оправдано. + порой у нас не заканчивается все на простых связях, которые могут оправдать нашу задачами одними JOIN'ами. Все зависит от ситуаций
Нет. В данном случае «база отработает один запрос за время X» vs «база отрботает 2 запроса за суммарное время X + мы по сети гоняем больше данных + нагружаем питон».
Сомнительно, что это верно на любых выборках — например, приджойненная таблица имеет две записи, а основная — миллиарды. Да и по сети может гоняться больше данных в случае запроса с джойном. FK — несколько байт, а его разворачивание в строке результата может занимать на порядки больше объема.

В общем данных только о схеме, по-моему, недостаточно, чтобы сказать «это будет медленно». Нужно знать реальные данные.
Один запрос с JOIN-ом на базе будет быстрее чем 2 запроса и JOIN на стороне питона как минимум из-за того что не надо 2 раза гонять данные.
Вот кстати совершенно не факт. Смотря что за БД используется. Если отталкиваться от MySQL, то есть ситуации когда вместо JOIN лучше сделать два запроса и склеить данные в коде
Все хорошо и в меру, пока у вас не появятся более одного джойна

Не совсем понял что вы этим хотите сказать: что такой подход обоснован для большого числа JOIN-ов?
То что я хотел донести своим комментарием — не надо пытаться делать работу базы данных.
У ORM одна проблема, его часто используют люди которые про СУБД слышали краем уха. В итоге вместо того чтобы сначала спроектировать БД, они сначала строят объектную модель, в итоге получают неудачно спроектированную БД с нарушением нормальных форм, а еще и ходят туда как в тупое хранилище. А потом появляются статьи вида «что же делать как нам быть». Хотя ответ прост, сначала ознакомится с реляционной теорией, узнать что такое нормальные формы, затем уже спроектировать БД и только после того как есть понимание как оно там начать использовать ORM. Дополнительно надо не забыть узнавать про жадную загрузку и оптимизацию запросов с указанием использования join вместо пачки запросов к СУБД. Нормальные ORM это позволяют делать. Как и позволяют ходить в СУБД нативным SQL.
Sign up to leave a comment.

Articles