@hesy

Как эффективно хранить canvas попискельно в БД с последующим отображением?

Стек: PHP (Laravel), JS, MySQL, Nginx, Websockets.

giphy.gif?cid=790b76115c04ec043355dadc40b70cee4ff942c07c48d2c0&rid=giphy.gif&ct=g

Есть canvas размером 2000х1000 (~2 млн. пикселей суммарно).
На этом полотне в реалтайме рисуют N подключенных клиентов по веб-сокетам.
Данные о пикселях лежат в MySQL.
Таблица
Schema::create('pixels', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id')->nullable();
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->smallInteger('x');
    $table->smallInteger('y');
    $table->string('color', 18);
    $table->timestamps();
    $table->index('id');
});

При каждом клике на стороне клиента, информация записывается в таблицу и разносится по подключенным клиентам.

Вопрос: как при открытии сайта, быстрее и эффективнее всего отобразить полотно со всеми пикселями из БД?

Как сейчас реализовано

Загрузка по чанкам: при открытии сайта делает N ajax запросов к апи, который возвращает чанк с пикселями. Использовал вариант с offset-limit, с where id > N and where id < N, эффект одинаковый.
Еще проблема в том, что запросы занимают большое кол-во времени, при этом блокируя другие открытые вкладки которые ждут пока завершатся предыдущие запросы с других вкладок (nginx php-fpm, очевидно завершать запросы, чтобы принять следующие плохая идея, т. к. нагрузка на сервер в эти моменты и так растет, а ожидаемое кол-во одновременно подключенных клиентов десятки тысяч (*в моих влажных мечтах)).


Вообще, идея с подобным подходом мне вообще не нравится, и я понимаю что это неправильно.
Может у вас есть идеи как подобное можно реализовать? И годится ли MySQL для таких целей?
  • Вопрос задан
  • 385 просмотров
Пригласить эксперта
Ответы на вопрос 3
FanatPHP
@FanatPHP
Чебуратор тега РНР
Ну как минимум за $table->string('color', 18); надо руки сразу отрывать
То есть на координатах наэкономили, 4 байта в сумме, а потом хоба - в 4 раза больше на несчастный цвет. при том что цветов там явно не больше десятка. И это не говоря уже про нормализацию.

По уму надо придумать простой бинарный протокол, который получает поток байт выравненными кусками
2 байта х
2 байта у
1 байт цвет
4 байта юзер (и нечего жадничать, никаких BigInteger. половины населения земного шара вполне хватит)
то есть всего 9 байт.
а не под сотню, как сейчас - ещё и завернутое в скобочки/кавычечки джейсона

Если подумать, то можно юзера сразу не передавать. Всех юзеров никто смотреть не будет, а при наведении можно и отдельный запрос послать. Тогда можно и BigInteger оставить.
Получится всего 5 байт на пиксель, то есть 10 метров на всю карту. Дофига, но подъемно.

По пагинации это дурь какая-то. Зачем "офсет-лимиты" если уже есть четкая разбивка.
Кто мешает запрашивать тупо построчно? Скажем, по 100 строк картинки? 10 запросов по мегабайту.
id в этой таблице по сути вообще не нужно, только если лара без него не сможет. Но по уму первичный ключ - это ху.

Из БД получать 2 лимона строк конечно тоже не сахар
Но можно наверное увеличить строки, хотя бы виртуально.
Вью или процедура, которая комбинирует скажем сто строк в одну

Насчет других хранилищ я не уверен. Там же наверняка нужна будет выборка обновлений, по таймстампу.
Но в целом с Редисом поэкспериментировать можно.
Ответ написан
lastuniverse
@lastuniverse
Всегда вокруг да около IT тем
Я сделал бы несколько иначе, сохранял в бд примерное следующее:

1. кто, когда и как рисовал
userID - идентификатор пользователя
timeStamp - временная метка
point - координата мышки

2. информация о смене инструмента рисования (кисточки) и настроек инструмента
userID - идентификатор пользователя
timeStamp - временная метка
toolType - тип инструмента (кисточки)
toolSettings - JSON строка с параметрами инструмента(цвет, размер и т.д.) (у разных кисточек могут быть свои, уникальные наборы параметров, поэтому систематизировать не выйдет, и поэтому JSON строка)

Имея такие записи в 2-х таблицах получим:
- в большинстве случаев гораздо меньший объем сохраняемой информации
- возможность undo как для своих действий, так и для действий других пользователей
- возможность в последствии отобразить не только результат совместного творчества, но и сам процесс
- возможность отключать на холсте с результатом как художества отдельных пользователей так и результат применения отдельных инструментов

ЗЫ: от себя добавлю, что для этой задачи в том виде как ее описал я больше подойдут noSQL базы, в которых можно будет обойтись одной коллекцией и несколькими типами записей:

1. запись о смене инструмента
{"action": "changeTool",  userID: "xxxxxxx",  timeStamp: 0000000000000, toolType:"pen"}


2. запись об изменении параметров инструмента
{"action": "changeToolSettings",  userID: "xxxxxxx",  timeStamp: 0000000000000, setings: {
    тут у каждого инструмента что-то свое, общими наверно будут только цвет и размер
}}


3. запись об пути мазюкания инструментом
{"action": "path",  userID: "xxxxxxx", path:[
    {timeStamp: 0000000000000, "x": 0, "y": 0},
    {timeStamp: 0000000000000, "x": 0, "y": 0},
    {timeStamp: 0000000000000, "x": 0, "y": 0},
    {timeStamp: 0000000000000, "x": 0, "y": 0},
    ....
]}
Ответ написан
mayton2019
@mayton2019
Bigdata Engineer
Для оптимизации загрузки landing page, можно хранить копии этой картинки в уменьшенном виде. 2000х1000 => 1000x500 => 500x250 e.t.c. Так игроделы делают для быстрого рендеринга текстур. Mip-уровни кажется называется.
Обновлять эти mip уровни можно не спеша. Через минутку.

База mysql вам вобщем-то не нужна. Картинку можно в реальном времени править atomic операциями и хранить в raw формате. Не знаю делают ли такое на PHP. Возможно вам нужен рукастый програмист на C++ или еще на каком-то языке чтобы подружить PHP с С++ и сделать сервис для этой картинки.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
20 апр. 2024, в 09:15
8000 руб./за проект
20 апр. 2024, в 08:39
100000 руб./за проект
20 апр. 2024, в 08:24
1500 руб./за проект