Каковы современные подходы в создании «Ленты новостей»?
Здравствуйте!
Собственно, проблема обычная, есть проект, в проекте есть различные сущности, например "Человек", "Заведение", "Событие". Все эти сущности могут генерировать новости. Пользователи могут подписываться на часть этих сущностей и потом читать в ленте их новости.
Вот тут самое волнительное и начинается: как и в чём хранить ленту? Нашел в основном двух-трёх годичные посты, где самым интересным вариантом показался вариант с хранением ленты для каждого пользователя в Redis. Но может за эти годы появились какие-то другие решения? Может есть какое-то специальное ПО для развёртывания feed-сервера?
Хранение ленты для каждого пользователя самый экономичный и (что очень важно) легко масштабируемый способ.
Если грубо, то каждая активность у вас на проекте порождает событие, а на эти события вы уже навешиваете формирование лент пользователей удобным для вас способом. Нужно лишь верно выбрать хранилище, отвечающее вашим задачам. А выбор будет определяться следующими характеристиками:
Пользователей пока 0. Но планируется много. А так как "падающих" проектов я уже насмотрелся, не хочется изначально наступать на грабли, потом и в поисках решения.
Время жизни ленты тоже вопрос ещё открытый. День-два - мало, месяц - возможно много.
Ещё в комментариях встречал, что советую использовать менеджеры очередей, типа RabbitMQ. Я же надеюсь, что исключительно для заполнения ленты, (PubSub) в БД, а не для хранения оной в менеджере очереди?
Кстати, как к хранению ленты отнесётся MongoDB? Ну то, что места отъест не мало - это понятно, а в плане производительности?
Денис Сафронов: Всё верно, менеджеры очередей позволяют асинхронно формировать ленты пользователей и не зависеть от их кол-ва и активности.
Монга как раз отлично для этого подойдёт с её schemaless, конечно при условии её правильной настройки. Только если у вас пользовательские ленты основа сервиса, то заранее подумайте о том, чтобы иметь возможность восстанавливать ленты за определённый срок, например, железно сохраняя часть последних событий. Потому что вдруг что-то пойдёт не так или захочется "всё сделать по другому".
Антон Каракулов: в ленте скорее будет оповещение пользователей о каком-то событии. Т.е. есть например заведение "Кафе у Бобра", на него подписано 100К пользователей, и в какой-то момент администратор этого кафе создаёт событие "Гала-концерт "Еду из Магадана на Родину к корешам". Оно сохраняется в БД, получает свою страницу, а ленте у подписчиков появляется сообщение о том, что создано новое событие. Ну, потом, само собой всякие уведомления "со стены" и из "фотоальбомов" события, т.е., грубо говоря, после успешного $record->save(), выполняем $feed->push($data), который уже кладёт данные в "Кролика", тот их передаёт в какой-то скрипт (вот тут тоже момент нужно продумать, дёргать php со всем фреймворком, или просто написать cli-скрипт (php, python, golang - не суть)), а тот уже всё это записывает в Mongo. Осталось решить момент с подчисткой "лишнего" в ленте, т.е. удаление старых записей.
И ещё, всё-же, Монго или Редис? Редис может держать часть данных в памяти, а если его перенести на отдельный сервак, ну или просто памяти много воткнуть - то возможно и все данные. В целом, можно и файлы с БД Mongo держать на tmpfs, но в случае ребута - они просто потеряются. Да и места Mongo ест не мало.
Денис Сафронов: Для >10k подписчиков на элемент нужно организовывать промежуточную очередь, чтобы не потерять уведомления, в случае сбоя скрипта в середине выполнения. Тоесть $feed->push($data) кладёт в базовую очередь событий. Затем скрипт (несколько экземпляров), который слушает эту очередь получает подписчиков по событию и записывает в следующую очередь атомарные события (мол этому пользователю такое уведомление, или этому пользователю такую запись в ленту), а уже следующий уровень разбирает весь этот завал, например, в 5-10 потоков (тут уже масштабирование от загрузки очереди).
Что касается удаления, то если шардить и иметь возможность расширяться, то можно и не удалять вовсе =) А если всё же удалять попроще и быть экономным, то можно пошамать с организацией шардинга данных. Например: если сделать ключ [пользователь, временной-фрейм] и вынести часть логики в приложение, то можно просто периодически дропать целые неактуальные коллеции.
Денис Сафронов: В священном вопросе Redis или Mongo выбирать нужно то, с чем уже умеешь работать или интересно разобраться, а так же от задач которые предстоит решать в ближайшем будущем. потому как и там и там есть свои + и -. =)
Вроде все довольно просто. При наличии индексов в таблице новостей саму ленту можно и не хранить. Сохраняйте только подписки пользователей на сущности. Вроде такого:
Таблица news : id / autor_type / autor_id / ...
Где author_type - ссылка на таблицу сущности, author_id - соответственно id сущности
Формируете ленту для пользователя запросом вроде:
SELECT * FROM news WHERE (author_type = "$1" AND author_id = "$2") AND (author_type = "$3" AND author_id = "$4") ...
Актуальный для меня вопрос, в скором времени запилю статью про одну из реализаций ленты на PostgreSQL с выборками не более 1-2мс на достаточно больших объемах данных.