Александр Кошелев написал подробную статью про инвалидацию кешей. Самое интересное (для меня, по крайней мере) в ней то, что там сделана попытка придумать декларативный синтаксис описания процесса инвалидации. И хотя в общем случае такая задача не решается, было бы очень полезно найти подход, который бы работал в пресловутых 80% случаев.
Пишу "бы", потому что для нас в "Офлайне" он, кажется, не сработает. Хочу, таким образом, покритиковать некоторые особенности этого решения. Может быть кто-нибудь придумает, как его улучшить.
Синтаксис
Первая придирка довольно простая — мне жутко не нравится синтаксис с многоэтажными декораторами. Я придерживаюсь той точки зрения, что в декоратор нужно выносить все то, что неважно для понимания логики работы функции. Кеширование — как раз тот случай. Но сама по себе конструкция из 4-10 строк настолько лезет в глаза, что заставить себя абстрагироваться от нее довольно трудно.
У нас в проекте вся логика инвалидации чего бы то ни было снесена в отдельный файл, где на сигналы вешаются функции инвалидации. То же самое можно сделать и в этом случае: завести "invalidation.py" в котором описать один большой список из dict'ов, которые иначе размазаны по коду. Очень похоже на urlconf будет по идее :-).
Завязка на модельные сигналы
Возможно я ошибаюсь, но мне кажется, что весь дизайн этой декларативной инвалидации ориентирован на то, чтобы вешаться в первую очередь на модельные сигналы (post_save, post_delete и т.п.) Я совершенно убежден, что использовать именно их для инвалидации кеша — неверная идея, потому что они работают на более низком уровне абстракции, чем тот, который должен знать про кеширование.
Пример. Вы храните в модели поста денормализованное количество комментариев, которое обновляется при появлении и удалении комментариев:
class Post(models.Model):
comment_count = models.IntegerField()
def _update_comment_count(self):
self.comment_count = # могучий запрос
self.save()
class Comment(models.Model):
post = models.ForiegnKey(Post)
def save(self):
super(Comment, self).save()
self.post._update_comment_count()
def delete(self):
# аналогично save
Если завязать, как у Александра, инвалидацию блока "последние 5 постов" на сохранение модели Post, то он будет инвалидироваться с каждым комментарием.
Это, понятное дело, только пример. Я хочу показать, что кеширование и инвалидация в Джанго — операции уровня view. Именно там, где программа понимает, "шо канкретна" имел в виду пользователь, она может послать нужные сигналы, по которым можно будет делать и более вменяемую инвалидацию.
Вот насколько декларативный синтаксис будет хорошо подходить к ситуации, когда большинство инвалидирующих сигналов — кастомные, сказать не берусь. Надо, наверное, попользоваться и понять.
Сложные случаи
Но больше всего мне беспокоит другое. А именно то, что сложные случаи, для которых у Александра предусмотрен fallback из декларативной модели в императивную — функция checker — будут составлять не такую уж и маленькую часть. У нас в проекте именно так и происходит. Вот, например, как описывается инвалидация блоков с аватарками людей идущих на событие в разных местах системы:
def invalidate_faces(sender):
event = sender.instance.event
city = get_user_city().text_code
keys = [
'%s-%s-%s-%s-%s-%s' % ('faces', event_id, city, category and category.id, tag and tag.id, user)
for user in [None, sender.user.yandex_uid]
for event_id in [event.id, None]
for category in [None, event.category]
for tag in [None] + list(event.tags.all())
]
clear_keys(keys)
Это четверной вложенный for, который генерирует целую пачку ключей, каждый из которых отвечает за вид этого блока в разных условиях (индексная страница, страница выборки событий, страница одного события и т.д.) И надо сказать, остальные инвалидации у нас тоже происходят именно над пачками ключей. Как описать это декларативно, я не представляю...
Впрочем, возможно у нас слишком усложнены правила показа всего этого добра. А возможно, во всем виновата моя идея свалить всю инвалидацию в один файл... Не знаю. Время покажет, а пока это просто мысль "на подумать"...
Комментарии: 3
Я об этом же думал. :-) Имхо, довольно здраво должно выйти. Но пока ничего реализовать не пытался...
А какие тут могут быть проблемы? Вроде как сигналы — и в Африке сигналы.
Внушаетъ. А не логичнее ли поставить кэш в зависимость от ЧЕТЫРЁХ ключей, и инвалидировать его по изменении любого?
p. s. Тутошний OpenID не смог принять мой адрес ЖЖ.
Проблема в данных, которые идут вместе с сигналом. У них свободный формат и семантика, поэтому мне кажется, что всегда придется писать код, который знает, что означают эти данные.
Этой проблемы не возникает с pre_save, pre_delete, потому что формат их параметра известен — это instance модели.