Дизайн таблицы DynamoDB для уведомлений

#database-design #amazon-dynamodb #dynamodb-queries

#database-design #amazon-dynamodb #dynamodb-запросы

Вопрос:

Используемые технологии: AWS, Lambda, DynamoDB, Python.

Я не очень разбираюсь в DynamoDB / NoSQL, и мой случай следующий:

  1. Мне нужно хранить сообщения, отправленные пользователям, каждый пользователь (идентифицируемый user_id) может иметь несколько сообщений (идентифицируемых message_id)
  2. Мне нужно отправить пользователю уведомление обо всех его / ее сообщениях, хранящихся в таблице
  3. Уведомления должны отправляться в указанное время на основе пользовательских настроек
  4. Пользователь может установить несколько раз уведомлений — это не ограничено, поэтому один пользователь может захотеть получать уведомления один раз в день, например, в полдень, а другой пользователь может захотеть получать уведомления, например, 4 раза в день (например. 7.15, 11:00, 15:00 и 18:00), здесь предпочтительна полная гибкость

Каждые пару минут будет запускаться лямбда-выражение для получения сообщений, о которых мне нужно уведомить пользователей. Lambda «знает, который час» и хочет получать только сообщения пользователей, которые хотят получать свои уведомления в данный момент времени, исходя из их предпочтений.

Текущий дизайн таблицы DynamodDB следующий: таблица user_messages — Первичный ключ (ключ раздела: user_id, Ключ сортировки: message_id), атрибуты: message_text, creation_time и т. Д.

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

Я не уверен, как подойти к этому в случае многократного уведомления каждого пользователя, теперь у меня есть 2 возможных сценария:

1. ограничьте время настройки уведомлений до N, например, максимум 3 на пользователя, сохраните настройки в 3 атрибутах и создайте 3 GSI, в таком случае лямбда-выражение будет запрашивать таблицу 3 раза при каждом запуске — это выглядит не элегантно, и я обеспокоен жестким ограничением количества уведомлений

в таком случае дизайн таблицы будет выглядеть следующим образом: таблица user_messages — Первичный ключ (ключ раздела: user_id, Ключ сортировки: message_id), атрибуты: message_text, creation_time и т. Д., GSI_1 (notfication_time_1), GSI_2 (notification_time_2), GSI_3 (notification_time_3)

2. создайте отдельную таблицу с пользовательскими настройками, такими как Ключ раздела: notification_time, атрибут: user_id

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

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

Видите ли вы какой-либо другой подход, который я мог бы использовать здесь?

Ответ №1:

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

Обновление: Есть два решения, мне трудно определиться, но я бы, вероятно, выбрал # 2

Я бы, наверное, выбрал такой дизайн одного стола, как этот:

ПК SK GSI1PK GSI1SK Тип атрибуты
U #1 NC #1 NCT #08:30 U #1NC #1 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 08:30}
U #1 NC # 2 NCT #17:30 U #1NC #1 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 17:30}
U #1 Сообщение # 2021-02-27…#ИДЕНТИФИКАТОР #123 Сообщение {message_id: 123, create_time: 2021-02-27T09:30:00Z, тело: bla
U #1 Сообщение # 2021-02-27…#ID#789 Сообщение {message_id: 789, create_time: 2021-02-27T10:30:00Z, тело: blub
U #2 NC #1 NCT #10:15 U #1NC #1 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 10:15}
U #1 Сообщение # 2021-02-27…#ID #654 Сообщение {message_id: 654, create_time: 2021-02-27T10:30:00Z, тело: тест

PK — это ключ раздела, SK — ключ сортировки, GSI1PK и GSISK — это ключи раздела и сортировки глобального вторичного индекса GSI1.

Теперь ваша лямбда-функция должна выполнить следующие шаги:

  1. Получите список пользователей, которых необходимо уведомить прямо сейчас: Query @ GS1; GSIPK=NCT#<time>
  2. Для каждого пользователя в результате от 1)
  3. Запрос основного индекса с PK=U#<user-id> and SK start_with MSG
  4. Отправляйте сообщения
  5. Удалите сообщения из таблицы

Таким образом, вы можете выполнить KEYS_ONLY проекцию для GSI1, что экономит затраты на хранение и RCU.

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

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


Альтернативный дизайн

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

ПК SK Тип атрибуты
U #1 NC #1 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 08:30}
U #1 NC # 2 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 17:30}
SM #17:30 U#1#ИДЕНТИФИКАТОР #123 SCHEDULED_MESSAGE {message_id: 123, create_time: 2021-02-27T09:30:00Z, тело: bla
SM #17:30 U#1#ИДЕНТИФИКАТОР #789 SCHEDULED_MESSAGE {message_id: 789, create_time: 2021-02-27T10:30:00Z, тело: blub
U #2 NC #1 NOTIFICATION_CONFIGURATION {time_of_day_in_utc: 10:15}
SM#10:15 U#2#ИДЕНТИФИКАТОР #654 SCHEDULED_MESSAGE {message_id: 654, create_time: 2021-02-27T10:30:00Z, тело: тест

Когда вы добавляете новое сообщение, вы выполняете следующие действия:

  1. Выполните запрос PK=U#<id>, SK starts_with NC , чтобы получить все конфигурации уведомлений
  2. Выберите конфигурацию уведомления, наиболее близкую к текущему времени (т.е. к моменту отправки следующего уведомления).
  3. Создайте запланированное сообщение, как показано в таблице, с GSI1PK, являющимся результатом из 2)

Лямбда-код, который должен отправлять сообщения, теперь может это делать:

  1. Выполните запрос с PK=SM#<time> , чтобы получить все сообщения, которые необходимо отправить сейчас
  2. Для каждого сообщения
    1. Отправьте сообщение пользователю
    2. Удалите сообщение из таблицы

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

Комментарии:

1. Добавлен альтернативный дизайн.

2. Спасибо, оба подхода заслуживают рассмотрения, нужно подумать об этом. Мне придется повторно отправлять уведомления до тех пор, пока пользователь сохраняет сообщения, но у него будет TTL, так что у вас будет максимальное время для сохранения сообщения. Итак, в варианте 2 мне нужно будет создать несколько копий сообщения и внести соответствующие изменения, если пользователь изменит свои временные предпочтения. Если я сохраняю несколько копий для каждого сообщения, я мог бы также пометить каждую копию с указанием времени в качестве ключа раздела GSI (без user_id) и извлечь все необходимое в текущее время за один раз. Что вы думаете об этом варианте? Единственный недостаток, который я вижу, — это объем памяти.

3. Отправка уведомления несколько раз кажется странной, но если вам это нужно — я бы выбрал дизайн 1, он может легче приспособиться к этому. Я не понимаю, чего достигнет дополнительный GSI в решении. Вы уже можете получать все уведомления за определенный момент времени с помощью одного запроса (возможно, разбивка на страницы)

4. @Maurice Для дизайна 1, при использовании запроса типа: Query @ GS1; GSIPK=NCT#<time> — как определить <время>, когда лямбда-функция может запускаться каждые 5 минут?

5. @I’ll-Be-Back В своих примерах я просто использовал время в форме HH:MM , если вы допускаете 5-минутные интервалы, вы можете легко взять текущее время в лямбда-выражении и округлить его в большую или меньшую сторону до ближайшего 5-минутного интервала и выполнить запрос.