Проектирование схемы HBase для лучшей поддержки конкретных запросов

У меня есть вопрос, связанный с конструкцией схемы HBase. Проблема довольно проста: я храню "уведомления" в hbase, каждый из которых имеет статус ( "новый", "замеченный" и "прочитанный" ). Вот API, который мне нужно предоставить:

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

У меня есть несколько идей, и я хотел посмотреть, является ли один из них явно лучшим, или если я полностью упустил хорошую стратегию. Общий для всех трех, я думаю, что одна строка за уведомление и наличие идентификатора пользователя в rowkey - это путь. Чтобы получить хронологическое упорядочение для разбивки на страницы, мне также нужно иметь обратную временную метку. Я хотел бы сохранить все примечания в одной таблице (поэтому мне не нужно объединять сортировку для вызова "получить все уведомления для пользователя" ) и не хотите писать пакетные задания для вторичных индексных таблиц (так как обновления до счет и статус должны быть в режиме реального времени).

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

Следующая возможность состоит в том, чтобы (2) закодировать статус в строке row, так что либо "userId_reverseTimestamp_status", либо затем выполняйте фильтрацию регулярных выражений rowkey при сканировании. Первая проблема, которую я вижу, - это необходимость удалить строку и скопировать данные уведомления в новую строку при изменении статуса (что, предположительно, должно происходить ровно дважды за уведомление). Кроме того, поскольку статус является последней частью строки, для каждого пользователя мы будем сканировать множество дополнительных строк. Это большой успех? Наконец, чтобы изменить статус, мне нужно знать, каков был предыдущий статус (для создания ключа строки), иначе мне нужно будет выполнить другое сканирование.

Последняя идея, которую я имел, состоит в том, чтобы (3) иметь два семейства столбцов: один для статических notif-данных и один как флаг состояния, то есть "s: read" или "s: new" с 's' как cf и статус как определитель. В каждой строке будет ровно одна строка, и я могу сделать MultipleColumnPrefixFilter или SkipFilter w/ColumnPrefixFilter против этого cf. Здесь также мне нужно будет удалить и создать столбцы при изменении статуса, но это должно быть намного легче, чем копировать целые строки. Моя единственная проблема - предупреждение в книге HBase о том, что HBase не преуспевает с "более чем двумя или тремя семействами столбцов" - возможно, если система нуждается в расширении с большим количеством запросов, стратегия multi-cf не будет масштабироваться.

Итак, (1) кажется, что у него слишком много сетевых издержек. (2), похоже, что это потратило бы потраченные расходы на копирование данных и (3) может вызвать проблемы со слишком большим количеством семейств. Между (2) и (3), какой тип фильтра должен обеспечивать лучшую производительность? В обоих случаях сканирование будет смотреть на каждую строку для пользователя, которая, по-видимому, имеет в основном уведомления о чтении, которые будут иметь лучшую производительность. Я думаю, что я склоняюсь к (3) - есть ли другие варианты (или хитрости), которые я пропустил?

Ответ 1

Вы задумались над этим, и я думаю, что все трое разумны!

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

Я думаю, что суть вашей проблемы - это изменение статуса. В общем, что-то вроде "read" → "delete" → "rewrite" вводит всевозможные проблемы concurrency. Что произойдет, если ваша задача не удалась? У вас есть данные в недопустимом состоянии? Будете ли вы записывать запись?

Я предлагаю вам вместо этого рассматривать таблицу как "append only". В принципе, сделайте то, что вы предлагаете для # 3, но вместо удаления флага сохраните его там. Если что-то было прочитано, оно может иметь три "s: seen", "s: read" там (если оно новое, мы можем просто предположить, что оно пустое). Вы также можете быть фантазией и поставить временную метку в каждом из трех, чтобы показать, когда это событие было удовлетворено. Вы не должны видеть большую часть производительности от этого, а затем вам не нужно беспокоиться о concurrency, поскольку все операции являются только для записи и атомарными.

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

Ответ 2

Мое решение:

Не сохранять статус уведомлений (видимый, новый) в hbase для каждого уведомления. Для уведомлений используется простая схема. Ключ: userid_timestamp - column: notification_message.

Как только клиент запросит API "Получить все новые уведомления", сохраните временную метку (все новые оповещения нажаты). Ключ: userid - colimn: All_new_notifications_pushed_time

Каждое уведомление с отметкой времени меньше, чем "Все новые оповещения, нажатые" предполагаются "видимыми", а если больше - "Новый"

Чтобы получить все новые уведомления: сначала получить значение (временная метка) для All_new_notifications_pushed_time byid затем выполните проверку диапазона в столбце notification_message по ключу: от current_timestamp до All_new_notifications_pushed_time.

Это значительно ограничит затронутые столбцы, и большинство из них должно быть в memstore.

Подсчитать новые уведомления на клиенте.