Построение индексов
Redis
Петр Трофимов, onliner.by
Товар в каталоге
Скомпилировать seo-заголовок товара
Seo, keywords, description
Посчитать обсуждения на форуме
Узнать рейтинг и кол-во отзывов
Скомпилировать краткую строку
Узнать диапазон цен магазинов
Проверить б/у предложения
Загрузить конфигурации
Загрузить галерею, видео, 3d-изображение
Получить группы параметров, сами параметры и их значения для товара
Как быстро отображать эту
информацию?
Покрыть кэшем
Нагрузка на сервер все равно остается
Пользователь видит неактуальную информацию
Держать кэш все время поднятым
Огромные интервалы между индексациями
Процессор все время занят
Event-based денормализация
Redis
Каталог B2B Баралог Корзина
Сервис
денормализации
DB
...
API
Булев параметр
NONE
YES
NO
Булев параметр
SET
1
2
5
SADD mobile:memory_cards:true 6
memory_cards=true
SMEMBERS mobile:memory_cards
1
5
2
6
Iphone6 (id = 6)
Собственно, содержание
Индексы и объекты
Индексация и поиск по параметрам
Булевы и flag-параметры
Словари и их диапазоны
Числа и их диапазоны
Карты отображения
Сочетание параметров
Сортировка
Числа и строки
Составная
Группировка
Как написать код
Индексы и объекты
Индексы
Объект
Объект
Объект
Поиск по
параметрам
Поиск по ID
Ссылки
Redis
Flag-параметры
ALL
YES
SADD mobile:onsale 6
onsale=true
SMEMBERS mobile:onsale 1
5
2
6
Iphone6 (id = 6)
SDIFF mobile:all mobile:onsale
Словари
SET
1
2
5
SADD mobile:mfr:apple 6
manufacturer=apple
SMEMBERS mobile:mfr:apple
1
5
2
6
Iphone6 (id = 6)
SINTERSTORE tmp mobile:mfr:apple mobile:mfr:samsung
SUNIONSTORE tmp mobile:mfr:apple mobile:mfr:samsung
SET
Словарный диапазон
SET
1
2
5
1
5
2
6
SUNIONSTORE tmp mobile:size:2 mobile:size:3 ...
Числа
ZADD mobile:price 600 6
Price = 600
5
6
Iphone6 (id = 6)
ZRANGEBYSCORE mobile:price 500 1000
3 => 300
4 => 400
5 => 500
ZRANGEBYSCORE + SADD = ZRANGEBYSCORESTORE (lua)
ZSET
Числовые диапазоны
-10 +20
Диапазон рабочих температур
start end
Overlap = start
Overlap = end
Overlap = complete
Overlap = inside
Overlap = partial
Карты отображения
Index Objects
1
2
100000
HASH
ID => KEY
6 => {catalog:product:iphone6}
Сортировка по числу
ZADD mobile:price 600 6
Price = 600
3
4
Iphone6 (id = 6)
ZRANGE mobile:price 1 2
ZCARD mobile:price
3 => 300
4 => 400
5 => 500
4
ZSET
Составная сортировка
3 => 300
4 => 400
5 => 500
3 => 0
4 => 0
5 => 1
price
is_new
ZUNIONSTORE tmp price is_new
WEIGHTS 1 1000
3 => 300
4 => 400
5 => 1500
composite
Сортировка по строке
Product:3 => Apple Iphone 3
Product:6 => Apple Iphone 6
3
4
5
SET
STRING KEYS
SORT set BY
Product:* ALPHA
STORE tmp
3
6
LIST
LRANGE list 1 5
Группировка
Object_id => parent_id
ZSET
ZINTER tmp set map
WEIGHTS 0 1
parent_id[]
Процесс индексации
1.трансформация объекта для индексации
2.построение цепочки index builder-ов
3.вычисление diff
4.выполнение index builder-ов
5.сохранение состояния индекса
6.коммит транзакции
7.событие - оповещение об изменении
индекса
Трансформация объекта для
индексации
{
"id": 598247,
"name": "Apple iPhone 6 (16Gb)",
"status": "active",
"rating": 3949,
"reviews_rating": 40,
"date": 1410278953,
"is_actual": true,
"has_no_parent": true,
"parent_id": null,
"manufacturer": "apple",
"key": "iphone6_16gb"
"parameters": {
"bool": {
"accelerometer": { "true": true },
...
},
"number": {
"acc_capacity": 181021,
"birthday": 1998,
...
},
"dictionary": {
"acc_type": { "nimh": true },
...
}
}
}
Построение цепочки index builder-
ов
$this->addBuilder(new IdIndex('id'))
->addBuilder(new IdMapIndex('key'))
->addBuilder(new StringOrderIndex('name'))
->addBuilder(new ArrayIndex('manufacturer'))
->addBuilder(new BoolIndex('is_actual'))
->addBuilder(new NumberIndex('rating'))
->addBuilder(new NumberIndex('date'))
->addBuilder(new NumberIndex('reviews_rating'))
->addBuilder(new BoolIndex('parameters.bool.*'))
->addBuilder(new BoolIndex('parameters.dictionary.*'))
->addBuilder(new NumberIndex('parameters.number.*'))
->addBuilder(new RangeIndex('parameters.range.*'))
->addBuilder(new ArrayIndex('parent_id'))
->addBuilder(new NumberIndex('group'));
Diff-механизм
Name: Apple Iphone 6
Price: 100000
Parameters:
Bool
Wifi: true
Number
Height: 100
Name: Apple Iphone 6
Price: 100000
parameters.bool.Wifi: true
parameters.number.Height: 100
Name: Apple Iphone 6
Price: 200000
parameters.bool.Wifi: true
Price: 200000
Parameters.number.height: 100
UPDATE
DELETE
Интерфейс index builder-а
/**
* This interface should be implemented by various Redis index builders
*/
interface BuilderInterface
{
/**
* Returns true if this key should be indexed by this builder
*/
public function matches($key);
/**
* Executes Redis command to update index
*/
public function update($redis, array $product, $key, $value);
/**
* Executes Redis command to delete index
*/
public function delete($redis, array $product, $key, $value);
}
Пример index builder-а
class BoolIndex extends Builder
{
public function matches($key)
{
return str_is($this->filter, $key);
}
public function update($redis, array $product, $key, $value)
{
if ($value) {
$redis->sadd($key, $product['id']);
}
}
public function delete($redis, array $product, $key, $value)
{
if ($value) {
$redis->srem($key, $product['id']);
}
}
}
Коммит транзакции
multi/pipeline
redis однопоточный
использование redis array
{catalog:product:1}:title
{catalog:product:1}:name
Методы обновления индекса
patchIndex
частичное обновление индекса
new = array_merge(previous, new)
putIndex
полная перезапись индекса
deleteIndex
удаление объекта из индекса
new = []
Процесс поиска
1.Создание цепочки Query на основе
аргументов
2.Выполнение подзапросов в рамках
IntersectionQuery в pipeline
3.Сортировка с пагинацией
4.Группировка (опционально)
5.Получение объектов по ID
6.Удаление временных ключей
Интерфейс Query
interface QueryInterface
{
/**
* Executes query to search products
*
* @param $redis
* @return string Redis key
*/
public function query($redis);
}
Пример Query
class ManufacturerQuery extends AbstractQuery implements QueryInterface
{
public function query($redis)
{
if (!is_array($this->value)) {
return $this->keys->indexKey("manufacturer:$this->value");
}
$unionKey = $this->keys->createKey('manufacturer');
$redis->sUnionStore(
$unionKey,
...$this->keys->mapIndexKeys("manufacturer:%s", $this->value)
);
return $unionKey;
}
}
Вопросы?
https://catalog.onliner.by
https://redis.io/documentation

Построение индексов Redis

Editor's Notes

  • #2 Key-value, memcache, catalog, show, lots info How many 17 gb - catalog, 700 k product, 700k categories Пример - каталог, с чего вдруг стали использовать редис. Показать на шапке товара
  • #6 Да нет фильтры, фильтрует список товаров, храним да и нет, отсутствие не храним
  • #7 Пример товара, готовая структура в редисе, пример добавления, пример поиска, готовый ключ, а чего вы не сделаете каталог на редис?. Пагинация, сортировка - позже
  • #10 Отличие от буль, есть множество всех, вычитание
  • #11 Теже флаги, отличия в ключе, плюс юнионы/диффы
  • #13 От до равно, inf для открытых,
  • #14 Отдельно для start/end, два множества
  • #15 Числовой айди, строковый ключ, хэш, схема ключа
  • #16 временный ключ при необходимости выполнение zinter сортированное/несортированное множество выполнение zrange/zrevrange для пагинации вычисление start/stop на основе page/limit выполнение zcard total found
  • #17 zunionstore weights
  • #18 индексация строк для сортировки используются строковые ключи (string) set/del приводится к нижнему регистру strtolower в имени ключа - ID объекта Method name? Hash? Product:titles : 123 -> apple iphone нельзя использовать zset, zrangebylex выполнение sort критерий сортировки - строковые ключи создание временного списка опция alpha = true опция store получение total count выполнение lrange пагинация
  • #19 индексация для группировки используются сортированные множества (zset) содержат карту отображения для группировки score - parent_id value - object_id Query after this ключ для группировки показывает связи между объектами выполняется обычный поиск попадают и обычные объекты, и представители групп выполняется zinter множество ID объектов сортированное множество базовый объект => дочерний объект weights 0, 1 выполняется zrange, array_unique, sadd из результата поиска отбираются сначала только базовые модели сортировка и пагинация затем - дочерние объекты формируется дерево объектов
  • #23 получаем текущее состояние индекса - hget приводим к плоскому виду текущее и новое состояние индекса - array_dot вызываем builder-ы для удаления и добавления - array_diff_assoc какие индексы существуют для объекта удалить объект из индексов при изменении характеристик быстрая переиндексация
  • #31 Sphinx vs redis slide after it