Wie kann ich diesen PHP / MySQL-Newsfeed verbessern?

74

Lassen Sie mich gleich sagen, dass ich weiß, dass dies nicht die beste Lösung ist. Ich weiß, es ist klobig und ein Hack eines Features. Aber deshalb bin ich hier!

Diese Frage / Arbeit baut auf einer Diskussion über Quora mit Andrew Bosworth auf , dem Schöpfer des Facebook-Newsfeeds.

Ich baue eine Art Newsfeed. Es ist nur in PHPund gebaut MySQL.

Alt-Text


Das MySQL

Das relationale Modell für den Feed besteht aus zwei Tabellen. Eine Tabelle fungiert als Aktivitätsprotokoll. in der Tat ist es benannt activity_log. Die andere Tabelle ist newsfeed. Diese Tabellen sind nahezu identisch.

Das Schema für das Protokoll lautetactivity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)

... und das Schema für den Feed lautet newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP).

Jedes Mal, wenn ein Benutzer etwas für den Newsfeed relevantes tut , beispielsweise eine Frage stellt, wird er sofort im Aktivitätsprotokoll protokolliert .


Generieren der Newsfeeds

Dann führe ich alle X Minuten (im Moment 5 Minuten, später 15 bis 30 Minuten) einen Cron-Job aus , der das folgende Skript ausführt. Dieses Skript durchläuft alle Benutzer in der Datenbank, findet alle Aktivitäten für alle Freunde dieses Benutzers und schreibt diese Aktivitäten in den Newsfeed.

Im Moment hat das SQL, was die Aktivität auswählt (aufgerufen ActivityLog::getUsersActivity()) LIMIT 100, aus Leistungsgründen * auferlegt. * Nicht, dass ich wüsste, wovon ich spreche.

<?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']);
      }
    }
  }
}

Anzeigen der Newsfeeds

Im Client-Code mache ich beim Abrufen des Newsfeeds des Benutzers Folgendes:

$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'];

}

Verbesserung des Newsfeeds

Verzeihen Sie jetzt mein begrenztes Verständnis der Best Practices für die Entwicklung eines Newsfeeds, aber ich verstehe, dass der Ansatz, den ich verwende, eine eingeschränkte Version des sogenannten Fan-Out beim Schreiben ist , begrenzt in dem Sinne, dass ich einen Cron-Job ausführe als Zwischenschritt, anstatt direkt in die Newsfeeds der Benutzer zu schreiben. Dies unterscheidet sich jedoch stark von einem Pull-Modell in dem Sinne, dass der Newsfeed des Benutzers nicht beim Laden, sondern regelmäßig kompiliert wird.

Dies ist eine große Frage, die wahrscheinlich viel Hin und Her verdient, aber ich denke, sie kann als Prüfstein für viele wichtige Gespräche dienen, die neue Entwickler wie ich führen müssen. Ich versuche nur herauszufinden, was ich falsch mache, wie ich mich verbessern kann oder wie ich vielleicht sogar von vorne anfangen und einen anderen Ansatz ausprobieren sollte.

Eine andere Sache, die mich an diesem Modell stört, ist, dass es eher auf Aktualität als auf Relevanz basiert. Wenn jemand vorschlagen kann, wie dies verbessert werden kann, um die Relevanz zu verbessern, wäre ich ganz Ohr. Ich verwende die API von Directed Edge zum Generieren von Empfehlungen, aber es scheint, dass für so etwas wie einen Newsfeed Empfehlungen nicht funktionieren (da bisher nichts bevorzugt wurde!).

Josh Smith
quelle
Was? Diesmal kein humorvolles Bild? Du kannst es besser machen als das! : P
Alex
1
@ Josh Versuchen Sie, diese :)
alex
1
@Josh Smith hat jeder Benutzer eine Newsfeed-Tabelle?
Chromedude
1
@josh smith Wenn Sie Ihren obigen Algorithmus neu erstellen, veröffentlichen Sie ihn bitte. Danke
namal
1
@JoshSmith, führen Sie eine SQL-Abfrage für jeden Freund aus, um dessen Aktivitäten zu erfassen?
John Smith

Antworten:

15

Wirklich coole Frage. Ich bin gerade dabei, so etwas selbst umzusetzen. Also werde ich ein bisschen laut nachdenken.

Hier sind die Fehler, die ich bei Ihrer aktuellen Implementierung sehe:

  1. Sie verarbeiten alle Freunde für alle Benutzer, aber Sie werden am Ende viele Male dieselben Benutzer verarbeiten, da dieselben Personengruppen ähnliche Freunde haben.

  2. Wenn einer meiner Freunde etwas veröffentlicht, wird es höchstens 5 Minuten lang nicht in meinem Newsfeed angezeigt. Während es sofort auftauchen sollte, oder?

  3. Wir lesen den gesamten Newsfeed für einen Benutzer. Müssen wir nicht einfach die neuen Aktivitäten abrufen, seit wir das letzte Mal die Protokolle geknackt haben?

  4. Das lässt sich nicht so gut skalieren.

Der Newsfeed sieht genauso aus wie das Aktivitätsprotokoll. Ich würde mich an diese eine Aktivitätsprotokolltabelle halten.

Wenn Sie Ihre Aktivitätsprotokolle datenbankübergreifend teilen, können Sie die Skalierung vereinfachen. Sie können Ihre Benutzer auch sharden, wenn Sie dies wünschen, aber selbst wenn Sie 10 Millionen Benutzerdatensätze in einer Tabelle haben, sollte MySQL in Ordnung sein, wenn Sie lesen. Wenn Sie also einen Benutzer suchen, wissen Sie, von welchem ​​Shard aus Sie auf die Protokolle des Benutzers zugreifen können. Wenn Sie Ihre älteren Protokolle von Zeit zu Zeit archivieren und nur einen neuen Satz von Protokollen verwalten, müssen Sie nicht so viel sharden. Oder vielleicht sogar überhaupt. Sie können viele Millionen Datensätze in MySQL verwalten, wenn Sie auch nur mäßig gut eingestellt sind.

Ich würde memcached für Ihre Benutzertabelle und möglicherweise sogar die Protokolle selbst nutzen. Memcached ermöglicht Cache-Einträge mit einer Größe von bis zu 1 MB. Wenn Sie Ihre Schlüssel geschickt organisieren, können Sie möglicherweise alle neuesten Protokolle aus dem Cache abrufen.

Dies wäre mehr Arbeit in Bezug auf die Architektur, aber es ermöglicht Ihnen, in Echtzeit zu arbeiten und in Zukunft zu skalieren ... insbesondere, wenn Benutzer mit dem Kommentieren jedes Beitrags beginnen sollen. ;)

Hast du diesen Artikel gesehen?

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

Dan Spiteri
quelle
1

zwischen können Sie Benutzerflags und Caching verwenden. Nehmen wir an, Sie haben ein neues Feld für den Benutzer als last_activity. Aktualisieren Sie dieses Feld, wenn der Benutzer eine Aktivität eingibt. Behalten Sie ein Flag bei, bis zu welchem ​​Zeitpunkt Sie die Feeds abgerufen haben. Sagen wir, es ist feed_updated_on.

Aktualisieren Sie nun die Funktion $ user-> getAllUsers (); um nur Benutzer zurückzugeben, deren letzte Aktivitätszeit später als feed_updated_on liegt. Dies schließt alle Benutzer aus, die kein Aktivitätsprotokoll haben :). Ähnliches Verfahren für die Benutzerfreunde.

Sie können auch Caching wie Memcache oder Caching auf Dateiebene verwenden.

Oder verwenden Sie eine nosql-Datenbank, um alle Feeds als ein Dokument zu speichern.

Akash Sharma
quelle
1

Ich versuche, selbst einen Newsfeed im Facebook-Stil zu erstellen. Anstatt eine weitere Tabelle zum Protokollieren der Benutzeraktivitäten zu erstellen, habe ich aus der UNION den "Rand" von Posts, Kommentaren usw. berechnet.

Mit ein bisschen Mathematik berechne ich die 'Kante' mithilfe eines exponentiellen Abklingmodells, wobei die verstrichene Zeit die unabhängige Variable ist, wobei die Anzahl der Kommentare, Likes usw. berücksichtigt wird. Jeder Beitrag muss die Lambda-Konstante formulieren. Die Kante nimmt zunächst schnell ab, flacht jedoch nach einigen Tagen allmählich auf fast 0 ab (erreicht jedoch nie 0).

Beim Anzeigen des Vorschubs wird jede Kante mit RAND () multipliziert. Beiträge mit höherem Rand werden häufiger angezeigt

Auf diese Weise haben populärere Posts eine höhere Wahrscheinlichkeit, länger im Newsfeed zu erscheinen.

Freeman L.
quelle
4
Sie haben nicht erwähnt, ob der Edge vorberechnet oder zur Laufzeit berechnet wurde?
Meson10
1

Anstatt einen Cron-Job auszuführen, ein Post-Commit-Skript. Ich weiß nicht genau, welche Funktionen PHP und MySQL in dieser Hinsicht haben - wenn ich mich richtig erinnere, erlaubt MySQL InnoDB erweiterte Funktionen als andere Varianten, aber ich erinnere mich nicht, ob es in der neuesten Version Dinge wie Trigger gibt.

Jedenfalls eine einfache Variante, die nicht auf viel Datenbankmagie beruht:

Wenn Benutzer X Inhalte hinzufügt:

1) Führen Sie nach dem Festschreiben der Datenbank einen asynchronen Aufruf von Ihrer PHP-Seite aus (natürlich asynchron, damit der Benutzer, der die Seite anzeigt, nicht darauf warten muss!)

Der Aufruf startet eine Instanz Ihres logischen Skripts.

2) Das Logikskript durchläuft nur die Liste der Freunde [A, B, C] des Benutzers, der den neuen Inhalt festgeschrieben hat (im Gegensatz zur Liste aller Mitglieder in der Datenbank!) Und hängt die Aktion von Benutzer X an die Feeds für jeden an dieser Benutzer.

Sie können diese Feeds einfach als direkte JSON-Dateien speichern und am Ende jeweils neue Daten anhängen. Besser ist es natürlich, die Feeds mit einem Backup auf das Dateisystem oder BerkeleyDB oder Mongo oder was auch immer Sie möchten im Cache zu halten.

Dies ist nur eine Grundidee für Feeds, die auf Aktualität und nicht auf Relevanz basieren. Sie KÖNNTEN die Daten nacheinander auf diese Weise speichern und dann eine zusätzliche Analyse auf Benutzerbasis durchführen, um nach Relevanz zu filtern. Dies ist jedoch ein schwieriges Problem in jeder Anwendung und wahrscheinlich kein Problem, das von einem anonymen Webbenutzer ohne detaillierte Angaben leicht behoben werden kann Kenntnis Ihrer Anforderungen;)

jsh

jsh
quelle
0

Würden Sie statistische Schlüsselwörter hinzufügen? Ich habe eine (grobe) Implementierung durchgeführt, indem ich den Hauptteil meines Dokuments aufgelöst, HTML entfernt, gebräuchliche Wörter entfernt und die gebräuchlichsten Wörter gezählt habe. Ich habe das vor ein paar Jahren nur zum Spaß gemacht (wie bei jedem solchen Projekt ist die Quelle weg), aber es hat für mein temporäres Test-Blog / Forum-Setup funktioniert. Vielleicht funktioniert es für Ihren Newsfeed ...

Mixer
quelle
3D Dies ist mit einer FULLTEXTSuchmaschine wie Sphinx einfacher zu implementieren , was ein weiterer möglicher Ansatz ist. Die Sorge, die ich mit so etwas oder dem von @stillstanding vorgeschlagenen Ansatz habe, ist, dass es sich wie ein Hack über einem Hack anfühlt. Was ich wirklich tun möchte, um die Relevanz zu bestimmen, ist die Berechnung der summierten Affinitätsbewertung des Benutzers mit dem Ersteller des Inhalts, dem Gewicht für den Inhaltstyp und einem Zeitverfallsfaktor. Aber ich bin mir noch nicht sicher, wie ich das anstellen soll ...
Josh Smith
Bis zu welcher Komplexität lassen Sie dies wachsen? Das scheint eine ziemlich kräftige Gewichtsverteilung zu sein, ist aber machbar. Sie müssten der Relevanz mit dem Alter einen logarithmischen Zerfall hinzufügen, aber es ist ziemlich vage, den 'Inhaltstyp' zu erhalten. Sie müssten eine Reihe von Schlüsselwörtern einrichten, mit denen sie übereinstimmen, um dies festzustellen (als schnelle Lösung. Dies wäre in einer groß angelegten Anwendung nicht ideal). Dies erfordert einige intensive Statistiken und Computerlesefähigkeiten ...
Blender
Es wird wahrscheinlich ziemlich komplex sein; Denken Sie an den Newsfeed von Facebook. Aber dies ist etwas, das wahrscheinlich ein größeres Umdenken erfordert, als ich es hier habe.
Josh Smith