Русский
Русский
English
Статистика
Реклама

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

В этом посте расскажу как устроена одна из самых глючных функций Пикабу - скрытие просмотренных постов. У этой функции богатая история проб и ошибок, 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 часто зависает в свежем.

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

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

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

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

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


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

Тест на сексуальность пройден Сбербанк, Карты, Скриншот, Юмор
Показать полностью 1
Показать полностью
Инглиш Юмор, Странный юмор, Мат
Показать полностью 1
Осторожней надо с ритуалами Ктулху, Говард Филлипс Лавкрафт, Монстр, Модератор, Комментарии на Пикабу, Ритуал, Призыв, Скриншот
Показать полностью
Показать полностью
Заботьтесь об окружающей среде Twitter, Скриншот, Юмор
Пожалели Комментарии на Пикабу, Скриншот, Внешность обманчива, Юмор, Жена, Испанцы
Пахнет стекольщик... Мемы, Авто, Обочечники, Юмор, Рифма
Показать полностью 1
Про маленькую инди-студию и "сервис" Diablo II: Resurrected, Blizzard, Компьютерные игры, Прогресс, Извинение
Показать полностью 1
Подозрительно. Может позвонить в ФСБ? Платная парковка, Подозрительно, Парковка
Покраска потолков Черный юмор, Google, Bing
Показать полностью 1
Источник: ktonanovenkogo.ru
К списку статей
Опубликовано: 12.10.2021 10:28:32
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Пикабу

Скрытие постов

Длиннопост

[моё]

Республика беларусь

Пожар

Спасение жизни

Дети

Текст

Без рейтинга

Сбербанк

Карты

Скриншот

Юмор

Москва

Дагестан

Рабство

Похищение

Видео

Негатив

Кавказцы

[моё]

Вахта

Договор

Работа

Текст

Без рейтинга

Трудоустройство

Нужен совет

Помеха справа

Вертикальное видео

Знаки

Уступи дорогу

Инструктор

Видео

Авто

Пдд

Юмор

Странный юмор

Мат

Ктулху

Говард филлипс лавкрафт

Монстр

Модератор

Комментарии на пикабу

Ритуал

Призыв

Скриншот

[моё]

Покупка недвижимости

Риэлтор

Ипотека

Странные люди

Мат

Длиннопост

Текст

[моё]

История

Кража

Офис

Ответ на пост

Текст

[моё]

Истории из жизни

Текст

Отношения

Жадность

Twitter

Скриншот

Юмор

[моё]

Текст

Покупатель

Продавец

Дюйм

Комментарии на пикабу

Скриншот

Внешность обманчива

Юмор

Жена

Испанцы

Спам

Робот

Лайфхак

Bash im

Текст

[моё]

Мемы

Авто

Обочечники

Юмор

Рифма

Diablo ii: resurrected

Blizzard

Компьютерные игры

Прогресс

Извинение

[моё]

Платная парковка

Подозрительно

Парковка

Забавное

Видео

Собака

Прятки

Растерянность

Реклама

Забавное

Видео

Toyota

Авто

Черный юмор

Google

Bing

Последние комментарии

  • Имя: Шеронова Наталья
    08.09.2021 | 00:08
    Отдельное спасибо за баобаб :) Подробнее..
  • Имя: 13
    16.06.2021 | 16:37
    Неожиданно увидеть свое фото и ссылку на левый источник ... Я размещал это фото на пикабу. Подробнее..
© 2006-2024, shop-archive.ru