Я бы реализовал это на GAE, вот так:
У каждого пользователя будет таблица с твитами подписчиков. Для этой таблицы будет использоваться ключ (пользователь, отметка времени по убыванию).
У каждого пользователя также есть таблица follower_ranges, которая отображает пользователя в набор смежных диапазонов идентификаторов подписчиков. Для большинства пользователей, у которых всего несколько тысяч подписчиков, в этой таблице будет одна запись (-inf .. + inf); это будет подразумеваемое значение по умолчанию. Для пользователей с большим количеством подписчиков каждый диапазон в таблице будет иметь несколько тысяч пользователей. Диапазоны будут сбалансированы с течением времени, чтобы количество пользователей в каждом из них оставалось в пределах некоторого интервала, например больше 1000, меньше 10000. Объединение всех диапазонов будет включать все идентификаторы пользователей.
Всякий раз, когда создается пользователь -> операция подписчика, она кодируется как действие и добавляется в очередь. Каждый элемент в очереди - это кортеж (отправитель, действие, полезная нагрузка, подчиненный поддиапазон). Работники очереди берут предмет, находят всех последователей в данном поддиапазоне и применяют действие к каждому из них. (Обратите внимание, что действие может быть «добавить твит», «удалить твит», «редактировать твит» и т. Д. В основном все, что необходимо применить ко всем подписчикам.)
Применение действия очереди к каждому подписчику будет связано с выполнением соответствующих записей и удалений в таблицу твитов каждого пользователя. Барьер в очереди будет означать, что запись не будет появляться мгновенно, но должна быть возможность удерживать задержку ниже нескольких секунд.
Показывать пользователю их твиты будет дешевой операцией: «ВЫБЕРИТЕ * ОТ ТИТЕРОВ, ГДЕ user_id =: user_id ORDER BY (made_at DESC) LIMIT: max_per_page». Это отсканирует одну таблицу и будет очень быстрой операцией. (Снизить задержку блокировки пользователя - это хорошо!)
Я думаю, что этот дизайн изначально неплохо масштабировался бы. Каждый компонент системы теперь можно легко масштабировать:
- Хранилище очереди может быть поддержано GAE и масштабировано согласно любой таблице хранилища данных
- Внешние интерфейсы можно масштабировать естественным образом, и нет необходимости в липкости
- В любое время можно добавить больше процессоров очереди
- Фактические таблицы хранения будут расти естественным образом и должны масштабироваться в хранилище данных.
Тем не менее, я могу вспомнить пару будущих улучшений, которые я хотел бы рассмотреть сразу:
- Сокращение хранения редко показанных данных. Этот дизайн денормализует каждый твит в копию для каждого подписчика. Однако обычно доступны только самые последние твиты. Удалив пользовательскую копию твитов по истечении N дней, мы сможем восстановить большой объем памяти. Если пользователь пытается просмотреть что-то из древней истории, мы получаем данные из денормализованных таблиц. Это будет медленнее, но не будет происходить слишком часто, и экономия будет значительной. Экономия памяти: (#avg_followers - 1) / # avg_followers
- Шаблон записи неоптимален. Через несколько элементов очереди каждый работник очереди будет писать в таблицу твитов каждого пользователя, таким образом, местоположение записей будет не очень хорошим. (В худшем случае у нас будет #processor * #storage серверных подключений.) Это можно исправить, применив несколько обновлений для каждого диапазона пользователей. Например, если два действия A и B должны применяться к диапазону [0, 10000), то один обработчик очереди может применить эти два действия одновременно.