PHP форумы - как справиться с непрочитанными обсуждениями / темы / посты

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

о непрочитанных обсуждениях / темах / сообщениях, есть о чем подумать. Я не знаю, как сделать форум, как MyBB, vBulletin, Invision Power Board, ванилин, phpBB, etc. справиться с этой проблемой, поэтому я хотел бы прочитать от вас, ребята, ваш опыт с этим. Я знаю, что использование таблицы базы данных только для этого-самый простой способ, но это потребует огромного чтения / записи, когда сообщество имеет более 10 000 членов и 1000 новых тем каждый месяц. Это сложно, но должен быть способ избежать перегрузки сервера.

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

7 ответов


не так много вариантов.

  1. отметьте каждый поток чтения каждым пользователем.

    • Недостатки: много строк в очень активных форумов
    • преимущества: каждый пользователь знает с пост читал или нет.
  2. отметьте каждый непрочитанный поток каждым пользователем.

    • Недостатки: много места с "нечитаемыми" строками, если есть бездействие много пользователи
    • решения: добавьте метку времени жизни и удалите старые записи с помощью cron
    • преимущества: каждый пользователь знает с пост читал или нет.
  3. используйте метки времени, чтобы определить, показывать ли его как непрочитанный или нет.

    • недостатки: пользователи не знают, с реальными непрочитанными потоками, метки показывают только "новые trheads" с момента последнего входа
    • Преимущество: Сохраньте космос!--8-->

другой альтернативой является смешивание растворов, то есть

1 и 3) показать поток как "непрочитанный", если они не старше X дней, и нет строки, помеченной как прочитанная для пользователя. Строки "read" могут быть удалены, когда они на X день старше, не затрагивая ничего.

преимущества

  • меньше расстояния используется для определения непрочитанных потоков

недостатки

  • создать cron, который держит систему в чистоте
  • пользователи не знают, читают ли они потоки старше x дней.

преимущества

  • каждый пользователь знает, какие "новые сообщения" прочитал или нет.

есть... другой.

другой способ хранения подробных прочитанных / непрочитанных данных для иерархической структуры форума(доска > раздел > поток и т. д.). Он делает это без a) необходимости предварительного заполнения прочитанной/непрочитанной информации и b) без необходимости хранить больше, чем U*(M/2) строк в худшем случае, где U-количество пользователей, а M-общее количество сообщений в базе данных (и обычно намного, намного меньше этого)

Я исследовал эту тему некоторое время назад. Я нашел это SMF / phpBB "обмануть" немного в том, как они хранят историю чтения пользователя. Их схема поддерживает хранение последних меток времени или идентификатора сообщения, который был отмечен как прочитанный в данной доске, форуме, подфоруме, теме (или просмотрен непосредственно браузером), например:

[ user_id, board, last_msg_id, last_timestamp ]

[ user_id, доска, форум, last_msg_id, last_timestamp ]

[ user_id, доска, форум, подфорум, last_msg_id, last_timestamp]

[ user_id, доска, форум, подфорум, тема, last_msg_id, last_timestamp ]

это позволяет пользователям отмечать определенные доски, форумы, темы и т. д., как прочтенный." Это требует, однако, либо действия со стороны пользователя (либо путем чтения, либо активного нажатия "отметить как прочитанное"), и в случае phpBB, не дает вам детализации, чтобы сказать: "я видел это конкретное сообщение, но не это конкретное сообщение."Вы также получаете ситуацию, когда вы сначала читаете последнее сообщение в теме (просмотр последнее действие в потоке), и вы сразу же предполагаете, что прочитали остальную часть потока.

он работает для SMF и phpBB Для хранения таких вещей, потому что редко вы просматриваете только один пост (представления по умолчанию настроены для 20+ сообщений на последней странице темы). Однако для более резьбовых форумов (особенно форумов, где вы просматриваете сообщения по одному) это не идеально. Пользователи этой системы, вероятно, будут заботиться много, если они прочитали один сообщение, но не другое, и может показаться громоздким, чтобы только иметь возможность пометить весь раздел как прочитанный, когда на самом деле они просто хотели, чтобы некоторые были помечены как прочитанные.

вы храните сообщения в таких кортежах: [ user_id, lower_msg_id, upper_msg_id ]

журнал истории пользователей поддерживается следующим образом:

при просмотре страницы функция смотрит, есть ли у user_id запись, где current_msg_id находится между lower_msg_id и upper_msg_id. Если да, то эта страница читается, и никаких действий не требуется. Если это не так, то должен быть выдан другой запрос, на этот раз определяющий, является ли current_msg_id одним меньше, чем lower_msg_id (current_msg_id == lower_msg_id-1), или одним больше, чем upper_msg_id (current_msg_id == upper_msg_id +1). Это тот случай, когда мы растем нашу "читаемую" или "видимую" границу на 1. Если мы от lower_msg_id или uppper_msg_id, то мы растем кортежа на 1 в этом направлении. Если мы не увеличиваем наш диапазон кортежей, то мы вставляем новый кортеж, [ user_id, current_msg_id, current_msg_id ].

угловой случай-это когда два диапазона кортежей приближаются друг к другу. В этом случае при поиске между нижней границей кортежа и верхней границей кортежа объедините две границы, установив верхнюю границу Нижнего кортежа на верхнюю границу верхнего кортежа, и удалите верхний кортеж.

пример кода в PHP:

function seen_bounds( $usr_id, $msg_id ) {

    # mysql escape
    $usr_id = mres( $usr_id );
    $msg_id = mres( $msg_id );

    $seen_query = "
        SELECT
            msb.id,
            msb.lower_msg_id,
            msb.upper_msg_id
        FROM
            msgs_seen_bounds msb
        WHERE
            $msg_id BETWEEN msb.lower_msg_id AND msb.upper_msg_id AND
            msb.usr_id = $usr_id
        LIMIT 1;
    ";

    # See if this post already exists within a given
    # seen bound.
    $seen_row = query($seen_query, ROW);

    if($seen_row == 0) {
        # Has not been seen, try to detect if we're "near"
        # another bound (and we can grow that bound to include
        # this post).
        $lower_query = "
            SELECT
                msb.id,
                msb.lower_msg_id,
                msb.upper_msg_id
            FROM
                msgs_seen_bounds msb
            WHERE
                msb.upper_msg_id = ($msg_id - 1) AND
                msb.usr_id = $usr_id
            LIMIT 1;
        ";

        $upper_query = "
            SELECT
                msb.id,
                msb.lower_msg_id,
                msb.upper_msg_id
            FROM
                msgs_seen_bounds msb
            WHERE
                msb.lower_msg_id = ($msg_id + 1) AND
                msb.usr_id = $usr_id
            LIMIT 1;
        ";

        $lower = query($lower_query, ROW);
        $upper = query($upper_query, ROW);

        if( $lower == 0 && $upper == 0 ) {
            # No bounds exist for or near this. We'll insert a single-ID
            # bound

            $saw_query = "
                INSERT INTO
                    msgs_seen_bounds
                (usr_id, lower_msg_id, upper_msg_id)
                VALUES
                ($usr_id, $msg_id, $msg_id)
                ;
            ";

            query($saw_query, NONE);
        } else {
            if( $lower != 0 && $upper != 0 ) {
                # Found "near" bounds both on the upper
                # and lower bounds.

                $update_query = '
                    UPDATE msgs_seen_bounds
                    SET
                        upper_msg_id = ' . $upper['upper_msg_id'] . '
                    WHERE
                        msgs_seen_bounds.id = ' . $lower['id'] . '
                    ;
                ';

                $delete_query = '
                    DELETE FROM msgs_seen_bounds
                    WHERE
                        msgs_seen_bounds.id = ' . $upper['id'] . '
                    ;
                ';

                query($update_query, NONE);
                query($delete_query, NONE);
            } else {
                if( $lower != 0 ) {
                    # Only found lower bound, update accordingly.
                    $update_query = '
                        UPDATE msgs_seen_bounds
                        SET
                            upper_msg_id = ' . $msg_id . '
                        WHERE
                            msgs_seen_bounds.id = ' . $lower['id'] . '
                        ;
                    ';

                    query($update_query, NONE);
                }

                if( $upper != 0 ) {
                    # Only found upper bound, update accordingly.
                    $update_query = '
                        UPDATE msgs_seen_bounds
                        SET
                            lower_msg_id = ' . $msg_id . '
                        WHERE
                            msgs_seen_bounds.id = ' . $upper['id'] . '
                        ;
                    ';

                    query($update_query, NONE);
                }
            }
        }
    } else {
        # Do nothing, already seen.
    }

}

Поиск непрочитанных сообщений-это поиск, где current_msg_id не существует между любым lower_msg_id и upper_msg_id для данного пользователя (запрос не существует в терминах SQL). Это не самый эффективный из запросов при реализации в реляционной базе данных, но может быть решена путем агрессивного индексирования. Например, следующий SQL-запрос для подсчета непрочитанных сообщений для данного пользователя, группы обсуждения ("товар"), что посты в:

$count_unseen_query = "
    SELECT 
        msgs.item as id,
        count(1) as the_count
    FROM msgs
    WHERE
    msgs.usr != " . $usr_id . " AND
    msgs.state != 'deleted' AND
    NOT EXISTS (
       SELECT 1 
       FROM 
          msgs_seen_bounds msb
       WHERE 
          msgs.id BETWEEN msb.lower_msg_id AND msb.upper_msg_id
          AND msb.usr_id = " . $usr_id . "
    )
    GROUP BY msgs.item
    ;

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

учитывая небольшой форум около 2000 + сообщений, ниже приведены статистические данные об использовании количества сохраненных кортежей, отсортированных по количеству раз, когда пользователи вошли в систему (приблизительно активность пользователя). Столбец "num_bounds" - это количество кортежей, необходимых для хранения просмотра пользователем "num_posts_read история.

id  num_log_entries num_bounds num_posts_read num_posts
479             584         11           2161       228
118             461          6           2167       724
487             119         34           2093       199
499              97          6           2090       309
476              71        139            481        82
480              33         92            167        26
486              33        256            757       154
496              31        108            193        51
490              31         80            179        61
475              28        129            226        47
491              22         22           1207        24
502              20        100            232        65
493              14         73            141         5
489              14         12           1517        22
498              10         72            132        17

Я не видел эту конкретную реализацию на любом форуме, кроме моего собственного пользовательского, и это небольшой. Мне было бы интересно, реализовал ли кто-нибудь еще или видел это в другом месте, особенно на большом и/или активном форуме.

С уважением,

Кайден


не совсем PHP-ответ, но вот как мы это делаем в нашем asp.сетевой форум (Я связан с этим продуктом, раскрывая это из-за правил)

  1. мы используем cookies, а не базы данных.
    • минус cookies-не " кросс-устройства "(посещение с другого компьютера показывает все как непрочитанное)
    • преимущество - нет огромной БД читает / пишет. И отслеживание работает для " guest" пользователи также! Это потрясающе.
  2. мы храним печенье с { topicID, lastReadMessageID } пары для каждой темы, которую посещает пользователь.
  3. если данные для конкретной темы не нашел в "cookie" мы предполагаем, что тема либо:
    • полностью непрочитано (если последнее сообщение темы больше MAX lastReadMessageID У (2)
    • полностью прочитайте (если в противном случае)

это имеет некоторые незначительные недостатки, но это делает работа.

PS. Кроме того, некоторые могут сказать, что использование cookies оставляет мусор на компьютере пользователя (я лично ненавижу это), но мы выяснили, что средний пользователь отслеживает около 20 тем, поэтому он занимает около 10 байт на тему, поэтому он занимает менее 200 байт на жестком диске пользователя.


почему вы обеспокоены?

Я не вижу проблемы с любым вводом-выводом для получения непрочитанных потоков. Не обязательно жить. 15-минутная задержка на основе значения кэша будет работать.

поэтому для непрочитанных потоков вы просто

псевдокод..

$result = SELECT id,viewcount from my_forum_threads

$cache->setThreads($result['id'],$result['viewcount']);

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

средняя страница на моем сайте занимает 20 mysql запросы. Когда я кэширую, это только два-четыре запроса.


почти любой форум, который я знаю, будет использовать какую-то временную метку ссылки, чтобы определить, следует ли считать поток/сообщение "непрочитанным" или нет. Эта метка времени обычно является датой / временем последнего действия, которое вы совершили во время предыдущего посещения форума.

таким образом, вы держите ie. метка времени previous_last_action & last_action в пользовательской таблице last_action обновляется при каждом действии пользователя, столбец previous_last_action устанавливается один раз в last_action при входе в систему (или при создании нового сеанса - если у вас есть функциональность" Запомнить меня"). Чтобы определить, является ли поток/сообщение непрочитанным, вы должны сравнить эту метку времени создания (или обновления) потока/сообщения со значением в previous_last_action для пользователя, вошедшего в систему.


быстрый ответ о том, как (я думаю) IPB делает это:

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

все сообщения менее 30 дней отслеживаются как запись JSON для каждой категории ID пользователя+. Пример: 12 категорий с 1000 активными пользователями = максимум 12 000 строк.

существует поле "непрочитанный счетчик" для быстрого поиска, скажем, на форуме Home, или где-то еще просто номер нужен.

Я мог бы быть полностью выключен на фактическом хранилище MySQL. Я не мог найти документацию по этому вопросу, но я покопался в базе данных и увидел таблицу, которая /выглядела/ как прочитанные/непрочитанные потоки (таблица: core_item_markers, для справки). Но я положительно отношусь к гибридной модели age / mysql.


Я прочитал все ответы и у меня появилась идея, что может быть лучшим сочетанием для этой темы (без кода).
Эта идея представляет собой смесь всех ваших идей и мало опыта у меня в программировании
Aprox 95% пользователей (статистика, полученная от администратора форума и его журналов форума) читают темы форума прямо до последнего сообщения (или страницы) и не возвращаются, читают сообщения 1-й страницы (или только 1-й пост) , а затем переходят на последнюю страницу, или они читают весь поток из от начала до конца, и если они повернут назад, они уже прочитали эту часть. Таким образом, хорошее решение будет работать следующим образом:
Я думаю, что если мы сделаем магазин для каждого пользователя, для каждого потока, метку времени последнего сообщения, которое пользователь просматривал (и, если применимо, первое сообщение, которое пользователь просматривал, даже если это не может быть полезно), мы могли бы получить что-то с этим. Система довольно проста и почти как phpbb. Было бы также полезно отметить последний пост, который мы видели, чтобы продолжить в этом один позже (вместо того, чтобы рассматривать всю эту страницу как прочитанную). И, поскольку каждый поток имеет свой собственный id. Нет необходимости организовывать, как это делает phpbb.