Как я могу улучшить эту ленту новостей PHP/MySQL?

позвольте мне начать с самого начала, сказав, что я знаю, что это не лучшее решение. Я знаю, что это kludgy и Хак функции. но именно поэтому я здесь!

этот вопрос/работа строится обсуждение на Quora с Эндрю Босворт, создатель ленты новостей Facebook.

я создаю ленту новостей сортов. Он построен исключительно в PHP и MySQL.

alt text


Для MySQL

реляционная модель канала состоит из двух таблиц. Одна таблица функционирует как журнал действий; фактически, она называется activity_log. Другая таблица -newsfeed. эти таблицы почти идентичны.

на схема для журнала is activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)

...и схема подачи is newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP).

любой время пользователь делает что-то отношение к ленте новостей, например, задавая вопрос, он войдет в журнал активности немедленно.


создание ленты новостей

затем каждые X минут (5 минут на данный момент, изменится на 15-30 минут позже), я запускаю работу cron который выполняет сценарий ниже. Этот скрипт перебирает всех пользователей в базе данных, находит все действия для все друзья этого пользователя, а затем записывает эти действия в ленту новостей.

в данный момент SQL это отбраковывает активность (вызывается ActivityLog::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'];

}

улучшение новости кормить

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

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

еще одна вещь, которая меня беспокоит в этой модели, заключается в том, что она работает на основе недавности, а не релевантности. Если кто-то может предложить, как это можно улучшить, чтобы работать в релевантности, я бы все уши. Я использую API Directed Edge для генерации рекомендаций, но кажется, что для чего-то вроде ленты новостей рекомендатели не будут работать (поскольку ранее ничего не было!).

5 ответов


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

вот недостатки, которые я вижу в своем уме с вашей текущей реализацией:

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

  2. Если один из моих друзей публикует что-то, это не будет отображаться в моей ленте новостей в течение не более 5 минут. Тогда как он должен появиться немедленно, верно?

  3. мы читаем всю ленту новостей для пользователя. Разве нам не нужно просто захватить новые действия с тех пор, как мы в последний раз хрустели бревнами?

  4. Это не так хорошо масштабируется.

лента новостей выглядит так же, как и журнал активности, я бы придерживался этого журнала активности таблица.

Если вы shard журналы активности по базам данных, это позволит вам масштабировать проще. Вы также можете отбросить своих пользователей, если хотите, но даже если у вас есть 10 миллионов пользовательских записей в одной таблице, mysql должен быть в порядке. Поэтому всякий раз, когда вы ищете пользователя, вы знаете, какой осколок для доступа к журналам пользователя. Если вы архивируете свои старые журналы так часто и поддерживаете только новый набор журналов, вам не придется так много осколков. Или, может быть, даже на всех. Вы можете управлять многими миллионы записей в MySQL, если вы настроены даже умеренно хорошо.

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

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

вы видели эту статью?

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


вы бы добавили статистический keywording? Я сделал (грубую) реализацию, взорвав тело моего документа, очистив HTML, удалив общие слова и подсчитав наиболее распространенные слова. Я сделал это несколько лет назад просто для удовольствия (как и в любом таком проекте, источник ушел), но он работал для моей временной настройки блога/форума. Возможно, это сработает для вашей ленты новостей...


между вами можно использовать пользовательские флаги и кэширование. Скажем, есть новое поле для пользователя как last_activity. Обновляйте это поле всякий раз, когда пользователь вводит какое-либо действие. Держите флаг, до тех пор, пока вы не извлекли каналы, скажем, feed_updated_on.

теперь обновите функцию $user - >getAllUsers (); чтобы вернуть только пользователей, которые имеют время last_activity позже feed_updated_on. Это исключит всех пользователей, у которых нет журнала действий :). Аналогичный процесс для пользователей друзья.

вы также можете использовать кэширование, как memcache или кэширование на уровне файлов.

или используйте некоторую NoSQL DB для хранения всех каналов как одного документа.


Я пытаюсь создать ленту новостей в стиле Facebook самостоятельно. Вместо того, чтобы создавать другую таблицу для регистрации действий пользователей, я рассчитал "край" из объединения сообщений, комментариев и т. д.

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

при отображении ленты каждое ребро умножается с помощью RAND (). Сообщения с более высоким краем будут появляться чаще

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


вместо запуска задания cron, какой-то скрипт после фиксации. Я не знаю конкретно, каковы возможности PHP и MySQL в этом отношении - если я правильно помню, MySQL InnoDB позволяет более продвинутые функции, чем другие разновидности, но я не помню, есть ли такие вещи, как триггеры в последней версии.

в любом случае, простое разнообразие, которое не полагается на много магии базы данных:

когда пользователь X добавляет содержание:

1) сделать асинхронный вызов с вашей PHP-страницы после фиксации базы данных (асинхронно, конечно, чтобы пользователю, просматривающему страницу, не пришлось ее ждать!)

вызов запускает экземпляр логического скрипта.

2) логика скрипт только через список друзей [A,B,C] пользователя, совершившего новый контент (в отличие от списка всех в БД!) и добавляет действие пользователя X к лентам для каждого из этих пользователей.

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

Это просто основная идея для каналов, основанных на недавности, а не релевантности. Вы можете хранить данные последовательно таким образом, а затем выполнять дополнительный синтаксический анализ на основе каждого пользователя для фильтрации по релевантности, но это сложная проблема в любом приложении и, вероятно, не может быть легко адресовано анонимным веб-пользователем без подробного знания ваших требований;)

jsh по