1115

Как работает скрытие просмотренных постов на Пикабу

В этом посте расскажу как устроена одна из самых глючных функций Пикабу - скрытие просмотренных постов. У этой функции богатая история проб и ошибок, 100500 жалоб пользователей, много попыток замены её и, как результат, достаточно интересное последнее техническое решение о котором в данном посте пойдет речь) Постараюсь все рассказать популярным языком, а в конце поста будет ссылка на кое-какой инструмент, который покажет визуально как вы смотрите Пикабу) Но для начала коротко расскажу о самой функции.

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Когда зарегистрированный пользователь листает ленты постов мы фиксируем факт просмотра каждого поста и сохраняем эту информацию на сервере. Затем, при открытии любой основной ленты (Горячее, Лучшее, Свежее, Подписки и тд), мы на лету фильтруем весь список постов этой ленты и оставляем только те посты, что пользователь ещё не видел. Таким образом пользователь один раз увидев пост на Пикабу больше его не должен видеть тут) “Да что вы  такое говорите!” - сказали почти все, кто пользовались функцией скрытия просмотренного :D Попробуем разобрать какие есть проблемы у системы скрытия просмотренного.


Проблемы

Условно проблемы скрытия просмотренного можно разделить на три вида: проблема хранения информации, неуловимые баги пропадающих запросов от клиента на сервер с фактом просмотра постов, и иные проблемы, больше связанные с особенностями некоторых страниц, работы приложений, браузеров и прочего.

В этом посте будем рассматривать самую интересную проблему - как хранить просмотры миллионов постов для миллионов пользователей и при этом за миллисекунды фильтровать большие массивы постов каждой ленты. Например, некий пользователь admin за 12 лет на Пикабу посмотрел в общей сложности 7 млн постов и сейчас открывает ленту лучшего за все время, где 13 тыс постов. Серверу нужно как-то моментально найти пересечение двух множеств из 7 млн и 13 тыс идентификаторов постов, чтобы исключить из ленты те посты, что пользователь уже видел. Задача нетривиальная для многих NoSQL БД, и уж тем более для всяких RDBMS а-ля MySQL. Так как время генерации страницы должно стремиться к нулю, то делать тяжелые запросы - не вариант, нужно искать быстрые решения.


Что мы пробовали ранее

Больше всего решений мы перепробовали на хранении в Redis некого буфера из 12 тыс последних просмотренных постов. Причем размер буфера все время рос: вначале был вроде 6 тыс, затем 8, 10, и вот остановились на 12 тыс. Изначально мы не ставили цель хранить просмотры постоянно, а хотя бы скрывать недавно просмотренные посты и для этого хорошо подходит Redis. В итоге последняя наша реализация на Redis была через Redis списки: это по сути обычный список записей в БД под общим ключом, который можно дополнять слева или справа, обрезать, и вытворять с ним многое чего веселого.


Однако буфер из 12 тыс последних просмотренных не хватал пользователям и многие сообщали, что видели посты повторно. Далее увеличивать размер буфера - путь в никуда. Решили, что нужно попробовать переводить систему просмотренных постов с ограниченного буфера во временном хранилище на хранение всех просмотров в постоянное хранилище. Для такого хранения рассматривали те БД, что есть в арсенале: Cassandra, MySQL, ElasticSearch, ClickHouse. Попробовав разные вариант остановились на Kafka + ClickHouse + Redis и какое-то время система просмотров работала на такой схеме. Однако в бою постепенно начали всплывать проблемы с такой схемой (нагрузка, скорость выполнения запросов, нестабильность кластера), которые мы не могли решить тот момент, и мы приняли решение попробовать что другое, менее ресурсоемкое и более заточенное под задачу)


Новая система хранения просмотров

Мы решили так: будем хранить для каждого пользователя по 1 биту для каждого поста, который он видел или не видел! Т.е. если, условно, на Пикабу 10 млн постов, то для каждого пользователя мы будем хранить 10 млн бит o___O.

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Абсурд! Зачем хранить где-то биты для всех постов, которые пользователь даже не видел ещё?

А все затем, чтобы легко на лету определять был ли просмотр определенного поста или нет.

Биты строго упорядочены так, как добавлялись посты. Т.е. бит под номером 8518117 хранит состояние просмотра поста с id=8518117. При таком хранении чтобы проверить был ли конкретный пост просмотрен пользователям нам нужно сделать очень простую с точки зрения процессора операцию - проверить состояние бита, который находится на такой же позиции в последовательности бит, как порядковый номер поста (его id).

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Чанки

Но 10 млн бит - это без малого 1.2 МБ данных и список состояний для очень большого числа постов, к тому же список постоянно растет. В 90% случаев нам нужно узнать состояния просмотрен или нет постов за определенный период времени. Например, открываем Горячее или Свежее, и там посты за последнюю неделю где-то, это примерно 13 тыс постов. Получается нет смысла загружать все биты сразу и поэтому мы их делим на куски равной длины (чанки, от англ. chunk).


Но какую длину чанков выбрать? Изначально мы взяли круглое число - 100 тыс бит на 1 чанк (это примерно полтора месяца постов). Однако потом выбрали размер чанка равный 131072 бит.

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Если кто-то понял что это за число - знай, ты красавчик! Для всех остальных - это число степени двойки (2 в 17й степени, т.н. POT число). Но зачем? Все просто, из-за математической операции поиска номера чанка. Мы на Пикабу при разработке бэкенда стараемся все зацикленные операции с большими количеством итераций делать максимально оптимальным кодом, экономить на спичках, так сказать (но без фанатизма, только для больших циклов). Если бы размер чанка был 100 тыс бит, то чтобы определить в каком чанке лежит бит для поста №8518117 нам потребовалось сделать следующее:

chunk = ⌊8518117 / 100000⌋ = 85

Тут у нас 2 операции, деление с остатком и округление (⌊..⌋ - это символы округления вниз). Однако, если чанк будет иметь длину 2^17 бит, то поиск номера чанка будет значительно быстрее выполняться следующей одной супер быстрой операцией:

chunk = 8518117 >> 17 = 64

Тут мы применили побитовое смещение на 17, что равносильно делению на 131072 и отсечению дробной части.

Зная номер чанка где лежит бит просмотра поста мы должны теперь определить номер нужного нам бита внутри чанка. Для этого находим какому порядковому номеру поста соответствует первый бит чанка:

first_story_id = chunk << 17 = 64 << 17 = 8388608

“Шо, опять?”) Да, снова побитовая операция, и все благодаря тому, что не от балды выбрали размер чанка. Операция смещения бит влево на 17 равносильна умножению числа на 131072, только быстрее чем обычная операция умножения)

Зная номер поста для первого бита внутри чанка мы легко можем посчитать номер бита для нужного нам поста:

bit_index = 8518117 - first_story_id = 129509

Но так как бит зачитать вот прямо так из другой кучи бит не каждый может (php не может), то нам нужно для начала получить байт в котором этот бит расположен следующей математикой

byte_index = bit_index >> 3 = 129509 >> 3 = 16188

“Шо, опять?” Все верно, тут мы тоже применили смещение, так как каждый школьник знает, что в байте хранится 8 бит, а число 8 - это тоже POT, 2 в 3й степени)

Мы знаем номер байта, в котором хранится нужный нам бит. Осталось зачитать этот байт как число и проверить состояние бита:

viewed = byte & (bit_index - (byte_index << 3))

Если viewed будет равен единице, значит пост с номером 8518117 когда-то был просмотрен пользователем :) И вот такую математику мы выполняем для всех постов в любой ленте, т.е. где-то цикл по 13 тыс постов при каждом открытии ленты.


Один чанк имеет 131072 бит, это 16384 байт. Так как чанк содержит последовательные биты просмотров (1) или наоборот не просмотров (0), то он идеально сжимается тем же gzip и весит всего 200-800 байт в среднем у пользователей. На загрузку одной ленты как правило достаточно загрузить 1 чанк, редко бывает, когда нужно загружать 2 и более чанков)


Чанки мы сохраняем в Aerospike. У нас их два для этого дела: горячий на ssd дисках для трех последних чанков (это примерно последние 400 тыс постов) и холодный на hdd дисках, где все остальные чанки.


Сейчас новая система скрытия просмотренных работает на связке Redis + Aerospike. В Redis записываем как и раньше временный буфер из 12 тыс последних просмотренных постов, а в Aerospike записываем уже на постоянное хранение. Зачем нам две технологии? Во-первых Redis позволяет не сразу записывать в чанки флаги, а делать 1 запись на каждые 5-10 просмотров. Это снижает вероятность такой проблемы, как race condition (состояние гонки), когда два параллельных процесса пытаются обновить одну бедненькую запись чанка. Во-вторых банальный fail over, если из-за аварии упадет Aerospike, то временно Redis будет хотя бы 12 тыс последних просмотров поддерживать, и наоборот.


На данный момент каждый пользователь имеет всего 65 чанков и самый заполненный - это 64й чанк) На прошлой неделе стартовал у всех 65 чанк, так что он еще совсем пустой.


Новую систему на чанках мы зарелизили всего пару месяцев назад, поэтому в чанках мы храним лишь информацию, что пользователи видели за эти последние пару месяцев.


Как вы смотрите Пикабу?

Когда мы тестировали новую систему хранения просмотренных постов мы написали к ней визуализатор чанков. Этот визуализатор выводит в картинку размером 800x660 пикселей все 131 тыс бит одного чанка. Если бит установлен в 1, то пиксель синий, иначе пиксель серый или белый. На визуализаторе свежих чанков можно увидеть какой объем постов вы видели, а какой так и не увидели. Т.е. если много серых точек - это все те мемчики, что вы пропустили) Если подряд идут много синих линий, значит вы смотрели свежее, так как система зафиксировала просмотры подряд идущих постов. Например, вот как смотрит Пикабу нейкий пользователь 0x00:
Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Как видно много горизонтальных синих линий, значит 0x00 часто зависает в свежем.

А вот к примеру как выглядит чанк рандомного автора из горячего (не буду раскрывать кто он):

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Сразу понятно, что пользователь редко в свежее заходит, да и вообще вероятно смотрит только горячее, лучшее или подписки свои. Также видно, что у него были пробелы в посещении сайта )

Как работает скрытие просмотренных постов на Пикабу Пикабу, Скрытие постов, Длиннопост, Текст

Увидеть свои чанки и то, как плотно вы смотрите Пикабу можно вот тут (лучше с ПК): https://pikabu.ru/page/misc/chunks/


Ссылка доступна только если есть авторизация на сайте)

Найдены дубликаты

Отредактировал Kouran 12 дней назад

Как работает Пикабу

2 поста87 подписчиков

Добавить пост
Лучшие посты за сегодня
7732

Совсем уже обнаглели

Совсем уже обнаглели
6273

Находка

5518

Вот оно что

Вот оно что
4293

Жители Сосенского поселения Новой Москвы, просившие решить проблему с мигрантами, создают группы самообороны

Жители Сосенского поселения Новой Москвы, просившие решить проблему с мигрантами, создают группы самообороны Негатив, Мигранты, Москва, Отряд, Самооборона
Показать полностью
preview
4190

Страна Арракия - страна без забот

Страна Арракия - страна без забот
3696

Прозвище

3673

Крик души вирусолога

Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Крик души вирусолога Коронавирус, Вирусология, Здоровье, Медицина, Длиннопост
Показать полностью
preview
preview
preview
+8
3648

Ответ на пост «Совсем уже обнаглели» 

3387

Тебе больше не повезёт

2861

Штош... не я первый начал

2813

Главное поддержать

Главное поддержать Находка, Моральная поддержка, Комментарии на Пикабу, Скриншот
2798

М.Видео извините

М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
М.Видео извините МВидео, Обман клиентов, Длиннопост
Показать полностью
preview
preview
preview
+5
2783

Эффективные менеджеры

Эффективные менеджеры
2778

Карательная кулинария

Карательная кулинария
2763

Минутка весёлых прозвищ

Минутка весёлых прозвищ Прозвища, Черный юмор, Комментарии на Пикабу, Длиннопост
Минутка весёлых прозвищ Прозвища, Черный юмор, Комментарии на Пикабу, Длиннопост
Показать полностью
preview
preview
2563

Джеки Чан делал самостоятельно большую часть трюков

Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Джеки Чан делал самостоятельно большую часть трюков Джеки Чан, Актеры и актрисы, Знаменитости, Раскадровка, Интервью, Фильмы, Каскадер, Покровитель, Работа, Травма, Длиннопост
Показать полностью
preview
preview
preview
+7
2527

Уже не половина зёрнышка в день

Уже не половина зёрнышка в день Дюймовочка, iPhone, Юмор
Показать полностью
preview
2527

Он предпочитает на 2000 лет постарше

Он предпочитает на 2000 лет постарше
2483

"Самая сложная часть межрасовых отношений, это совместное фото"

"Самая сложная часть межрасовых отношений, это совместное фото" Отношения, Расы, Фотография, Повтор
Показать полностью
preview
2481

Сколько всего над нами летает и чего

Похожие посты закончились. Возможно, вас заинтересуют другие посты по тегам: