Тёмный
No video :(

Оптимизация Django. 6 - Celery таски - отложенный пересчет, SingleTone 

Senior Pomidor Developer
Подписаться 22 тыс.
Просмотров 9 тыс.
50% 1

Опубликовано:

 

5 сен 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 73   
@shevladislav
@shevladislav Год назад
Обычно вообще не пишу комментарии, оставляю лайки и т.д. В общем хотел бы поделиться своим личным наблюдением. Уже где-то около годика учу питон, джангу и все что с этого вытекает. Пересмотрел просто огромное количество курсов как платных так и бесплатных, книжек, статей. Пока что из всего что я видел твой курс, однозначно, лучший (как этот, так и предыдущий). Во всех же остальных курсах, как правило, просто освещают какие-то базовые аспекты, которые в доке можно нарыть за 1 день, если не парочку часов) Я и раньше натыкался на твой, канал, когда еще мало в чем понимал и меня отпугнуло, на тот момент, количество просмотров и твоя подача. Сейчас заметил тенденцию, что как раз каналы на которых очень мало просмотров ( по меркам современного ютуба ) дают наиболее качественную инфу. Ну в каком-то роде я даже рад этому, меньше людей знают про такие качественные каналы, меньше конкурентов будет среди джунов (а их сейчас ой как не мало), с другой стороны, желаю тебе больше просмотров, как качественному контентмейкеру)
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Спасибо за такой хороший отзыв! Да, я никак не раскручиваю канал и на нем не зарабатываю . Я понимаю почему большие каналы часто делают контент хуже. У меня есть несколько любимых (не IT) каналов , иногда смотрю видео и думаю, типа «высосано из пальца», вообще можно было не снимать, идей почти ноль, а потом рекламная интеграция. Типа делают хоть что-то , чтобы рекламу отбить. Сложно делать регулярно видео и оставаться на хорошем уровне.
@MrVernuk
@MrVernuk Год назад
Спасибо, ценная информация! Очень нравится, что все делается не спеша и хорошо объясняется. В большинство видео, которое видел, обычно все рассказывается быстро и не все сразу можно уловить. Тут же создается специально проблема, которую решают и при это рассказывают, как делать нужно, как не нужно, зачем то, зачем это. Респект!
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Спасибо вам!
@Dlphin-nm7di
@Dlphin-nm7di Год назад
Очень полезные уроки. Спасибо большое!🔥🔥🔥
@weyzem
@weyzem Год назад
Когда меняешь Docker-compose Ребилд делать не нужно, а просто перезапустить. Ты когда пишешь docker-compose up, он понимает, какой именно ты файл запускаешь. То, что ты пишешь в docker-compose.yml - это просто настройки. Билдить нужно только тогда, когда ты добавляешь новые библиотеки в проект или новые сервисы.
@NARUTO58958
@NARUTO58958 Год назад
Благодарю за качественный контент и замечательную подачу материала!
@andreykuskov8807
@andreykuskov8807 Год назад
Спасибо, узнал еще один хороший кейс с Celery
@begula_chan
@begula_chan Год назад
Благодарю за видео! Исходя из последних видео, я так понял, что можно использовать prefetch_related к FK и был очень удивлен.
@eugene_mountainland
@eugene_mountainland 7 месяцев назад
Очень классно, спасибо большое)
@pg_7v
@pg_7v Год назад
Спасибо! celery_singleton, это то решение о котором я спрашивал в комментарии к предыдущему видео. Правда, у вас очень качественный контент!
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Я так и понял)
@the_codest
@the_codest Год назад
С синглтоном немного непонятен смысл. Получается, менеджер действительно ошибся, указав не то значение, но успел нажать Save. Потом тут же поменял значение, снова сохранил и довольный жизнью закрыл админку. А под капотом в реальности цена подписок не меняется, хотя у самого Service цена изменилась.
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Это может быть и два менеджера, которые одновременно редактируют объект. А вообще , таски запускаются асинхронно и та, которую поставили в очередь позже, может вроде запуститься раньше. И с этим тоже приходится жить )
@jamjam3337
@jamjam3337 6 месяцев назад
спасибо!😎
@the_codest
@the_codest Год назад
Сказать, что автор - молодец, значит ничего не сказать! Огромное спасибо за материал! Однако же, вставлю свои 5 копеек (исправьте, где не прав, плз): 1) "метод save() возвращает None, поэтому return не нужен." Я ОШИБСЯ. Извините. Возвращает. Странно, в доке по Джанго везде вызывают родительский save() без return. 2) Сперва нужно вызвать super().save(), а потом только запускать таски. Иначе таски дергают старый plan.discount_percent 3) А нафига же сначала запускать цикл по self.subscriptions.all()? Получается, цикл вызывает запрос просто чтобы получить айдишники (тогда хотя бы получать только айдишники с помощью "self.subscriptions.values_list('id', flat=True)"), а потом еще и таски на каждый полученный айдишник дергают из БД subscription по этому айдишнику! Не лучше ли переписать таску, чтобы она получала только айди самого экземпляра Plan, а уже внутри получала запись Plan из БД, подтягивала под него кверисет и работала напрямую с ним? 3.1) В таком случае в таске не надо будет циклом обращаться к "subscription.plan.discount_percent", ведь мы его один раз возьмем из экземпляра Plan. 3.2) Кверисет для самой таски тоже бы оптимизировать, например ".subscriptions.select_related('service').only('service__full_price', 'plan_id')". Хотя, если по совести, то к service__full_price тоже обращаться смысла нет: он же не изменился. Так что даже проще - включаем математику и всего лишь изменяем прайс сабскрипшна на столько, на сколько изменился скидочный процент )) 3.3) Внутри таски - вместо сохранения в цикле каждой записи subscription бахнем Subscription.objects.bulk_update(subscription_list, fields=['price'])! Ибо нефиг! 4) Насколько мне известно, Джанго не рекомендует ковыряться в __init__(). Вместо этого можно влезть в метод "from_db": @classmethod def from_db(cls, db, field_names, values): instance = super().from_db(db, field_names, values) instance._discount_percent = instance.discount_percent return instance def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.discount_percent != getattr(self, '_discount_percent', None): set_price.delay(self.id)
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Спасибо за такой обширный комментарий. К сожалению, сейчас нет времени чтобы ответить по каждому пункту . Согласен, что не все там сделано идеально. Многие вещи специально упрощены, на какие-то просто не хватило времени чтобы додумать. В следующих курсах постараюсь учесть больше нюансов
@Fr3PO4
@Fr3PO4 Год назад
По поводу пункта 2. Если super().save() надо вызывать раньше, почему тогда всё работает итак и цены обновляются с новым значением дисконта?
@the_codest
@the_codest Год назад
@@Fr3PO4 Хз, у меня брало старое значение. Возможно, какие-то различия в запуске самой таски. Но, по всей здравой логике, если мы не вызвали save от родителя, то мы не сохранили значение в БД. А таска при выполнении обращается к тому самому НЕсохраненному плану. Видимо, если таска запускается с опозданием (sleep), то план, конечно, успевает сохраняться.
@Fr3PO4
@Fr3PO4 Год назад
@@the_codest Понял, спасибо!
@user-rs1kn6zc2w
@user-rs1kn6zc2w Год назад
Вечер добрый. Неплохое видео, но использовать сигналы в данной реализации было бы правильней всего.
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Добрый вечер. А почему?
@user-rs1kn6zc2w
@user-rs1kn6zc2w Год назад
@@SeniorPomidorDeveloper Так это специально отведенный функционал для реализации каких либо событий произошедших в системе, таким образом не придется ни чего ломать и перегружать. Например на этапе сохранения или обновление, описаны готовые события, таким образом все taski (что будут выполняться при создании или изменении) будут лежать в одном месте, их не придется размазывать по всему проекту в modal или views.
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Это один из вариантов архитектуры. Мне не нравится сигналы за их неявность. Это немного не по питонски. Вызовы происходят синхронно, а в коде их не видно. Также сам смысл пересчета по сохранению мне не нравится , надо привязывать их к изменениям конкретных полей, чтобы не запускать их впустую. На сигналах такое не получится реализовать, то решение, которое нашел , делает это через дополнительный запрос в базу. Если говорить про способ переопределения , то он ничего не ломает. Во многом разработка на DRF происходит способом переопределения, ничего плохого не вижу.
@esofdes
@esofdes Год назад
@@SeniorPomidorDeveloper зато в реализации с сигналами не будет ошибки, которую встретил я. У меня воркер запускается раньше, чем база отрабатывает сохранение данных. По итогу если убрать time.sleep(5), то для расчёта используются старые данные. Не знаю, почему у вас на видео всё хорошо, но у меня воркер отрабатывает слишком быстро. Не спасает даже перенос функции save().super() выше save_price.delay()🥲 Может, я что-то сделал не так, но пока что у меня вызывает недоумение подобное использование Celery UPD: Не знаю, можно ли оставлять ссылки под видео, поэтому просто скажу, что есть целая отдельная статья, посвящённая асинхронному доступу к БД. Там предлагается использовать функцию "django.db.transaction.on_commit" Применимо к данной ситуации функцию я переписал следующим образом: def save(self, *args, **kwargs): result = super().save(*args, **kwargs) if self.__full_price != self.full_price: transaction.on_commit(lambda: [set_price.delay(subscription.id) for subscription in self.subscriptions.all()]) Хотя, возможно, корректнее было бы сделать две отдельных таски для изменения плана и изменение сервиса, чтоб не вызывать кучу функций внутри лямбда-функции
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
@@esofdes Да, "on_commit" использовать надо, для того чтобы таска запустилась не в процессе save() а по завершению коммита в базу. Если я этого не добавил в курс, то это моя ошибка, забыл видимо. На работе мы это используем постоянно. Что касается сигналов, то они наверное тоже решают проблему, так-как запускаются после коммита в базу. Но это не делает их использлвание обязательным. Сигналы я вроде тоже в курсе использую для on_delete, но вцелом мне не нравится это архитектурное решение, я писал выше - почему.
@clauseclause6640
@clauseclause6640 Год назад
Если поменять план (не процент по нему, а именно привязать новый план) цена не пересчитается
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Хорошее замечание!
@user-gm8gh2gp2v
@user-gm8gh2gp2v 9 месяцев назад
Очень бы хотелось один момент добавить по оптимизации - поскольку мы обновляем только одно поле в subscription, то нам необязательно писать просто save(), да и чаще всего я сам прошу коллег указывать явно поля, поэтому для явности и облегчения запроса лучше сделать subscription.save(update_fields=['price']). И ещё один момент, который сейчас пришёл в голову - для подобных тасок, которые работают с множеством инстансов модели - может быть, есть смысл попробовать bulk_update?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 9 месяцев назад
Да, я вроде рассказывал про update fields, наверное в других видео. Bulk update имеет смысл чтобы не делать save в интерации, но незнаю насколько он легче/быстрее делает апдейт, надо какое-то сравнительное тестирование сделать или почитать о нем. Но если апдейт просто одним запросом по фильтру , так конечно лучше
@user-gm8gh2gp2v
@user-gm8gh2gp2v 9 месяцев назад
@@SeniorPomidorDeveloper чатбот сказал, что на больших данных bulk_update обещает быть быстрее, более детально я не влезал ещё) В целом спасибо за курс, просто отлично рассказываешь и по делу, нравится!
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 9 месяцев назад
@user-gm8gh2gp2v по сравнению с save, наверное да. Но это на моей практике, редкий кейс. Если надо один объект сохранять то достаточно save, если группу по признаку, тут нужен filter().update() как один запрос. Где-нибудь в парсерах , наверное будет актуален bulk create и bulk update, мне редко приходится их писать
@gonfrix9382
@gonfrix9382 7 месяцев назад
А также неудобно в консоли запросы отслеживать. Есть ли какие то альтернативные варианты это делать, кроме использования debug toolbar и django silk?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 7 месяцев назад
Даже не знаю, это просто текст. Может и есть какие-то варианты. Напишите если найдете.
@gonfrix9382
@gonfrix9382 7 месяцев назад
Спасибо большое за все@@SeniorPomidorDeveloper
@shaxzodnizamov9773
@shaxzodnizamov9773 Год назад
Огромное спасибо за труд, очень нравится подача материала. Но вот есть пару моментов: 1) раз мы запомнили старый price, не легче использовать сигналы? Там и старые данные имеется 2) мы получали список подписок и использовали только id, может стоит из БД вытаскивать только id 3) мы через цикл создавали таски для каждой подписки. Может в таск передать список id подписок. 4) в таске если будем принимать список id, можно через фильтр получить нужные подписки и вызвать update после аннотации. . update(price=F(‘annotated_price’) Можно так сделать ?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Спасибо! 1) не очень люблю сигналы. Они работают синхронно , но не очевидно, когда их много то сложно отследить что откуда вызывается , сложно дебажить . Проще на save повесить или в mixin. Там в следующих видео я буду использовать один , но без него там было не обойтись . 2) думаю это хорошо, если так можно 3) считаю что это плохая практика. От количества данных могут быть проблемы с памятью . Типа если в аргумент передать список длинной в 100.000 id. 4) про annotate().update() идея хорошая
@shaxzodnizamov9773
@shaxzodnizamov9773 Год назад
3) в нашем случае, можно переделать таску под план и передать только id плана и в таске через annotate и update одним запросом решить. Так у нас не будет 100,000 задач
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Можно и так. По разному можно.
@user-kn5ip9lr6r
@user-kn5ip9lr6r 10 месяцев назад
У меня вопрос. А можно было бы не переписывать save-методы для моделей, а создать signals, которые бы срабатывали при изменении полей full_price(модель service) и discount_percent(модель Plan). Или лучше все таки переписывать save для моделей?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 10 месяцев назад
Сигналы не срабатывают на конкретных полях, только на сохранение и удаление модели . В остальном не вижу разницы save и сигналов , вопрос организации кода в проекте.
@user-kn5ip9lr6r
@user-kn5ip9lr6r 10 месяцев назад
@@SeniorPomidorDeveloper вот как! Спасибо, я точно знаю, что сигналы срабатывают на изменение поля many-to-many-field и почему-то подумал, что на изменение каждого поля можно применять сигналы. В какой-то книге читал, там автор советует оставлять модели максимально простыми и не нагружать их методы, а все выносить в сигналы. Иначе потом, когда проект становится большим, их трудно воспринимать на глаз.
@ibrahimoglu
@ibrahimoglu Год назад
👍
@hhhscvx
@hhhscvx 3 месяца назад
Почему-то что бы ни делал - таска сразу проваливается в Failed. Есть идеи в чем может быть проблема? Ваш код 10 раз перекопипастил
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 3 месяца назад
В логи надо копать , может там написано. Сюда не удобно копипастить, у нас есть группа, ссылка в профиле, может там кто-то поможет понять.
@user-kn5ip9lr6r
@user-kn5ip9lr6r 9 месяцев назад
У меня еще один вопрос. Допустим, на сайте мы даем возможность пользователю самостоятельно загружать видео из своего ПК. И это тяжелое задание, которое лучше бы отправить в celery, чтобы человек не ждал, когда видео загрузится, а сразу перешел на нужную нам страницу. Для этого у нас в модели есть поле FileField. Как в этом случае поступить? Можно просто добавить декоратор @shared_task к методу save( ) нашей модели? Или нам нужно переопределить save-метод, вычленив загрузку видео в отдельную функцию и отправив непосредственно ее в celery а не весь save? Буду очень признателен за подсказку.
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 9 месяцев назад
Хм.. думаю что в этом случае никак. Мы можем вынести в таску то, что как бы уже есть у нас в приложении или то , к чему из приложения мы имеем доступ. Если пользователь делает upload ему ни закрывать вкладку , ни прерывать соединение с сайтом, все это происходит синхронно. Есть другие варианты это оптимизировать, но это не при помощи celery
@Ch1ck3nWTF
@Ch1ck3nWTF Год назад
Привет. А есть ли смысл при запросе из вьюхи SubscriptionView с перефетчем заюзать select_related по нужным моделям? Как-нибудь так: Subscription.objects.select_related('plan', 'service', 'client') ... bla-bla bla. А потом в питоне уже посчитать total price курсов. Там, вроде, всего один большой запрос получится. Или я что-то не так понимаю?)
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Можно. Но один большой запрос (с кучей джойнов) может быть намного тяжелее нескольких маленьких. Нет универсального правильного решения, смотря как данные устроены и какое их количество
@user-dz5hi6mu1x
@user-dz5hi6mu1x 8 месяцев назад
Не совсем понимаю, делал, как у Вас, но в конце вместо одной таски все равно две остаются почему-то, если быстро менять скидку тарифного плана. У меня только одна подписка с таким планом, не понимаю в чем дело
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 8 месяцев назад
Сложно так сказать. Попробуйте с кодом из моего проекта github.com/chepe4pi/service_app
@KonstantinePotapov
@KonstantinePotapov Год назад
стоит наверно сначала вызвать save моделек, а потом запускать селери воркер, иначе он в теории может отработать на старых данных. result = super().save(*args, **kwargs) start_task.delay(self.blabla.id) return result
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Ну там между delay и save думаю разница будет сотые секунды. Не запустится таск раньше. Но я согласен с ходом мысли. Действительно, логично было бы сделать запуск таска после.
@user-ku4qk2cb5p
@user-ku4qk2cb5p Год назад
гдето на стековерфлоу было обсуждение и есть мнение что ходить с базу из селери такое себе решениеюююю
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
😵 а нафига тогда нужно это celery ?
@Chel1k7
@Chel1k7 Год назад
А как так получается, что если в самом начале таски поставить задержку в 10 сек, и посмотреть изменения на сайте, то данные всё равно обновляются, не смотря на то что таска ещё активна? она прост игнорирует time spleep и идет дальше по коду ?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Хм, не должно быть такого. Нужно повнимательнее разобраться, может эти изменения что-то другое делает , а не эта таска
@SeliverstovMusic
@SeliverstovMusic Год назад
Если SingleTone не принимает новую таску, то у нас будет перерасчёт не по последним изменениям?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Параметры пересчета определяются в самой таске, не в аргументах.
@SeliverstovMusic
@SeliverstovMusic Год назад
@@SeniorPomidorDeveloper но если мы поставим sleep(5) ближе к концу таски (после annotate), то у нас будут подобные ошибки. Есть ли способ отменять таски в очереди и оставлять последнюю?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Таска , которую уже забрал воркер, , которая уже запущена, отменяться не будет. Это не безопасно, не нужно прерывать функцию, во время выполнения. Таску , которая стоит в очереди, отменять нет смысла, в случае, когда приходит такая-же, с аналогичными аргументами. Их, по факту, еще нет. Это только записи в очереди
@user-gd5mw6dz5o
@user-gd5mw6dz5o Год назад
мне тоже показалось что тут есть проблема, но на самом деле её нет (вроде бы), попробую объяснить: - данные с которыми таска работает берутся из базы в момент выполнения таски (поэтому они всегда актуальные) - с синглтоном мы не сможем поставить в очередь таску на пересчет стоимости подписки, если такая же таска уже лежит в очереди (в этом нет смысла, наша цель обновить цену в подписке, и задача уже в очереди, благодаря первому пункту гарантируется, что цена расчитается по самым свежим данным) - при этом если такая же таска находится в процессе выполнения или уже выполнилась, то запрет на добавление в очередь не должен срабатывать Есть тонкость насчет запрещать/не запрещать добавление в очередь если аналогичная таска на выполнении. Я бы сказал, что нужно НЕ запрещать, потому что таска уже могла считать старые данные до того, как мы их изменили => чтобы гарантировать что цена подписки будет пересчитана по свежим данным нужно: - запрещать постановку в очередь если аналогичная таска лежит в очереди - разрешать постановку в очередь если аналогичная таска выполняется либо выполнена (уже забрана из очереди) Вообще, мне показалось (из того, что я увидел, сам еще не попробовал), что Singletone не ставит таску в очередь если аналогичная на выполнении. Если это так то проблема актуально. Но это было бы странно, все-таки такие вещи ведь обычно умные люди делают?))
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Хороший вопрос. Документация гласит - «The Singleton class overrides apply_async() of the base task implementation to only queue a task if an identical task is not already running.» «… чтобы поставить в очередь таск, только в том случае , если аналогичный таск еще не запущен». Лично мне это формулировка совсем не понятна. То есть видимо он не будет поставлен в очередь, если таск уже запущен? А если таск аналогичный в очереди , тоже получается не будет запущен , но документация об этом не пишет. Вообщем можно взять и проверить , если у вас курс все еще есть на компе, я уже удалил. По логике я согласен с вашими мыслями, таску скорее надо ставить в очередь, если аналогичная отрабатывает , по крайней мере в кейсе с пересчетами .
@emurze-nx7xh
@emurze-nx7xh 10 месяцев назад
Привет, ты не знаешь как можно использовать Singleton с RabbitMQ?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper 10 месяцев назад
Да также. А в чем там могут быть особенности?
@user-lt4nw7cz8l
@user-lt4nw7cz8l Год назад
Пишу такой же код, и у меня происходит зацикливание таска. При save() вызывается таск, в котором тот же save(). И зациклился. Почему так?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
Значит что-то упустили , или недосмотрели до конца. Там есть такой вариант чтобы таски по кругу друг друга вызывали и делали бесконечный save, я как раз этого избегаю в коде
@user-lt4nw7cz8l
@user-lt4nw7cz8l Год назад
Singleton от бесконечного save спасает?
@SeniorPomidorDeveloper
@SeniorPomidorDeveloper Год назад
@@user-lt4nw7cz8l нет, это про другое . Думаю что можно попробовать сравнить свой код с тем, что в репозитории github.com/chepe4pi/service_app в нужной ветке. Ну или пройти урок заново
@aliaksandrlitvinau480
@aliaksandrlitvinau480 Год назад
попробуйте только изменить существующую подписку в админке, а не создавать новую. при создании новой подписки статус таски "failure" во flower
@aliaksandrlitvinau480
@aliaksandrlitvinau480 Год назад
@SeniorPomidorDeveloper ведь такое поведение и подразумевается, так как при создании подписки таски по подсчету не запускаются?
@pretcb
@pretcb Год назад
+
Далее
Обо мне
9:08
Просмотров 10 тыс.
Как дела перцы?
00:25
Просмотров 72 тыс.
Оптимизация Django. 5 - Celery + Docker
30:31
ВЕСЬ FASTAPI ЗА 30 МИН
28:37
Просмотров 32 тыс.