28

私はこの質問がここで数回尋ねられたことを知っています、しかし答えのどれも私を喜ばせませんでした。これは、ほとんどすべてがデータベースに関連する膨大な読み取り/書き込みプロセスを伴うためです。これは絶対に避けたいと思います。

未読のディスカッション/トピック/投稿については、考えることがたくさんあります。MyBBvBulletin、 Invision Power BoardVanillaphpBBなどのフォーラムシステムがこの問題にどのように対処するのかわかりません。そのため、皆さんからの経験をお読みしたいと思います。そのためだけにデータベーステーブルを使用するのが最も簡単な方法であることを私は知っていますが、コミュニティに毎月10,000を超えるメンバーと1000の新しいトピックがある場合、それは膨大な読み取り/書き込みを伴います。難しいですが、サーバーの過負荷を回避する方法があるはずです。

では、この問題のベストプラクティスとして、また他のフォーラムシステムがどのように対処するかについて、どのように考えていますか?

4

7 に答える 7

9

他にもある。

階層的なフォーラム構造 (ボード > セクション > スレッドなど) の詳細な既読/未読データを保存する別の方法。これは、a) 既読/未読の情報を事前入力する必要がなく、b) 最悪の場合に U*(M/2) 行を超える行を保存する必要がなく、U はユーザー数、M はデータベース内の投稿の総数 (通常は、これよりはるかに少ない)

私はこのトピックについて少し前に調査しました。SMF/phpBB は、ユーザーの読み取り履歴を保存する方法を少し「ごまかす」ことがわかりました。それらのスキーマは、次のように、特定のボード、フォーラム、サブフォーラム、トピックで既読としてマークされた (またはブラウザーによって直接表示された) 最後のタイムスタンプまたはメッセージ ID のいずれかのストレージをサポートします。

[ user_id、ボード、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 がこのようなものを保存するのに役立ちます。これは、1 つの投稿だけを表示することはめったにないためです (トピックの最後のページにある 20 件以上の投稿に対してデフォルトのビューが設定されています)。ただし、よりスレッド化されたフォーラム (特に、一度に 1 つずつメッセージを表示するフォーラム) では、これは理想的とは言えません。このシステムのユーザーは、あるメッセージを読んで別のメッセージを読んでいないかどうかを非常に気にする可能性が高く、実際にはいくつかのメッセージを既読にしたいのに、セクション全体を既読にすることしかできないのは面倒だと思うかもしれません.

次のようなタプルにメッセージを保存します: [ 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 より 1 小さいか (current_msg_id == lower_msg_id-1)、または upper_msg_id より 1 大きいか (current_msg_id == upper_msg_id +1) を判別します。これは、「読み取り」または「表示」境界を 1 ずつ大きくする場合です。タプル範囲を拡大していない場合は、新しいタプル [ user_id, current_msg_id, current_msg_id ] を挿入します。

コーナー ケースは、2 つのタプル範囲が互いに近づく場合です。この場合、下位タプル境界と上位タプル境界の間を検索する際に、下位タプルの上位境界を上位タプルの上位境界に設定して 2 つの境界をマージし、上位タプルを削除します。

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.
    }

}

未読の投稿を検索すると、特定のユーザーの lower_msg_id と upper_msg_id の間に current_msg_id が存在しない場所が見つかります (SQL 用語では NOT EXISTS クエリ)。リレーショナル データベースに実装する場合、これは最も効率的なクエリではありませんが、積極的なインデックス作成によって解決できます。たとえば、次の 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

この特定の実装は、私自身のカスタム フォーラム以外のフォーラムでは見たことがありません。それは小さなものです。特に大規模および/または活発なフォーラムで、他の誰かが実装したか、これが他の場所で実装されているのを見た場合、私は興味があります。

よろしく、

カイデン

于 2011-02-18T19:46:30.840 に答える
3

正確にはPHPの回答ではありませんが、asp.netベースのフォーラムでそれを行う方法を次に示します(私はこの製品に所属しており、ルールのためにそれを開示しています)

  1. データベースではなくCookie を使用します。
    • Cookie のデメリット- 「クロスデバイス」ではありません (別のコンピューターからアクセスすると、すべてが未読として表示されます)
    • 利点- 巨大な DB の読み取り/書き込みがありません。また、追跡は「ゲスト」ユーザーにも機能します。これは素晴らしいです。
  2. { topicID, lastReadMessageID }ユーザーがアクセスするすべてのトピックのペアを含む Cookie を保存します。
  3. 特定のトピックのデータがCookie に 見つからない場合、トピックは次のいずれかであると見なされます。
    • lastReadMessageID完全に未読 (トピックの最後のメッセージが(2)の MAX より大きい場合)
    • 完全に読んだ(そうでない場合)

これにはいくつかの小さな欠陥がありますが、機能します。

PS。また、Cookie を使用するとユーザーのコンピューターにゴミが残ると言う人もいるかもしれませんが (個人的にはこれが嫌いです)、平均的なユーザーは約 20 トピックのトップを追跡することがわかったので、トピックごとに約 10 バイトかかるので、200 バイト未満で済みますユーザーのハードドライブ上。

于 2013-12-15T11:56:23.663 に答える
1

私が知っているほとんどすべてのフォーラムは、何らかの参照タイムスタンプを使用して、スレッド/メッセージを「未読」と見なすかどうかを決定します。このタイムスタンプは、通常、フォーラムへの前回のアクセスで実行した最後のアクションの日付/時刻です。

だからあなたはつまりを保ちます。ユーザー テーブル内の previous_last_action & last_action タイムスタンプ。last_action はユーザー アクションごとに更新されます。ログイン時 (または新しいセッションの作成時 - 「remember me」機能がある場合) に、previous_last_action 列が一度 last_action に設定されます。スレッド/メッセージが未読かどうかを判断するには、そのスレッド/メッセージの作成 (または更新) タイムスタンプを、現在ログインしているユーザーの previous_last_action の値と比較します。

于 2010-02-18T16:16:23.507 に答える
1

なぜあなたは心配していますか?

未読スレッドを取得するための I/O に問題はありません。ライブである必要はありません。キャッシュ値に基づく 15 分の遅延は機能します。

したがって、未読のスレッドの場合は、

擬似コード..

$result = SELECT id,viewcount from my_forum_threads

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

次に、ページの読み込み時に、データベースを再度クエリするのではなく、キャッシュ値を取得するだけです。それは本当に大きな問題ではありません。

私の Web サイトの平均的なページでは、20 回の mysql クエリが必要です。キャッシュすると、2 ~ 4 個のクエリしかありません。

于 2010-02-18T13:29:14.270 に答える
1

IPBがどのように(私が思うに)それを行うかについての簡単な答え:

構成量 (デフォルトでは 30 日) より古いすべての投稿は、自動的に既読としてマークされます。cron ジョブは、管理可能なサイズを維持するために、各ユーザーからこれらを削除します。

30 日未満のすべての投稿は、各ユーザー ID + カテゴリの JSON エントリとして追跡されます。例: 1000 人のアクティブ ユーザーを持つ 12 のカテゴリ = 最大 12,000 行。

たとえば、フォーラムのホームなど、数字だけが必要な場所をすばやく検索するための「未読カウント」フィールドがあります。

実際の MySQL ストレージを完全にオフにすることができます。これに関するドキュメントは見つかりませんでしたが、データベースを調べたところ、既読/未読のスレッドのように見えるテーブルが見つかりました (テーブル: core_item_markers、参照用)。しかし、私はハイブリッド age/mysql モデルには肯定的です。

于 2013-07-10T17:19:57.020 に答える
0

私はすべての答えを読み、この主題に最適な組み合わせである可能性があるというアイデアを思いつきました(ただしコードはありません)。
このアイデアは、すべてのアイデアと、Aproxのプログラミングで私が経験したわずかな経験を組み合わせたものです。
ユーザーの95%(フォーラム管理者と彼のフォーラムログから取得した統計)は、フォーラムのトピックを最後の投稿(またはページ)まで直接読んでいます。戻って、最初のページの投稿(または最初の投稿だけ)を読んでから最後のページに移動しないでください。または、スレッド全体を最初から最後まで読んで、戻った場合はすでにそれを読んでいます。部。したがって、適切なソリューションは次のように機能します。
ユーザーごと、スレッドごとにストアを作成すると、ユーザーが最後に表示した投稿のタイムスタンプ(および、該当する場合は、役に立たない場合でもユーザーが最初に表示した投稿)を取得できると思います。これでどこかに。システムは非常にシンプルで、phpbbのシステムとほとんど同じです。また、(そのすべてのページを既読と見なすように強制されるのではなく)後でその投稿に続くことがわかった最後の投稿にマークを付けることも役立ちます。また、各スレッドには独自のIDがあるためです。phpbbのように整理する必要はありません。

于 2011-02-24T13:41:18.150 に答える