web-dev-qa-db-ja.com

このPHP / MySQLニュースフィードを改善するにはどうすればよいですか?

これは最善の解決策ではないことを知っていると言って、すぐに始めましょう。私はそれが気味悪いと機能のハックであることを知っています。 しかし、だから私はここにいる!

この質問/作業は、Facebookのニュースフィードの作成者である Andrew BosworthとのQuoraに関するいくつかの議論 から成り立っています。

ある種のニュースフィードを作成していますPHPMySQLのみで構築されます。

alt text


MySQL

フィードのリレーショナルモデルは、2つのテーブルで構成されています。 1つのテーブルはアクティビティログとして機能します。実際、_activity_log_という名前です。もう1つのテーブルはnewsfeedです。 これらのテーブルはほぼ同じです。

ログのschemaactivity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)です

...そして、フィードのschemanewsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)です。

ユーザーが何かをするたびにニュースフィードに関連する、たとえば質問をする、アクティビティログに記録されますすぐに。


ニュースフィードの生成

次に、X分ごと(現時点では5分、15〜30分後に変更されます)、cronジョブを実行します以下のスクリプトを実行します。このスクリプトは、データベース内のすべてのユーザーをループし、そのユーザーのすべての友人のすべてのアクティビティを見つけて、それらのアクティビティをニュースフィードに書き込みます。

現時点では、アクティビティをカリングするSQLActivityLog::getUsersActivity()で呼び出されます)には、パフォーマンス*の理由で_LIMIT 100_が課されています。 *私が話していることを知っているわけではありません。

_<?php

$user = new User();
$activityLog = new ActivityLog();
$friend = new Friend();
$newsFeed = new NewsFeed();

// Get all the users
$usersArray = $user->getAllUsers();
foreach($usersArray as $userArray) {

  $uid = $userArray['uid'];

  // Get the user's friends
  $friendsJSON = $friend->getFriends($uid);
  $friendsArray = json_decode($friendsJSON, true);

  // Get the activity of each friend
  foreach($friendsArray as $friendArray) {
    $array = $activityLog->getUsersActivity($friendArray['fid2']);

    // Only write if the user has activity
    if(!empty($array)) {

      // Add each piece of activity to the news feed
      foreach($array as $news) {
        $newsFeed->addNews($uid, $friendArray['fid2'], $news['activity'], $news['activity_id'], $news['title'], $news['time']);
      }
    }
  }
}
_

ニュースフィードを表示する

クライアントコードで、ユーザーのニュースフィードを取得するとき、次のようにします。

_$feedArray = $newsFeed->getUsersFeedWithLimitAndOffset($uid, 25, 0);

foreach($feedArray as $feedItem) {

// Use a switch to determine the activity type here, and display based on type
// e.g. User Name asked A Question
// where "A Question" == $feedItem['title'];

}
_

ニュースフィードの改善

ニュースフィードを開発するためのベストプラクティスについての私の限られた理解を許しますが、私が使用しているアプローチはfan-out on write、ユーザーのニュースフィードに直接書き込むのではなく、中間ステップとしてcronジョブを実行しているという意味で制限されています。ただし、これはプルモデルとは大きく異なります。つまり、ユーザーのニュースフィードはロード時にコンパイルされるのではなく、定期的にコンパイルされるという意味です。

これは、おそらく多くのやり取りに値する大きな質問ですが、私のような新しい開発者が必要とする多くの重要な会話の試金石として役立つと思います。私は自分が何を間違っているのか、どのように改善できるのか、あるいはゼロから始めて別のアプローチを試すべきなのかを把握しようとしています。

このモデルに関して私を悩ませているもう1つの点は、関連性ではなく最新性に基づいて機能することです。誰かがこれをどのように改善して関連性を高めることができるかを提案できれば、私はすべての耳になります。推奨事項の生成にDirected EdgeのAPIを使用していますが、ニュースフィードなどの場合、推奨者は機能しません(以前は何もお気に入りがなかったためです!)。

71
Josh Smith

本当にクールな質問。私は実際にこのようなことを自分で実装している最中です。だから、私は少し大声で考えるつもりです。

以下は、現在の実装で私の心に浮かぶ欠陥です。

  1. すべてのユーザーのすべての友人を処理していますが、同じグループの人々が同様の友人を持っているという事実のために、同じユーザーを何度も処理することになります。

  2. 友達の誰かが何かを投稿すると、せいぜい5分間はニュースフィードに表示されません。すぐに表示されるはずですよね?

  3. ユーザーのニュースフィード全体を読んでいます。前回ログをクランチしてから、新しいアクティビティを取得するだけでいいのではありませんか?

  4. これはうまくスケーリングしません。

ニュースフィードはアクティビティログとまったく同じデータのように見えるので、その1つのアクティビティログテーブルを使用します。

データベース間でアクティビティログを分割すると、より簡単にスケーリングできます。必要に応じてユーザーを分割することもできますが、1つのテーブルに1,000万のユーザーレコードがある場合でも、mysqlは読み取りを正常に実行できます。そのため、ユーザーを検索するたびに、ユーザーのログにアクセスするシャードがわかります。古いログを頻繁にアーカイブし、ログの新しいセットのみを維持する場合、それほどシャードする必要はありません。または多分まったく。適度に調整されていれば、MySQLで何百万ものレコードを管理できます。

Memcachedをユーザーテーブルと、場合によってはログ自体にも活用します。 Memcachedは最大1MBのキャッシュエントリを許可します。キーを整理するのが賢明であれば、キャッシュから最新のログをすべて取得できる可能性があります。

これは、アーキテクチャに関する限り、より多くの作業になりますが、リアルタイムで作業し、将来的にスケールアウトできるようになります...特に、ユーザーにcommenting各投稿で。 ;)

この記事を見ましたか?

http://bret.appspot.com/entry/how-friendfeed-uses-mysql

13
Dan Spiteri

統計キーワードを追加しますか?ドキュメントの本文を展開し、HTMLを削除し、一般的な単語を削除して、最も一般的な単語を数えることで(粗雑な)実装を行いました。私は数年前にそれを楽しみのために作成しました(そのようなプロジェクトの場合と同様、ソースはなくなりました)が、一時的なテストブログ/フォーラムのセットアップでは機能しました。ニュースフィードで機能するかもしれません...

0
Blender

Facebookスタイルのニュースフィードを自分で作成しようとしています。ユーザーのアクティビティを記録する別のテーブルを作成する代わりに、投稿、コメントなどのUNIONから「エッジ」を計算しました。

数学を少し使って、指数関数減衰モデルを使用して「エッジ」を計算します。時間経過は独立変数であり、各投稿がラムダ定数を定式化するためにコメント、いいねなどの数を考慮します。 Edgeは最初は速く減少しますが、数日後には徐々に0に近くなります(しかし0には決して達しません)

フィードを表示するとき、各エッジはRand()を使用して乗算されます。 Edgeの高い投稿がより頻繁に表示されます

これにより、より人気のある投稿ほど、ニュースフィードに表示される確率が高くなります。

0
Freeman Latif

Cronジョブを実行する代わりに、何らかのコミット後スクリプト。私はPHPとMySQLがこれに関してどのような機能を持っているのか具体的には知りません-MySQL InnoDBが他の種類よりも高度な機能を許可しているが、何かがあるかどうかは覚えていません最新バージョンのトリガーのように。

とにかく、多くのデータベースの魔法に依存しない単純な種類:

ユーザーXがコンテンツを追加するとき:

1)データベースのコミット後、PHPページから非同期呼び出しを行います(もちろんページを表示しているユーザーが待つ必要がないように非同期に!)

呼び出しは、論理スクリプトのインスタンスを開始します。

2)ロジックスクリプトはのみ新しいコンテンツをコミットしたユーザーの友人[A、B、C]のリスト(DBの全員のリストとは対照的に!)を通過し、アクションを追加しますユーザーXからこれらの各ユーザーのフィードへ。

これらのフィードを単純なJSONファイルとして保存し、それぞれの最後に新しいデータを追加することができます。もちろん、ファイルシステム、BerkeleyDB、Mongo、または好きなものへのバックアップを使用して、フィードをキャッシュに保持する方が良いでしょう。

これは、関連性ではなく、最新性に基づいたフィードの基本的な考え方にすぎません。この方法でデータを順番に保存してから、ユーザーごとに追加の解析を行って関連性でフィルタリングしますが、これはどのアプリケーションでも難しい問題であり、おそらく匿名のWebユーザーが詳細なしで簡単に対処できる問題ではありませんあなたの要件の知識;)

jsh

0
jsh

ユーザーフラグとキャッシュを使用できます。たとえば、last_activityというユーザー用の新しいフィールドがあるとします。ユーザーがアクティビティを入力するたびに、このフィールドを更新します。フィードを取得した時間にfeed_updated_onと言うまで、フラグを保持します。

関数$ user-> getAllUsers();を更新します。 feed_updated_onよりもlast_activity時間が遅いユーザーのみを返すようにします。これにより、アクティビティログを持たないすべてのユーザーが除外されます。ユーザーの友人のための同様のプロセス。

Memcacheやファイルレベルのキャッシュなどのキャッシュを使用することもできます。

または、すべてのフィードを1つのドキュメントとして保存するためにnosql DBを使用します。

0
Akash Sharma