Хорошая ли идея делать счетчик просмотров в записи и обновлять его при каждом просмотре
Что проверяют
Понимаете ли вы, во что превращается запись в базу на каждый просмотр страницы. Это вопрос про чувство нагрузки: блокировки строки, распухание таблицы, невозможность кэшировать ответ. Правильный ответ начинается с вопроса о масштабе.
Как отвечать
Для нагруженного места идея плохая, и важно объяснить механику. Каждый просмотр это UPDATE, а значит новая версия строки, запись в журнал и работа автовакуума. Все просмотры одной популярной записи бьются в одну строку и выстраиваются в очередь на блокировке. Плюс страница перестаёт быть кэшируемой, раз на каждый заход нужна запись.
Первое, что нужно сказать: инкремент считает база, а не приложение.
# гонка: читаем, прибавляем, пишем
post.views += 1
post.save()
# атомарно, без потерянных обновлений
Post.objects.filter(pk=pk).update(views=F("views") + 1)Дальше по нагрузке счётчик выносят из основной таблицы: отдельная таблица счётчиков, затем буфер в Redis через INCR со сбросом в базу фоновой задачей раз в минуту, затем поток событий и агрегация пачками.
Закройте ответ требованиями. Счётчик просмотров это не деньги: приблизительное значение, задержка в минуту и потеря части инкрементов при перезапуске обычно допустимы, и именно это разрешает всю оптимизацию.
Где спотыкаются
Видят гонку, но лечат её транзакцией с SELECT FOR UPDATE. Потерянных обновлений не будет, зато очередь на горячей строке станет длиннее, а нужен был один атомарный инкремент на стороне базы.
Забывают про то, что считается просмотром. Без дедупликации по сессии или пользователю в счётчик попадают обновления страницы, переходы назад и боты, и число перестаёт что-либо значить.
Предлагают кэш и не отвечают, что будет при его перезапуске. Нужно сказать вслух: значения периодически выгружаются в базу, при падении теряется последний интервал, и это осознанный размен на скорость.