Обновлено: 30 апреля 2026 г. в 07:40

Twitter/X

средний

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

Twitter/X усложняется не в момент записи одного твита, а там, где одну публикацию нужно быстро превратить в свежую ленту для миллионов подписчиков при огромном перекосе в сторону чтения.

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

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

Стратегия раздачи

Главный выбор здесь не в базе данных, а в том, когда раскладывать твит по готовым лентам и когда собирать ответ на чтении.

Кэш домашней ленты

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

Тренды в потоке

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

Популярные аккаунты

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

Введение

Проектирование Twitter/X упирается не в таблицу твитов, а в домашнюю ленту, которую нужно быстро обновлять для миллионов подписчиков при огромном перекосе в сторону чтения. Главная инженерная развилка здесь состоит в том, когда выгодна , а когда лучше .

Функциональные требования

  • Публикация твита — короткий текстовый пост, медиа и базовые взаимодействия.
  • Домашняя лента — персональная лента твитов от аккаунтов, на которые подписан пользователь.
  • Лента пользователя — все твиты конкретного автора в обратном хронологическом порядке.
  • Подписка и отписка — управление графом подписок между аккаунтами.
  • Лайки и ретвиты — базовые сигналы вовлечения.
  • Поиск — поиск по твитам, аккаунтам и ключевым словам.
  • Трендовые темы — быстрое определение всплесков обсуждения в реальном времени.
  • Уведомления — упоминания, ответы, лайки и другие события вокруг аккаунта.

Нефункциональные требования

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

  • Масштаб: около 500M зарегистрированных пользователей и 200M DAU.
  • Объём записи: порядка 500M новых твитов в сутки.
  • Профиль нагрузки: чтений на порядки больше, чем записей, примерно 1000:1.
  • Домашняя лента: ответ желательно удерживать в пределах 200 мс.
  • Допустимая задержка появления: 5-10 секунд для обновления у части подписчиков приемлемы.
  • Доступность: ориентир — 99.99%, при этом сервис должен переживать частичные сбои без массового отказа.
  • Проблема популярных аккаунтов: публикация не должна останавливаться, если у автора десятки или сотни миллионов подписчиков.

Оценка трафика: 200M DAU × 100 чтений ленты в день дают примерно 20B чтений в сутки, то есть около 230 тыс. на чтение. 500M твитов в сутки дают около 6 тыс. запросов в секунду на запись.

Главная задача: сборка ленты

Главная архитектурная проблема Twitter/X — как собирать домашнюю ленту быстро и дёшево, когда почти каждый твит нужно показать многим подписчикам. Базовых стратегий две: либо использовать и заранее раскладывать твит по готовым лентам, либо выбрать и собирать ленту по запросу из последних публикаций нужных аккаунтов.

Раздача при записи

После публикации твит сразу попадает в кэш домашних лент подписчиков.

  • Быстрое чтение: лента уже предсобрана.
  • Простая логика на пути чтения.
  • Дорогая публикация для очень популярных аккаунтов.
  • Высокая цена хранения из-за дублирования записей.
  • Часть работы тратится на неактивных подписчиков.

Раздача при чтении

Домашняя лента собирается по запросу: система берёт список подписок, читает последние твиты и сливает их в один ответ.

  • Публикация почти не зависит от числа подписчиков.
  • Меньше дублирования в хранилище.
  • Нет работы впустую для неактивных пользователей.
  • Чтение становится тяжелее и требует нескольких источников данных.
  • Слияние и фильтрация на лету усложняют путь ответа.

Гибридный подход

На практике Twitter/X использует гибридную схему: для обычных аккаунтов работает раздача при записи, а для очень популярных авторов — раздача при чтении. Такой режим снижает цену там, где публикация одного твита иначе взорвала бы систему по числу записей.

Гибридная схема ленты

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

Приём твита

API и загрузка медиа

Сервис твитов

сохранение твита и выбор стратегии

Обычные аккаунты: раздача при записи

Сервис раздачи

запись в кэш лент подписчиков

Кэш домашних лент

Redis ZSET для каждого пользователя

Популярные аккаунты: раздача при чтении

Хранилище популярных авторов

отдельный слой для больших аккаунтов

Сборка при чтении

слияние с готовой лентой

Сервис ленты

ранжирование, фильтрация и выдача

Как распознают популярные аккаунты

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

Архитектура кэша домашней ленты

Кэш домашней ленты нужен, чтобы самый частый путь чтения был дешёвым. Обычно публикация подтверждается быстро, а сама раскладка по кэшам уходит в , чтобы не держать пользователя на тяжёлой фоновой работе.

Структура ленты в Redis

# У каждого пользователя есть собственная домашняя лента в Redis
# Key: timeline:{user_id}
# Value: Sorted Set (score = timestamp, member = tweet_id)

ZADD timeline:12345 1705234567 "tweet_abc123"
ZADD timeline:12345 1705234890 "tweet_def456"

# Получение последних 100 твитов
ZREVRANGE timeline:12345 0 99

# Приближённая оценка объёма:
# 200M пользователей × 800 твитов × 8 байт = 1.28 TB
# С репликацией ×3 = около 4 TB Redis-кластера

Ограничения кэша

  • Обычно держат только последние 800 твитов на пользователя.
  • Старые записи удаляются по рангу.
  • Глубокая история дочитывается из основного хранилища.

Воркеры раздачи

  • Работают асинхронно и не блокируют подтверждение публикации.
  • Собирают обновления батчами, чтобы не делать лишние записи.
  • Имеют ретраи и защиту от повторной обработки.

Трендовые темы

Определение трендов — это уже : системе важно ловить всплеск обсуждения, а не просто абсолютное число твитов. Для компактного счёта хорошо подходит , а скорость роста считать через .

Контур трендов

Нажмите на этап, чтобы подсветить соответствующую часть контура.

Поток твитов

топик Kafka

Извлечение сущностей

хэштеги и сущности

Фильтрация

спам и стоп-слова

Скоринг трендов

окна и затухание

Кэш трендов

Redis sorted sets

Count-Min Sketch

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

Скользящее окно

Обычно система смотрит на последние 5-15 минут и постепенно понижает вес старых сообщений. Так легче отличить настоящий всплеск интереса от давно накопленного фона.

Формула скоринга трендов

# Упрощённая формула скоринга трендов Twitter

def calculate_trend_score(topic, current_window):
    # Текущая скорость: твиты в минуту за последние 5 минут
    current_count = count_in_window(topic, minutes=5)

    # Базовый уровень: среднее число твитов за 5 минут за последние 7 дней
    baseline_count = get_baseline(topic, days=7)

    # Во сколько раз тема растёт быстрее обычного
    if baseline_count > 0:
        velocity_ratio = current_count / baseline_count
    else:
        velocity_ratio = current_count * 10  # бонус для новой темы

    # Вес свежести: экспоненциальное затухание
    recency_weight = sum(
        tweet.weight * exp(-lambda * (now - tweet.timestamp))
        for tweet in current_window.tweets
    )

    # Буст от вовлечения
    engagement_score = (likes + retweets * 2 + replies * 3) / total_tweets

    # Финальный счёт
    score = velocity_ratio * recency_weight * (1 + log(engagement_score))

    # Фильтрация спама и ботов
    if unique_users_ratio < 0.3:  # слишком мало уникальных пользователей
        score *= 0.1  # сильный штраф

    return score

Ранжирование ленты

Современный Twitter/X опирается не только на хронологию, но и на . Модель пытается предсказать вероятность вовлечения и поднять в ленте те твиты, которые пользователь с большей вероятностью прочитает, лайкнет или откроет.

Признаки для ранжирования

Признаки твита
  • Возраст твита.
  • Наличие медиа.
  • Наличие ссылок.
  • Длина текста.
  • Текущий темп вовлечения.
  • Средняя вовлечённость автора.
Признаки пользователя
  • История взаимодействий с автором.
  • Тематические интересы.
  • Привычки по времени активности.
  • Близость в графе подписок.
  • Тип устройства.
  • Контекст текущей сессии.

Двухшаговое ранжирование

Шаг 1: быстрый отбор кандидатов. Система собирает примерно 1000 твитов из кэша домашней ленты, публикаций популярных аккаунтов и дополнительных блоков вроде «Вы могли это пропустить».

Шаг 2: финальное ранжирование. Модель оценивает каждого кандидата, после чего пользователю показывают верхнюю часть списка. Время вывода модели стараются удерживать в пределах десятков миллисекунд.

Архитектура поиска

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

Поисковый контур

Переключайте между путём индексации и путём пользовательского запроса.

Путь индексации

Приём документа

твит и токенизация

Оперативный индекс

шарды Lucene в памяти

Холодный индекс

дисковые сегменты

Путь запроса

Разбор запроса

термы и фильтры

Поиск по шардам

параллельный поиск

Слияние и ранжирование

свежесть и вовлечение

Задержка индексации ~10 с
P99 поиска < 200 мс

Модель данных

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

# Базовые таблицы

tweets {
    tweet_id: UUID (Snowflake ID)
    user_id: UUID
    content: VARCHAR(280)
    media_urls: JSON
    created_at: TIMESTAMP
    reply_to_tweet_id: UUID (nullable)
    retweet_of_id: UUID (nullable)
    quote_tweet_id: UUID (nullable)
    like_count: INT
    retweet_count: INT
    reply_count: INT
}

users {
    user_id: UUID
    username: VARCHAR(15)
    display_name: VARCHAR(50)
    bio: VARCHAR(160)
    follower_count: INT
    following_count: INT
    is_verified: BOOLEAN
    is_celebrity: BOOLEAN  # follower_count > 10K
    created_at: TIMESTAMP
}

follows {
    follower_id: UUID
    followee_id: UUID
    created_at: TIMESTAMP
    PRIMARY KEY (follower_id, followee_id)
}

# Денормализованные структуры для чтения
user_timelines (Redis Sorted Set)
    Key: timeline:{user_id}
    Score: tweet_timestamp
    Member: tweet_id

# Твиты популярных аккаунтов
celebrity_tweets (Redis Sorted Set)
    Key: celebrity:{user_id}
    Score: tweet_timestamp
    Member: tweet_id

Генерация Snowflake ID

Twitter разработал Snowflake ID, чтобы получать уникальные и примерно сортируемые по времени идентификаторы без центрального координатора.

# Структура Snowflake ID (64 бита)
┌────────────────────────────────────────────────────────────────┐
│ 1 bit │   41 bits timestamp   │ 10 bits │    12 bits          │
│unused │   (milliseconds)      │machine  │   sequence          │
│       │   since epoch         │   ID    │   number            │
└────────────────────────────────────────────────────────────────┘

# Свойства:
# - Примерно сортируется по времени
# - Не требует центральной координации
# - 4096 ID в миллисекунду на машину
# - Хватает на десятки лет до переполнения

# Пример ID: 1605978261000000000
# Timestamp: 2020-11-21 12:31:01 UTC
# Machine: 42
# Sequence: 0

def generate_snowflake_id(machine_id, last_timestamp, sequence):
    timestamp = current_time_ms() - TWITTER_EPOCH

    if timestamp == last_timestamp:
        sequence = (sequence + 1) & 0xFFF  # 12 bits
        if sequence == 0:
            timestamp = wait_next_millis(last_timestamp)
    else:
        sequence = 0

    id = (timestamp << 22) | (machine_id << 12) | sequence
    return id, timestamp, sequence

Высокоуровневая архитектура

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

Высокоуровневая архитектура

Выберите поток, чтобы подсветить ключевые компоненты.

Пограничный слой и маршрутизация

Клиенты

веб и мобильные приложения

CDN

статика и медиа

Балансировщик

маршрутизация трафика

API Gateway

авторизация и лимиты

Основные сервисы

Сервис твитов

приём и запись твитов

Сервис ленты

сборка домашней ленты

Сервис поиска

путь пользовательских запросов

Сервис трендов

потоковая обработка

Сервис пользователей

профили и граф подписок

Асинхронный слой и данные

Очередь сообщений

Kafka и асинхронная раздача

База твитов

основное хранилище

Кэш домашних лент

Redis ZSET

Поисковый индекс

Lucene и Elasticsearch

Кэш трендов

Redis sorted sets

Графовая база

граф подписок

Подсказки для интервью

Ключевые компромиссы

  • Раздача при записи против раздачи при чтении и почему Twitter выбирает гибрид.
  • Цена кэша против цены вычисления на лету.
  • Скорость выдачи против свежести обновления ленты.
  • Точность трендов против цены потоковой обработки.

Частые уточняющие вопросы

  • Что делать с аккаунтом, у которого 100M подписчиков?
  • Как масштабировать воркеры раздачи и не допускать очередей на часы?
  • Как отделять настоящие тренды от спама и ботов?
  • Как встраивать персонализацию вроде «Для вас» поверх базовой ленты?

Дополнительные ресурсы

Связанные главы

Чтобы отмечать прохождение, включи трекинг в Настройки