- История разработки фасетного поиска средствами PHP
- Агрегаты
- Возникновение задачи
- Устройство инфраструктуры
- Решение
- История оптимизаций
- Фасетный поиск на php
- Фильтрация данных с помощью zend-filter
- Контекстное экранирование с помощью zend-escaper
- Подключение Zend модулей к Expressive
- Совет: отправка информации в Google Analytics через API
- Подборка PHP песочниц
- Совет: активация отображения всех ошибок в PHP
История разработки фасетного поиска средствами PHP
Как экспериментальный Pet Project дошел до production и на что способны современные версии языка PHP. Немного о проблематике фасетного поиска в части построения агрегатов.
Если ваша первая реакция: «Почему не на Sphinx/ElasticSearch/etc?», не торопитесь с выводами. Воспринимайте изложенное как интересный исследовательский опыт в области возможностей языка и его оптимизаций.
Спойлер: пришлось даже написать порт на GoLang, чтобы лучше понять пути оптимизации кода.
Агрегаты
- простой сценарий — ничего особо не меняется, у варианта цвета проставляется «галочка». Это не очень удобно, пользователь видит лишние варианты фильтрации, выбор которых не приводит к изменению результатов;
- распространенный случай — первый вариант, дополненный информацией о количестве товаров, доступных при выборе этого значения фильтра. Обновится число доступных товаров в фильтрах.
- более редкий, но самый удобный вариант — недоступные фильтры и их значения будут отключены/спрятаны, обновятся данные о количестве товаров за выбранным значением фильтра.
На этих примерах видны различные варианты реализации.
Скрытие лишних значений проще реализовать на ElasticSearch. Часто в нем используют сквозной индекс по всем товарам, но в этом случае не известен полный стартовый набор фильтров для категории. В ElasticSearch можно использовать отдельные индексы под каждую категорию, иногда такой подход более эффективен. Так же на ElasticSearch проще работать с динамическими наборами свойств.
Отключение и счетчик товаров проще реализовать на Bitmap или Sphinx. Сложнее обстоят дела, когда наборы свойств заранее не известны.
При должном усердии любое поведение можно реализовать и на Sphinx и на Elastic.
Кстати, в SuperJob фасетный поиск организован на Sphinx, еще ребята делали собственные патчи в код самого Sphinx, но это тема отдельной статьи или даже доклада.
Именно этот функционал был реализован в библиотеке на PHP. Он прост в использовании и достаточно быстр, что может конкурировать с «серьезными решениями». Учитывает особенности фильтрации агрегатов, позволяет реализовывать отключение/скрытие лишних опций фильтров. И главное, скрывает сложность всего за парой методов.
Возникновение задачи
На тот момент я работал в компании ПартКом (оптовая продажа автозапчастей), которая на первый взгляд, совершенно не про эксперименты IT и инновации. Большой Enterprise с 80 млн. SKU номенклатуры, логистикой, складами, доставкой и прочими прелестями жизни.
Большая часть проектов в web запускалась как внутренние мини-стартапы со всеми вытекающими. С одной стороны, минимум ресурсов и времени, с другой — свобода выбора инструментов и подходов. Это позволяло очень быстро запускать MVP и экспериментальные сервисы. Запуск нового функционала мог занимать от одного дня до пары недель.
Внезапно образовался свободный промежуток времени и ресурсы, что позволило взяться за идею запуска MVP каталога автоаксессуаров (щетки, лампочки, коврики, масло и тп), все то, что удобно покупать на основе характеристик.
Устройство инфраструктуры
Сервис обработки прайс-листов обрабатывал огромные объемы данных и загружал их в БД, в минуту заливались десятки миллионов строк прайс-листов.
Описания товаров и их характеристики заполнялись в отдельном сервисе специально обученными людьми.
Из общего объема номенклатуры нас интересовало порядка 300 тыс наименований товаров, которые были в наличии на складах. Эти товары были разбросаны по 100 категориям. В каждой категории дерева каталога могли находиться несколько сущностей «Продукт».
«Продукт» определял набор допустимых атрибутов и их значений для определенного вида товаров (например: щетки могут иметь тип/размер/применяемость). У каждого продукта был четко определенный и структурированный набор атрибутов. Продукты в одной категории являлись родственными, часть их атрибутов пересекалась. В атрибутах часто встречались длинные перечисления, а также они могли содержать сразу несколько значений из доступного списка (MultiValue).
Товары привязывались к «продуктам» по номенклатуре и так же редактировались в каталоге. Товар содержал фактические значение атибутов, можно представить как объект одного из классов «продукт».
Сервис каталога умел возвращать данные о категориях, товарах и продуктах по JSON API, при этом он не держал существенную нагрузку, был исключительно внутренним информационным сервисом.
Решение
- SplFixedArray — не использовался, так как при индексировании не известен итоговый размер массива, данные индекса нужно прозрачно экспортировать и импортировать, внутри обработки активно используются функции для работы с массивами, которые не поддерживают SplFixedArray.
- Php-ds/etc. — не использовалось, по тем же причинам что и SplFixedArray, дополнительная зависимость для интерпретатора в production.
История оптимизаций
При построении агрегатов требовалось находить много пересечений массивов и тут обнаружился один интересный нюанс.
Представим, что есть два массива — $dataA и $dataB, значения они могут хранить по нашему усмотрению в ключах или в значениях.
Если элемент содержит значения в ключах, назову его $keysA или $keysB.
Значений в массивах очень много, но мы точно знаем, что в первом их меньше чем во втором.
Нам нужно посчитать количество пересекающихся элементов.
Вариант 1
Вариант 2
Вариант 3
$count = 0;
foreach ($dataA as $v) if (isset($keysB[$v])) $count++;
>
>
Для тестирования этого примера прогнал каждый вариант по 10 итераций в цикле:
Фасетный поиск на php
В этом разделе помещены уроки по PHP скриптам, которые Вы сможете использовать на своих ресурсах.
Фильтрация данных с помощью zend-filter
Когда речь идёт о безопасности веб-сайта, то фраза «фильтруйте всё, экранируйте всё» всегда будет актуальна. Сегодня поговорим о фильтрации данных.
Контекстное экранирование с помощью zend-escaper
Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.
Подключение Zend модулей к Expressive
Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.
Совет: отправка информации в Google Analytics через API
Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.
Подборка PHP песочниц
Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.
Совет: активация отображения всех ошибок в PHP
При поднятии PHP проекта на новом рабочем окружении могут возникнуть ошибки отображение которых изначально скрыто базовыми настройками. Это можно исправить, прописав несколько команд.