Медленное обновление против медленного выбора

StackOverflow https://stackoverflow.com/questions/2054570

  •  20-09-2019
  •  | 
  •  

Вопрос

Это вопрос о компромиссах.

Представьте себе социальную сеть.У каждого пользователя есть сообщение о состоянии, которое он может изменить в любое время.Всякий раз, когда он меняет его, все его друзья уведомляются через стену (например, в Facebook).

Чтобы это сработало.У нас есть 3 таблицы: Users(id, name), FriendLists(userId, friendsUserId), Notifications(?).

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


1-й вариант

CREATE TABLE Notifications
(
toUserId bigint NOT NULL,
[identity] bigint IDENTITY(1,1) NOT NULL,
fromUserId bigint NOT NULL,
data varchar(256) NOT NULL,
CONSTRAINT [PK_Notifications] PRIMARY KEY CLUSTERED (toUserId, [identity])
)

Отправлять уведомления:

-- Get all friends of @fromUserId.
WITH Friends AS
   (SELECT FriendLists.friendUserId
 FROM FriendLists
 WHERE userId = @fromUserId)
-- Send updates to all friends.
SELECT
 friendUserId as toUserId,
 @fromUserId as fromUserId,
 @data as data
INTO Notifications
FROM Friends

В этом случае для каждого изменения статуса мы создаём 50 записей (при условии 50 друзей).Это плохо.Однако хорошим моментом является то, что получение уведомлений для конкретного пользователя происходит очень быстро, поскольку у нас есть кластерный индекс для toUserId.

2-й вариант

CREATE TABLE Notifications
(
toUserId bigint NOT NULL,
[identity] bigint IDENTITY(1,1) NOT NULL,
fromUserId bigint NOT NULL,
data varchar(256) NOT NULL,
CONSTRAINT [PK_Notifications] PRIMARY KEY CLUSTERED ([identity])
)
CREATE NONCLUSTERED INDEX [IX_toUserId] ON Notifications (toUserId ASC)

Отправлять уведомления:

-- Get all friends of @fromUserId.
WITH Friends AS
   (SELECT FriendLists.friendUserId
 FROM FriendLists
 WHERE userId = @fromUserId)
-- Send updates to all friends.
INSERT INTO Notifications(toUserId, fromUserId, data)
    VALUES(friendUserId, @fromUserId, @data)

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


Получение уведомлений одинаково для обоих методов:

SELECT TOP(50) fromUserId, [identity], data
FROM Notifications
WHERE toUserId  = @toUserId

Итак, что вы думаете по этому поводу?

Это было полезно?

Решение

Во-первых, операции чтения всегда будут более трудоемкими по сравнению с записью, поскольку каждая «стена» будет просматриваться гораздо больше раз, чем обновляться.Так что вам лучше читать чертовски быстро.

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

Поэтому я бы определенно выбрал структуру, оптимизированную для чтения.

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

Другие советы

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

В этой ситуации создание кластерного индекса для (toUser,identity) кажется плохой идеей, потому что кластеризованный индекс действительно должен быть вставлен в порядке возрастания.Конечно, SQL позаботится о сохранении сортировки таблицы, но это связано с высокими затратами производительности (это и есть суть вашего вопроса). Но в целом вставки, о которых заранее известно, что они не находятся в определенном порядке, не рекомендуются для кластеризованные индексы.Вот очень хороший три часть статья о рекомендациях по кластерным индексам.

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

Что касается медленных обновлений, обновления статуса на сайтах социальных сетей являются идеальной ситуацией для очередей сообщений.Таким образом, вы можете настроить базу данных по мере необходимости, чтобы ускорить чтение, и если это повлияет на производительность записи, пользователю не придется пострадать.С их точки зрения, обновление было мгновенным, даже несмотря на то, что «прилипание» могло занять несколько минут.

Для очень больших баз данных я предоставлю слово гуру SQL, которые могут рассказать о стратегиях секционирования (меньшие, более управляемые таблицы для новых данных, большие/тяжело индексированные таблицы для старых данных) и решениях репликации.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top