Денис @ur001
Пользователь
Информация
- В рейтинге
- Не участвует
- Откуда
- Москва, Москва и Московская обл., Россия
- Дата рождения
- Зарегистрирован
- Активность
Специализация
Backend Developer, Software Architect
Lead
Python
PostgreSQL
Redis
Celery
Django
Fastapi
Elasticsearch
Kubernetes
Clean Architecture
MySQL
Наверно, по префиксам удобно сделать префиксное дерево, чтобы не проходиться по всем префиксам в цикле
Спасибо за статью!
Если интересно, могу предоставить эмбединги с sociation.org (не сочиите за рекламу, проект некоммерческий). Они получены не на корпусе текстов, а, наоборот, из ассоциаций пользователей. Слов меньше, только отмодерированные существительные и устойчивые словосочетания, но они намного качественнее. Я сравнивал с RuVectors тестами и эмпирически.
Ну хорошо :) Вы используете очередь. В очереди пересчитываете счётчик полностью, т.к. инкрементное обновление считаете ненадёжным. Благодаря «схлопыванию» удаётся снизить накладные расходы, т.к. «при реактивном изменении счетчика (100500 хомячков в секунду) расчет производится только 1 раз на over 9000 фактических изменений». Я правильно вас понял?
Откатывать транзакцию или нет — зависит от бизнес-требований. Можем ли мы совершить действие если обновлние сётчика не гарантировано? Можем ли мы позволить себе иметь задержку при рассчёте счётчика? Можем ли мы не менять значение счётчика, если было подряд +1 и -1, или, например нам важно получить рейтинг 100, а потом обратно 99, т.к. при достижении 100 срабатывает триггер и пост становится золотым. Очереди — это замечательно, очень удобно и, во многих случаях незаменимо. Они просто не имеют, ИМХО, прямого отношения к теме топика.
Логично что отправка письма в фоне с повтором при неудаче, т.к. уведомление тут вторично. Во всех проектах отправка email/sms/push и прочих уведомлений делается так. С утвердительным ответом от платёжной системы наоборот, т.к. оплата важнее. А к чему это вы?
Рейтинг пользователя на хабе скорее всего да, не вызовет большого перебора, немного ступил. Но одновременно с ним нужно пересчитывать ещё рейтинг хаба. Там будет перебор всех постов хаба. Но мне всё же странно, почему вы наставиваете на полном пересчёте вместо инкрементного обновления, когда оно намного производительнее. Вам не верится что инкрементное обновление может не сбиться? :) Думаете что 1+1+1 в какой-то момент может стать 2 или 4, если повторять эту операцию много раз?
Если схлопывать несколько обновлений в один, то готов с натяжкой согласиться. Тем не менее, два SELECT SUM() имеет сложность O(N1) + O(N2), где N1 и N2 число постов в старом и новом хабе (это при наличии индексов, при отсутствии это число всех постов на Хабре). Т.е. SQL, каким бы волшебным он не казался, честно пробежится по всем постам хаба и просуммирует рейтинг. Если схлопывания, про который вы писали, нет (а это иногда ограничение бизнес-требований), при большой соц. сети с кучей показателей, то полный пересчёт на производительности скажется драматически.
Ок. Нам в очередь пришёл id поста и мы посмотрели по changelog-у, или получили сразу в виде параметров помимо post_id следующие поля: old_hub_id, old_is_published, old_is_deleted, old_user_id, и из базы (которая к этому моменту кстати могла опять измениться) или как-то ещё вычислили актуальные на момент срабатывания счётчика hub_id, is_published, is_deleted, user_id. Да, может это звучит необычно, но на dirty реализована передача черновика другому пользователю, по этому предположим что автор тоже может измениться. Какой вы напишите обработчик для обновления вашего «кеша» рейтинга пользователя на хабе?
Ну тут мы кажется сошлись во мнениях, я в комментарии написал то же самое.
Ну допустим пользователь отредактировал пост сменив хаб и скрыв в черновики. Как мы узнаем что хаб был сменён (а значит нужно вычесть рейтинг поста из рейтинга в старом хабе) и что пост не был в черновиках до этого?
Ну если я потом смогу по этому кешу сделать запрос
То у нас просто разное понимание терминологии что считать кешом.
А чем очередь помогает? Считать в очереди или не нет это вопрос нужен ли вам синхронный счётчик или можно иметь значение с задержкой. В некоторых случаях при отказе в обновлении счётчика нужно откатить транзакцию оменить всё действие. Иногда это вопрос скорости отклика, если на какое-то действие (лайк, к примеру) обновляется слишком много счётчиков и это приведёт к ощутимой для пользователя задержке при синхронном обновлении, то нужно перенести часть рассчётов в очередь.
Допустим у вас в профиле список хабов, в которые вы внесли максимальный вклад. Это запрос по таблице счётчиков [hub_id, user_id] --> rating с сортировкой по убыванию рейтинга. Рейтинг на хабе это, к примеру, сумма рейтингов опубликованных и неудалённых постов пользователя на хабе. Что вы будете класть в очередь? Как на основе этого будете обновлять счётчик? Чем поможет в этом случае кеш?
Да, только ели счётчиков много полностью на триггерах их довольно тяжело поддерживать. Добавление новых, переименование или изменение условий на существующих счётчиках, миграция структуры бд и т.п. будет каждый раз головной болью.
Идея ловить тригерами изменения и отправлять на обработку в очередь на той же БД (PGQ) мне, в принципе понравилась. Этот подход позволял отлавливать только изменения, при этом писать логику их обработки на нормальном питоне. Но сама PGQ по сравнению с Celery отвратительна — это раз. Триггеры отлавливающие изменения приходилось обновлять вместе с миграцией БД, короче не слишком приятно выходило. Но потенциал в этом подходе есть.
А, понятно. Я сначала не понял, что вы про хранение счётчиков только в кеше. Я тут ответил.
Иногда такой подход оправдан. Но для некоторых вещей просто неприемлем. Например, из Трипстера: запись на экскурсию с ограниченным числом участников. После того, как максимальное число людей оплатило, регистрация на событие закрывается, неоплаченные заказы отменяются, всем участникам и гиду приходят уведомления.
Второй вариант — значения счётчиков вам постоянно нужны для выборок.
Да, я имел в виду именно это. И да, дело, по большей части, в дороговизне полного пересчёта на каждое изменение. Никакое кеширование, тут помочь не может — что именно вы будете кешировать? :)
А понял, спасибо
Не, не могу помочь. На Хабре я давно не работаю
У нас тоже финансовые операции присутствуют, и, некоторые напрямую зависят от счётчиков. По этому когда я пришёл на проект и обнаружил MyISAM, первым делом бросился переводить на InnoDB и заворачивать в транзакции. Но к тому моменту накопилось столько мусорного кода, что банально не найдены и необеззаражены все места где что-то типа
Т.е. без транзакций и SELECT FOR UPDATE, без, хотя бы save(update_fields=['status']) и прочее. И, конечно, это ломает счётчики.
А кто у вас добавляет в очередь пометку о необходимости обновить счётчик?
Ну так я же старался проще :) Подскажите что кажется сложным. Я надеялся, что самым понятным будет код, кроме которого можно особо ничего и не читать
Хм… Не до конца понял. В одном из проектов я тоже делал возможность пересчёта одного или нескольких счётчиков. Счётчики задаются декларативными правилами типа:
Т.е. тут тоже обычные счётчики даже не в отдельной таблице, а прямо в основных моделях.
В вашем случае increment( counterName, ±1) вызывается вручную в методах публикации/распубликации или это происходит автоматически на основе конфигурации? Как примерно устроена конфигурация? Как работает пересчёт?
А расскажете? Как было, что было проще? И да, какие из моих рассуждений показались вам заумными, может я переформулирую? :)