Разработка схемы HBase для лучшей поддержки конкретных запросов - PullRequest
10 голосов
/ 24 января 2012

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

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

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

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

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

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

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

Ответы [ 2 ]

2 голосов
/ 25 января 2012

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

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

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

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

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

1 голос
/ 31 января 2012

Мое решение:

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

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

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

Чтобы получить все новые уведомления:сначала получите значение (отметка времени) для All_new_notifications_push_time по идентификатору пользователя, затем выполните сканирование диапазона в столбце messages_message по ключу: от current_timestamp до All_new_notifications_pressed_time.

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

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

...