Доставка элементов фида активности умеренно масштабируемым способом - PullRequest
16 голосов
/ 07 февраля 2010

В приложении, над которым я работаю, есть лента активности, где каждый пользователь может видеть активность своих друзей (так же, как Facebook). Я ищу умеренно масштабируемый способ показа потока активности данного пользователя на лету. Я говорю «умеренно», потому что я пытаюсь сделать это только с базой данных (Postgresql) и возможно memcached. Например, я хочу, чтобы это решение масштабировалось до 200 тысяч пользователей, каждый из которых имеет 100 друзей.

В настоящее время существует основная таблица действий, в которой хранится визуализированный html для данного действия (Джим добавил друга, Джордж установил приложение и т. Д.). В этой основной таблице действий содержатся исходный пользователь, html и отметка времени.

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

Итак, если у меня есть 100 друзей, и я выполняю 3 действия, тогда таблица объединения увеличится до 300 единиц.

Очевидно, что эта таблица будет расти очень быстро. Однако у него есть приятное свойство: выборочная операция, отображаемая для пользователя, требует одного (относительно) недорогого запроса.

Другой вариант - просто сохранить основную таблицу активности и запросить ее, сказав что-то вроде:

select * from activity where source_user in (1, 2, 44, 2423, ... my friend list)

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

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

Большое спасибо!

1 Ответ

12 голосов
/ 22 февраля 2010

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

  1. Вы можете создать несколько таблиц действий и выполнить UNION ALL при извлечении данных из базы данных. Например, пролонгируйте их ежемесячно - активность_2010_02 и т. Д. Просто по вашему примеру - 200 000 пользователей x 100 друзей x 3 действия = 60 миллионов строк. Это не касается производительности PostgreSQL, но вы можете рассмотреть это исключительно для удобства сейчас и, в конечном итоге, для легкого расширения в будущем.

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

Собираетесь ли вы отображать весь фид активности, возвращаясь к началу времен? Вы не предоставили много деталей в исходном вопросе, но я рискну предположить, что вы будете показывать последние 10/20/100 пунктов, отсортированных по отметке времени. Пары индексов и предложения LIMIT должно быть достаточно для мгновенного ответа (как я только что проверил на таблице с примерно 20 миллионами строк). Это может быть медленнее на занятом сервере, но это то, что должно быть решено с аппаратными решениями и решениями для кэширования, Postgres не станет там узким местом.

Даже если вы предоставляете каналы активности, восходящие к незапамятным временам, paginate вывод! Предложение LIMIT спасет вас там. Если базового запроса с LIMIT на нем недостаточно, или если у ваших пользователей есть длинный хвост друзей, которые больше не активны, вы можете рассмотреть возможность ограничения поиска последним днем ​​/ неделей / месяцем first и затем укажите список идентификаторов друзей:

select * from activity 
  where ts <= 123456789 
    and source_user in (1, 2, 44, 2423, ... my friend list)

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

Это просто, если я выберу между двумя решениями, которые вы сейчас рассматриваете. Я бы тоже посмотрел на такие вещи, как:

  1. Пересмотр вашей денормализации таблицы. Хранение предварительно сгенерированного вывода HTML действительно лучший способ? Будете ли вы лучше ориентироваться на производительность, если вместо этого будете искать таблицу действий и генерировать шаблонный результат на лету? Предварительно сгенерированный HTML может показаться лучше с самого начала, но такие вещи, как дисковое хранилище, API, будущие изменения макета и хранение HTML, могут быть не такими привлекательными. Таблица поиска может содержать ваши возможные действия - добавление друга, изменение статуса и т. Д., А журнал активности будет ссылаться на это и на идентификатор друга, если в активности участвует другой пользователь.

  2. Выполнение предварительной генерации HTML, но не сохранение его в базе данных. Сохраните материал на диске как предварительно сгенерированные страницы. Это не серебряная пуля, однако, и в значительной степени зависит от соотношения записи и чтения на вашем сайте. То есть типичная ветка обсуждения на публичном форуме может содержать десяток сообщений, но ее можно просматривать сотни раз - хороший кандидат на кеширование. Принимая во внимание, что если ваше приложение более настроено на немедленное обновление статуса, и вам придется заново генерировать HTML-страницу и сохранять ее снова на диске после каждой пары просмотров, тогда этот подход не имеет большого значения.

Надеюсь, это поможет.

...