LINUX.ORG.RU

Оптимальный способ хранения списка пользователей онлайн


0

2

Допустим, есть условный форум/чат/доска/etc с пользователями. Раньше я такое делал с помощью отдельной обычной SQL-таблицы, в которой хранились ники пользователей и время последней активности. Каждый раз, когда пользователь запрашивал страницу с сервера, он сначала вносился в эту таблицу(или обновлялось время последней активности), а потом из этой таблицы ему выдавался список всех пользователей онлайн.

Как такие задачи решаются в современном вебе?

★★
Ответ на: удаленный комментарий

Как такие задачи решаются в современном вебе?

Современный web - это посложнее, да побольше всяких микросервисов? Тогда ставим очередь (Kafka, например, или если есть что-нибудь ещё по тяжелее, то её). Шлём туда сообщения об активности юзера. Пишем сервис, который будет читать эти сообщения и рассылать другим клиентам по web-socket.

А в существующем решении какие недостатки, что появилось желание это переделать?

ПС. В монго есть ttl индех, по которому записи будут сами удаляться по полю типа DateTime.

her_s_gory
()
Ответ на: комментарий от her_s_gory

А в существующем решении какие недостатки, что появилось желание это переделать?

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

u5er ★★
() автор топика
Ответ на: комментарий от u5er

Я просто подумал, что если будет 100500 пользователей

Навскидку.

Можно кешировать в том-же redis. Кеш обновлять раз в пару минут воркером, клиенту отдавать список из кеша. Статус пользователя онлайн - это же не realtime?

EDIT:

Кеш обновлять раз в пару минут воркером

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

her_s_gory
()
Последнее исправление: her_s_gory (всего исправлений: 1)
Ответ на: комментарий от her_s_gory

Статус пользователя онлайн - это же не realtime?

Немного не понял вопроса. Просто выборка из таблицы по времени последней активности. Если речь про форумы и доски, то там «таймаут» обычно порядка 10-15 минут. Если речь про чаты, то секунд 20-30.

u5er ★★
() автор топика
Ответ на: комментарий от u5er
if (LanguageName=="C#"||LanguageName=="C"||LanguageName=="C++"||LanguageName=="Java"||LanguageName=="Python"||LanguageName=="Kotlin"||LanguageName=="JS"||LanguageName=="Scala")

вроде всё, остальные яп не нужны, хотя на самом деле среди C#, Java и Kotlin тоже надо один выбрать. Я бы выбрал C# как самый менее уродский.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от Tark

Отвечу с анонима. Нет не станет. Там другие проблемы. Когда тебе нужно 10.000 rps, а фреймворк на джабке или пистоне способен только 1000 выдать, то для решения этой проблемы запускается 10 инстансов приложения, работающих под нгинкс или иным балансировщиком… Вот поэтому апишки работают по rest и не зранят состояния в сессиях, которые по сути ничем не отличаются от хранения данных в глобальном словарике… Ага. Список юзверей онлайн удобнее всего хранить в редисе, он для такого и спроектирован, там можно задать время жизни записи, после которых она протухнет и будет удалена, и данные между кучей инстансов будут синхранизованы

anonymous
()
Ответ на: комментарий от u5er

Я просто подумал, что если будет 100500 пользователей и при каждом запросе будет выполняться обновление и запрос списка пользователей из таблицы, то это создаст большую конкуренцию в первую очередь за диск.

А ты хочешь на каждый запрос отдавать сразу всех 100500 пользователей?

theNamelessOne ★★★★★
()

Redis же. Хранит данные в памяти, супер быстро, при необходимости можно дропнуть данные на диск.

Куча разных встроенных структур вроде хешей, мапов и прочего. Даже зачатки очередей и pub/sub есть.

Библиотеки есть для любого ЯП.

skyman ★★★
()
Ответ на: комментарий от anonymous

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

Tark ★★
()

В играх есть такая оптимизация называется пространственное хеширование (spatial hashing) суть её заключается в том что у тебя в сцене могут быть 10005000 объектов и тебе в момент времени нужно узнать с какими из них можно взаимодействовать. Для этого пространство разбивается на блоки фиксированной ширины и высоты, а каждое действие объекта приводит к пересчёту того в каком блоке он сейчас находится, так игрок окружённый миллионом шариков может взаимодействовать лишь с 50тью например, лопнув их иголкой, а до остальных он просто не дотянется и они будут просто проигнорированы всеми расчётами.

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

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

хранить всё в оперативке.

Вот для наглядности онлайн пример

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

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

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

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от LINUX-ORG-RU

Ты усложняешь, ему нужна либо мепа/куча, где ключ - это айди юзера, значение это время протухания, либо Redis.

Если мепой пользуемся, то раз в какое-то время пробегаемся по ней и удаляем те ключи для которых время меньше текущего. А в Redis такое вроде из коробки есть.

Norgat ★★★★★
()
Последнее исправление: Norgat (всего исправлений: 1)
Ответ на: комментарий от Norgat

значение это время протухания

Время потухания надо обходить и потухать =) Хотя смотря как делать…

Ты усложняешь

Наверное, да. Зато прикольно и ничего обходить не надо, всё всегда актуально :)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

А не слишком ли накладно? Может проще удалять тех пользователей, которые ушли в оффлайн? Я имею в виду из списка онлайн. Тогда просто выборка select * from `online`;(в случае sql бд) и всё.

u5er ★★
() автор топика
Ответ на: комментарий от u5er

А не слишком ли накладно?

Понятия не имею, реализовать можно 100500 миллиардами способов =) Моё дело предложить, а ляжет ли это хорошо на реальный случай угадать невозможно.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от rtxtxtrx

В нормальных языках, тех, которые не рождены чтобы сдохнуть как php (и прочих cgi) можно и без таблиц хранить в словаре/объекте/ассоциативном массиве в виде id => last_seen

Можно примеры этих языков с привязкой по временной шкале? Когда создавался php что было лучше? CGI для своего времени крутая штука, как говорится «аналогов нет».

anonymous
()
Ответ на: комментарий от LINUX-ORG-RU

Наверное, да. Зато прикольно и ничего обходить не надо, всё всегда актуально :)

Ну у тебя обход зашит в алгоритм, только сложнее в реализации будет. Обход нужен для очистки памяти, не более. Проверка времени делается в один if.

А так - поставить Redis и не тратить на это время.

Norgat ★★★★★
()
Ответ на: комментарий от rtxtxtrx

можно и без таблиц хранить в словаре/объекте/ассоциативном массиве в виде id => last_seen

Только если у тебя поделка для курсовой. В реальном мире нужно общее хранилище. Т.е. какая-то БД.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Тебе для ознакомления, или чтобы решить задачу?

Для ознакомления и расширения кругозора. Всю дорогу я использовал связку апач+пых+мускуль. Для моих хобийных поделок хватает полностью. Хочу узнать, как подобные задачи решаются «по-взрослому».

u5er ★★
() автор топика
Ответ на: комментарий от u5er

Первая мысль - использовать таблицу в оперативной памяти

Какого типа таблицы? С блокировкой всей таблицы при записи, тормоза возникнут не с 100500, а уже с 100 пользователями.

yandrey ★★
()

Как такие задачи решаются в современном вебе?

В современном вебе это решается так же как и не в современном. Сначала делается как проще и быстрее реализовывать, а потом уже, если результат не укладывается во временные рамки, делается нагрузочное тестирование, и оптимизируются узкие места. Ну и в 99% случаях узкие места очень неожиданны, оптимизировать надо то что тормозит, а не то что хочется оптимизировать. А то понабегут клоуны со своими кафками и получается оптимизация в 10 раз медленнее и не там где надо.

vtVitus ★★★★★
()
Ответ на: комментарий от rtxtxtrx

В нормальных языках, тех, которые не рождены чтобы сдохнуть как php (и прочих cgi) можно и без таблиц хранить в словаре/объекте/ассоциативном массиве в виде id => last_seen

В современном подходе к деплою инстансы - расходный материал. Их с лёгкостью прибивают и перезапускают. Поэтому, словарь/объект/массив здесь не подходит.

anonymous
()

сделай ендпоинт который будет получать презенс запросы от клиентов и раз в 5 секунд сбрасывать собранные данные в эту твою табличку

Syncro ★★★★★
()
Ответ на: комментарий от rtxtxtrx

В нормальных языках, тех, которые не рождены чтобы сдохнуть как php (и прочих cgi) можно и без таблиц хранить в словаре/объекте/ассоциативном массиве в виде id => last_seen

Только если один инстанс этого процесса будет.

her_s_gory
()
Ответ на: комментарий от her_s_gory

я сам это же и указал с анонима чуть далее моего сообщения. хватит мне писать про это. да и масштабирование не потребуется с вероятностью 99%, 80% проектов мертворожденные и умирают в первые 2 года

rtxtxtrx ★★
()
Ответ на: комментарий от u5er

Вот так же и решаются. Все зависит от конкретной задачи, нагрузки, масштабов и т. д.
Так тут уже задали хороший вопрос: а тебе только количество показывать, список (top N), узнать живой ли конкретный пользователь (напр. чтобы возле логина показывать зелененький кружок)?
Обычно, в реальности, все начинают с простого решения и усложняют по мере надобности (роста нагрузки и числа пользователей). Так отображать число живых пользователей задача простая: хранишь время активности, как ты и сделал, фоновая задача (раз в N минут) чистит протухшие записи (кажется, Mongo сама даже так умеет), подсчитывает общее количество и кладет в отдельное место (БД, Memcached, Redis, ...). Потом просто отображается этот готовый счетчик, всегда актуальный.

Так то никаких чудес нет. Обычно, все просто кешируется в памяти а не читается напрямую из БД. Ну а кеш ты уже можеш строить как при запросе так и в фоне, как в моем примере выше.

urxvt ★★★★★
()

Делал такое через Redis. На сайте с десятком миллионов зарегистрированных пользователей работало нормально (сколько в пике одновременно было онлайн - не помню).

anonymous
()
Ответ на: комментарий от rtxtxtrx

хватит мне писать про это. да и масштабирование не потребуется с вероятностью 99%, 80%

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

я сам это же и указал с анонима чуть далее моего сообщения

Телепаты ещё из запоя не выходили.

хватит мне

Охренел? Это публичный форум, что хочу, то и пишу.

her_s_gory
()
Последнее исправление: her_s_gory (всего исправлений: 1)
Ответ на: комментарий от u5er

Сарказма не предполагал. Не совсем верно понял задачу просто. Точнее додумал подробности, которых не было.

Просто обычно хранят не только статус онлайн-офлайн, но и время последнего захода для вообще всех — это бывает полезно. Ну вот даже на том же ЛОРе в профиле есть. Я думал, у тебя такая же штука примерно.

CrX ★★★★★
()

Если надумаете свой велосипед строить, можете позаимствовать подход Redis, у них это сделано примерно так:

  1. Есть хеш-мапа с ключами-значениями и есть вторая хеш-мапа с ключами-TTLами

  2. При каждом запросе данных он лениво удаляет истёкшие записи. Получили ключ, а оказалось, что ключ истёк — удалить данные и сказать, что там ничего не было.

  3. Также постоянно крутится цикл, активно удаляющий истёкшие записи. На каждой итерации, N(=10) раз в секунду, берём M(=20) случайных записей и, если они истекли, то удаляем. Если было истечено больше K(=25)% ключей — повторить.

Начиная с Redis 6 ещё добавили оптимизацию:

  1. Если на предыдущем шаге были найдены записи, которые не истекли, но вроде бы скоро собираются, они добавляются в базисное дерево (radix tree) и на следующей итерации проверки начинаются сразу с этих записей.

Даже без последней оптимизации получается как-то элегантнее, чем тереть истёкшие записи по cron’у.

А если вам нужны только ключи и TTLы, без значений, можете вообще основную мапу из этой цепочки вырезать.

А лучше не изобретать велосипед и взять готовое решение. Не нравится лицензия — найдите альтернативу. Их, вроде бы, полно. 🤷‍♂️

witaway
()
Последнее исправление: witaway (всего исправлений: 1)

Тут это… В соседнем треде рекомендуют писать исключительно на С89 и HTML3, данные хранить в файлике, желательно в своём бинарном формате, и никакого мерзкого JS и HTTPS. Всё выше написанное – ересь и хипстерство.

anonymous
()