Форум сайта python.su
0
Здравствуйте.
Есть такая задача:
нужно собирать статистику показов рекламы на сайте, чтобы не дергать на каждый показ базу решил держать джанговский объект в 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()
Офлайн
13
Попробуйте использовать BannerStatistic.objects.get_or_create(year=year, month=month, day=day, banner=banner) вместо вашей доморощеной логики.
Дефолтные значения для banner_clicks, banner_shows и banner_CTR задайте на уровне модели. Exception BannerStatistic.MultipleObjectsReturned по идее не должно бросаться, ведь у вас по одной записи на день/баннер.
Офлайн
0
Edнет, get_or_create в данном случае не поможет, ведь проблема возникает в момент save().
Попробуйте использовать BannerStatistic.objects.get_or_create(year=year, month=month, day=day, banner=banner) вместо вашей доморощеной логики.
Дефолтные значения для banner_clicks, banner_shows и banner_CTR задайте на уровне модели. Exception BannerStatistic.MultipleObjectsReturned по идее не должно бросаться, ведь у вас по одной записи на день/баннер.
Отредактировано (Янв. 24, 2011 10:22:57)
Офлайн
13
А вы попробуйте. Глядишь и без RabbitMQ как-нибудь обойдетесь :)
Офлайн
13
Насчет locking-а. Может как-нибудь так? http://djangosnippets.org/snippets/833/
Еще можно использовать IPC или fcntl локи, если у вас уних. Ну, на худой конец и mkdir сойдет. Но AMQP для этого я бы не стал городить.
Отредактировано (Янв. 24, 2011 21:29:21)
Офлайн
0
Edну я попробовал AMQP, он очень легкий и очень быстрый :)
Насчет locking-а. Может как-нибудь так? http://djangosnippets.org/snippets/833/
Еще можно использовать IPC или fcntl локи, если у вас уних. Ну, на худой конец и mkdir сойдет. Но AMQP для этого я бы не стал городить.
Офлайн
13
Я тоже использовал и использую. Просто слишком это из пушки по воробьям. Подумайте сами - 5 строчек кода с fcntl.lockf или поднятие AMQP брокера, установка питоновых биндингов к нему и программирование вашего механизма локов поверх него.
Офлайн
0
Edа там получается что даже лочить не надо,
Я тоже использовал и использую. Просто слишком это из пушки по воробьям. Подумайте сами - 5 строчек кода с fcntl.lockf или поднятие AMQP брокера, установка питоновых биндингов к нему и программирование вашего механизма локов поверх него.
Отредактировано (Янв. 25, 2011 15:12:48)
Офлайн
13
LestatChebЗадание - это не термин AMQP, он занимается передачей сообщений. Если же вы имеете в виду celery с ее задачами, то вам нужно будет искусственно зажать ее до одного процесса, иначе ваши задачи будут выполняться параллельно. Дело ваше, конечно, но на мой взгляд celery немного для другого сделана. Этого же можно достичь написав примитивный скрипт, запуская его скажем из крона. Никакой AMQP не нужен, если вам просто последовательно нужно выполнять задания. Не для этого он просто.
а там получается что даже лочить не надо, просто задания встают в очередь и поочередно обновляют статистику
кстати а причем тут fcntl.lockf? он же файлы лочит.Именно. Это системный лок, поэтому виден в разных процессах. В одном перед апдейтом можно залочить, а в другом проверить не залочен ли. Как-то так.
LOCK TABLE можно в базе конечно делать, но тогда другой процесс (и пользователь вместе с ним) будет ждать пока этот лок снимется, т.о. сервак скорее выдаст timeout и пользователь ничего не получит :)Если не хотите ждать, то это уже совсем другое дело. Тогда локи вам наверное не подойдут. Подойдет тупой скриптик, запускающийся регулярно и сбрасывающий статистику из mamecache-a в базу.
а с AMQP кинул задание асинхронное и отдавать дальше пользователю странички.
Офлайн
13
Хотя, подумавши, и лок подойдет. Вам же по идее нужно только вызов .save() залочить. Тогда если он залочен, то нужно просто ничего не делать - все уже сбрасывается в базу. Ничего ждать не нужно.
Ну и, естественно, воспользоваться get_or_create, иначе получите тот же конфликт при сохранении, что и сейчас.
Офлайн