Индексирование с использованием отсортированных наборов Redis
Я хотел бы получить отзывы и предложения относительно двух подходов, которые я рассматриваю для реализации поисковых индексов с использованием отсортированных наборов Redis.
Ситуация и цель
В настоящее время у нас есть несколько таблиц ключ-значение, которые мы храним в Cassandra, и для которых мы хотели бы иметь индексы. Например, одна таблица будет содержать записи о людях, а таблица Кассандры будет иметь идентификатор в качестве своего первичного ключа и сериализованный объект в качестве значения. Объект будет иметь такие поля, как first_name, last_name, last_updated и другие.
Мы хотим, чтобы у нас была возможность выполнять поиск, такой как "last_name =" Smith "AND first_name >" Joel "", "last_name <'Aaronson" "," last_name = "Smith" AND first_name = "Winston" "и т. Д., Поиск должен дать идентификаторы совпадений, чтобы мы могли затем извлечь объекты из Кассандры. Я думаю, что вышеупомянутые поиски могут быть сделаны с одним индексом, отсортированным лексикографически по last_name, first_name и last_updated. Если нам нужны некоторые поиски с использованием другого порядка (например, "first_name = 'Zeus'"), у нас может быть подобный индекс, который позволил бы их (например, first_name, last_updated).
Мы смотрим на использование Redis для этого, потому что мы должны иметь возможность обрабатывать большое количество записей в минуту. Я ознакомился с некоторыми распространенными способами использования отсортированных наборов Redis и предложил две возможные реализации:
Вариант 1: один отсортированный набор для каждого индекса
Для нашего индекса по last_name, first_name, last_updated мы должны иметь отсортированный набор в Redis по ключевым индексам:people:last_name:first_name:last_updated, который будет содержать строки в формате last_name:first_name:last_updated:id . Например:
кузнец: Joel:1372761839.444: 0azbjZRHTQ6U8enBw6BJBw
(Для разделителя я мог бы использовать '::' вместо ':' или что-то еще, чтобы лучше работать с лексикографическим порядком, но давайте пока проигнорируем это)
Все элементы будут иметь оценку 0, так что отсортированный набор будет отсортирован лексикографически самими строками. Если я затем захочу сделать запрос вроде "last_name = 'smith' И first_name <'bob'", мне нужно будет получить все элементы в списке, которые идут до "smith: bob".
Насколько я могу судить, у этого подхода есть следующие недостатки:
- Нет функции Redis для выбора диапазона на основе значения строки. Эта функция, называемая ZRANGEBYLEX, была предложена Сальваторе Санфилиппо по адресу https://github.com/antirez/redis/issues/324, но она не реализована, поэтому мне пришлось бы находить конечные точки с помощью бинарного поиска и самостоятельно получать диапазон (возможно, с использованием Lua или на уровне приложений с Python, который является языком, который мы используем для доступа к Redis).
- Если мы хотим включить время жизни для записей индекса, кажется, что самый простой способ сделать это - иметь регулярно запланированную задачу, которая проходит через весь индекс и удаляет элементы с истекшим сроком действия.
Вариант 2: небольшие отсортированные наборы, отсортированные по last_updated
Этот подход будет аналогичным, за исключением того, что у нас будет много меньших отсортированных наборов, каждый из которых будет иметь временное значение, например last_updated для баллов. Например, для одного и того же last_name, first_name, last_updated index у нас будет отсортированный набор для каждой комбинации last_name, first_name. Например, ключом могут быть indexes:people:last_name=smith:first_name=joel, и в нем будет запись для каждого человека, которого мы назвали Джоэл Смит. Каждая запись будет иметь в качестве имени идентификатор, а в качестве значения - значение last_updated. Например:
значение: 0azbjZRHTQ6U8enBw6BJBw; оценка:1372761839,444
Основными преимуществами этого являются (а) поиск, когда мы знаем, что все поля, кроме last_updated, будут очень простыми, и (б) реализация времени жизни будет очень легкой, используя ZREMRANGEBYSCORE.
Недостаток, который мне кажется очень большим:
- Кажется, что управление и поиск таким способом намного сложнее. Например, нам нужно, чтобы индекс отслеживал все его ключи (например, в какой-то момент мы хотим очистить) и делал это иерархически. Такой поиск, как "last_name <'smith'", потребует сначала просмотреть список всех фамилий, чтобы найти те, которые стоят перед кузнецом, затем для каждого из тех, кто ищет все содержащиеся в нем имена, затем для каждого из них. получить все предметы из своего отсортированного набора. Другими словами, много компонентов для создания и беспокойства.
Завершение
Так что, мне кажется, первый вариант был бы лучше, несмотря на его недостатки. Я был бы очень признателен за любые отзывы об этих двух или других возможных решениях (даже если они о том, что мы должны использовать что-то кроме Redis).
3 ответа
Я настоятельно не рекомендую использовать Redis для этого. Вы будете хранить тонну дополнительных данных указателя, и если вы когда-нибудь решите, что хотите выполнять более сложные запросы, такие как,
SELECT WHERE first_name LIKE 'jon%'
вы столкнетесь с неприятностями. Вам также нужно будет создать дополнительные очень большие индексы, которые пересекают несколько столбцов, на случай, если вы захотите найти два поля одновременно. По сути, вам нужно будет взломать и реинжиниринг поисковой структуры. Вам было бы намного лучше использовать Elastic Search, Solr или любую другую платформу, уже созданную для того, что вы пытаетесь сделать. Redis потрясающий и имеет много хороших применений. Это не один из них.Если оставить в стороне предупреждение, чтобы ответить на ваш актуальный вопрос: я думаю, что вам лучше всего использовать вариант вашего первого решения. Используйте один отсортированный набор для каждого индекса, но просто конвертируйте ваши буквы в цифры. Преобразуйте ваши буквы в некоторое десятичное значение. Вы можете использовать значение ASCII или просто присвоить каждой букве значение 1-26 в лексикографическом порядке, предполагая, что вы используете английский язык. Стандартизируйте, чтобы каждая буква занимала одинаковую числовую длину (поэтому, если 26 - ваше наибольшее число, 1 будет написано "01"). Затем просто добавьте их вместе с десятичной точкой впереди и используйте их в качестве показателя для каждого индекса (т. Е. "Шляпа" будет ".080120"). Это позволит вам правильно упорядочить отображение 1: 1 между словами и этими числами. При поиске преобразуйте буквы в цифры, и тогда вы сможете использовать все функции отсортированного набора Redis, такие как
ZRANGEBYSCORE
без необходимости переписывать их. Функции Redis написаны очень, очень оптимально, поэтому вам лучше использовать их, когда это возможно, вместо того, чтобы писать свои собственные.
Для этого вы можете использовать мой проект python-stdnet, он выполняет всю индексацию за вас. Например:
class Person(odm.StdModel):
first_name = odm.SymbolField()
last_name = odm.SymbolField()
last_update = odm.DateTimeField()
Как только модель зарегистрирована с бэкэндом Redis, вы можете сделать это:
qs = models.person.filter(first_name='john', last_name='smith')
так же как
qs = models.person.filter(first_name=('john','carl'), last_name=('smith','wood'))
и многое другое
Фильтрация быстрая, так как все идентификаторы уже в наборах.
Вы можете проверить redblade, он может автоматически поддерживать индекс, и он написан Node.JS.
//define schema
redblade.schema('article', {
"_id" : "id"
, "poster" : "index('user_article')"
, "keywords" : "keywords('articlekeys', return +new Date() / 60000 | 0)"
, "title" : ""
, "content" : ""
})
//insert an article
redblade.insert('article', {
_id : '1234567890'
, poster : 'airjd'
, keywords : '信息技术,JavaScript,NoSQL'
, title : '测试用的SLIDE 标题'
, content : '测试用的SLIDE 内容'
}, function(err) {
})
//select by index field or keywords
redblade.select('article', { poster:'airjd' }, function(err, articles) {
console.log(articles[0])
})
redblade.select('article', { keywords: 'NoSQL' }, function(err, articles) {
console.log(articles[0])
})