Уведомления

Группа в Telegram: @pythonsu

#1 Янв. 21, 2011 12:16:58

LestatCheb
От:
Зарегистрирован: 2009-06-09
Сообщения: 50
Репутация: +  0  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Здравствуйте.

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

Вот тут и появляется проблема, процессов с джангой запущено несколько десятков, и при каких-то условиях (так и не понял при каких), на email приходит traceback с ошибкой при вызове save() для сохранения статистики в базу с сообщением “IntegrityError: duplicate key value violates unique constraint ”unique_together“”.

код следующий:

    bstat = cache.get(cache_key)
if not bstat:
# получаем/создаем объект статистики для данного баннера
try:
bstat = BannerStatistic.objects.get(year=year, month=month, day=day, banner=banner)
except BannerStatistic.MultipleObjectsReturned:
bstat = BannerStatistic.objects.filter(year=year, month=month, day=day, banner=banner).order_by('id')[0]
except BannerStatistic.DoesNotExist:
bstat = BannerStatistic()
bstat.year, bstat.month, bstat.day = year, month, day
bstat.banner = banner
bstat.banner_clicks = 0
bstat.banner_shows = 0
bstat.banner_CTR = 0

# обновляем статистику по кликам/показам
if action == 'show':
bstat.banner_shows += 1
elif action == 'click':
bstat.banner_clicks += 1

# сохраняем в базу каждые N показов или при каждом клике, до этого держим в memcached
if settings.DEBUG:
SAVE_SHOWS_CONDITION_COUNT = 1
else:
SAVE_SHOWS_CONDITION_COUNT = 1000
if (bstat.banner_shows % SAVE_SHOWS_CONDITION_COUNT == 0) or (action == 'click'):
bstat.save()
т.е. видимо ситуация примерно следующая, наступает новый день
(соответственно собирается новая статистика за день,
т.е. объектов за сегодня пока еще не создавалось в базе):

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


вот, собственно вопрос, как решить данную задачу?
нужен какой-то Lock manager?
multiprocessing тут насколько я понял не поможет, т.к. общего родителя у процессов нет.


Буду благодарен за любую помощь и отсылки куда копать.

Спасибо.



Офлайн

#2 Янв. 23, 2011 22:54:01

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Попробуйте использовать BannerStatistic.objects.get_or_create(year=year, month=month, day=day, banner=banner) вместо вашей доморощеной логики.
Дефолтные значения для banner_clicks, banner_shows и banner_CTR задайте на уровне модели. Exception BannerStatistic.MultipleObjectsReturned по идее не должно бросаться, ведь у вас по одной записи на день/баннер.



Офлайн

#3 Янв. 24, 2011 10:11:51

LestatCheb
От:
Зарегистрирован: 2009-06-09
Сообщения: 50
Репутация: +  0  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Ed
Попробуйте использовать BannerStatistic.objects.get_or_create(year=year, month=month, day=day, banner=banner) вместо вашей доморощеной логики.
Дефолтные значения для banner_clicks, banner_shows и banner_CTR задайте на уровне модели. Exception BannerStatistic.MultipleObjectsReturned по идее не должно бросаться, ведь у вас по одной записи на день/баннер.
нет, get_or_create в данном случае не поможет, ведь проблема возникает в момент save().

думаю решение можно решить через RabbitMQ / Celery / Carrot.

кстати кто-нибудь их пробовал с джангой?



Отредактировано (Янв. 24, 2011 10:22:57)

Офлайн

#4 Янв. 24, 2011 21:16:25

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

А вы попробуйте. Глядишь и без RabbitMQ как-нибудь обойдетесь :)



Офлайн

#5 Янв. 24, 2011 21:24:28

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Насчет locking-а. Может как-нибудь так? http://djangosnippets.org/snippets/833/
Еще можно использовать IPC или fcntl локи, если у вас уних. Ну, на худой конец и mkdir сойдет. Но AMQP для этого я бы не стал городить.



Отредактировано (Янв. 24, 2011 21:29:21)

Офлайн

#6 Янв. 24, 2011 22:37:10

LestatCheb
От:
Зарегистрирован: 2009-06-09
Сообщения: 50
Репутация: +  0  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Ed
Насчет locking-а. Может как-нибудь так? http://djangosnippets.org/snippets/833/
Еще можно использовать IPC или fcntl локи, если у вас уних. Ну, на худой конец и mkdir сойдет. Но AMQP для этого я бы не стал городить.
ну я попробовал AMQP, он очень легкий и очень быстрый :)
вдобавок легко масштабируется.

можно конечно свой велосипед сделать с lock manager'ом,
погляжу завтра.



Офлайн

#7 Янв. 24, 2011 23:25:43

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Я тоже использовал и использую. Просто слишком это из пушки по воробьям. Подумайте сами - 5 строчек кода с fcntl.lockf или поднятие AMQP брокера, установка питоновых биндингов к нему и программирование вашего механизма локов поверх него.



Офлайн

#8 Янв. 25, 2011 10:22:19

LestatCheb
От:
Зарегистрирован: 2009-06-09
Сообщения: 50
Репутация: +  0  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Ed
Я тоже использовал и использую. Просто слишком это из пушки по воробьям. Подумайте сами - 5 строчек кода с fcntl.lockf или поднятие AMQP брокера, установка питоновых биндингов к нему и программирование вашего механизма локов поверх него.
а там получается что даже лочить не надо,
просто задания встают в очередь и поочередно обновляют статистику

update:
кстати а причем тут fcntl.lockf? он же файлы лочит.
LOCK TABLE можно в базе конечно делать, но тогда другой процесс (и пользователь вместе с ним) будет ждать пока этот лок снимется, т.о. сервак скорее выдаст timeout и пользователь ничего не получит :)
а с AMQP кинул задание асинхронное и отдавать дальше пользователю странички.

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



Отредактировано (Янв. 25, 2011 15:12:48)

Офлайн

#9 Янв. 25, 2011 16:26:36

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

LestatCheb
а там получается что даже лочить не надо, просто задания встают в очередь и поочередно обновляют статистику
Задание - это не термин AMQP, он занимается передачей сообщений. Если же вы имеете в виду celery с ее задачами, то вам нужно будет искусственно зажать ее до одного процесса, иначе ваши задачи будут выполняться параллельно. Дело ваше, конечно, но на мой взгляд celery немного для другого сделана. Этого же можно достичь написав примитивный скрипт, запуская его скажем из крона. Никакой AMQP не нужен, если вам просто последовательно нужно выполнять задания. Не для этого он просто.

кстати а причем тут fcntl.lockf? он же файлы лочит.
Именно. Это системный лок, поэтому виден в разных процессах. В одном перед апдейтом можно залочить, а в другом проверить не залочен ли. Как-то так.

LOCK TABLE можно в базе конечно делать, но тогда другой процесс (и пользователь вместе с ним) будет ждать пока этот лок снимется, т.о. сервак скорее выдаст timeout и пользователь ничего не получит :)
а с AMQP кинул задание асинхронное и отдавать дальше пользователю странички.
Если не хотите ждать, то это уже совсем другое дело. Тогда локи вам наверное не подойдут. Подойдет тупой скриптик, запускающийся регулярно и сбрасывающий статистику из mamecache-a в базу.



Офлайн

#10 Янв. 25, 2011 16:41:20

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

shared object for processes via memcached

Хотя, подумавши, и лок подойдет. Вам же по идее нужно только вызов .save() залочить. Тогда если он залочен, то нужно просто ничего не делать - все уже сбрасывается в базу. Ничего ждать не нужно.
Ну и, естественно, воспользоваться get_or_create, иначе получите тот же конфликт при сохранении, что и сейчас.



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version