Гибридное хранилище данных MySQL / Denormalized для оптимизации производительности REST API - PullRequest
0 голосов
/ 15 мая 2018

У меня есть веб-приложение, построенное на PHP (Symfony 3) и MySQL (Doctrine). Все это работает хорошо, и теперь я хочу создать REST API, чтобы сделать некоторые части данных приложения общедоступными.

Для упрощения, скажем, у меня есть страница / products , а для каждого продукта - страница подробностей / product / {id} . На странице продуктов пользователи могут применять несколько фильтров к списку продуктов, например, какие категории они хотят. Большинство фильтров - это просто список флажков, которые пользователь может выбрать (без текстовых фильтров).

Таблица продуктов имеет много связей, хотя она не была чрезмерно нормализована; это присуще домену, с которым я работаю. Чтобы получить все данные для одной строки продукта, мне нужно сделать + - 20 объединений по 15 отдельным запросам. Да, я знаю, что это много, но большинство таблиц - это просто простые таблицы поиска, а общее время запроса занимает всего + - 3 мс. Фильтрация списка продуктов осуществляется с помощью построителя запросов SQL. Поскольку на странице продуктов отображается только список названий продуктов, производительность здесь не проблема.

Но вот в чем проблема: REST API должен будет сгенерировать список полных объектов продукта со всеми данными (не только именами). Как вы можете себе представить, фильтрация + все дополнительные объединения / запросы и GROUP BY не очень хороши для производительности. Чтобы решить эту проблему, я думал о создании какой-то гибридной системы, используя только SQL для записи обновлений в базу данных и сохраняя хранилище документов, предназначенное только для чтения, для извлечения продуктов из.

Самая простая реализация, о которой я могу подумать, это создать таблицу product_api_cache , в которой хранятся продукты, сгенерированные как JSON, готовые для отображения в API. Если пользователь запрашивает ресурс / api / products , построитель запросов применяет фильтры для возврата списка идентификаторов продуктов, которые затем можно использовать для получения JSON продуктов из product_api_cache таблица.

Более продвинутой реализацией было бы использование подходящего хранилища документов, такого как ElasticSearch или MongoDB. Я не уверен, как это будет соответствовать текущей системе фильтрации (построитель SQL-запросов). Значит ли это, что мне нужно продублировать всю логику фильтрации специально для ElasticSearch?

Кроме того, JSON, который должен возвращать API-интерфейс, не на 100% соответствует реальному объекту продукта (он значительно упрощается за счет его сериализации). Значит ли это, что мне нужно написать 2 отдельных слоя сериализации? Первый для хранения версии объекта JSON 1-на-1, чтобы ElasticSearch мог правильно запросить его, а затем второй для сериализации результата ElasticSearch в упрощенное для пользователя представление. Поскольку ElasticSearch возвращает JSON, означает ли это, что мне нужно десериализовать этот результат в объект продукта, чтобы затем снова сериализовать объект продукта?

Какой разумный способ реализовать это? Есть ли еще способы сделать это? Думаю ли я что-то не то?

Ответы [ 4 ]

0 голосов
/ 16 мая 2018

MySQL имеет собственную поддержку JSON . Фактически, он также работает как хранилище документов аналогично MongoDB.

0 голосов
/ 15 мая 2018

Я сосредоточусь на опции денормализованной таблицы. Если вы строите денормализованную таблицу для чтения данных, вы в основном реализуете CQRS (см. https://martinfowler.com/bliki/CQRS.html).. Я делал это несколько раз, «объект-обертка» имеет исходный объект в качестве одного из своих свойств, например ::

class ProductExtended {

    /** @var Product **/
    private $product;

    /** @var float **/
    private $originalPrice;

    /** @var float **/
    private $discountedPrice;

   ...

Вы запрашиваете вашу ProductExtended сущность как любую другую сущность, но в БД это одна таблица, поэтому производительность выше, а фильтрация проще, если вы добавляете в ProductExtended все доступные для поиска свойства.

Усилия направлены на то, чтобы поддерживать эту таблицу обновленной: вам необходимо добавить прослушиватели для любого изменения в Product и, возможно, также команду, которая перестроит все это, чтобы быть уверенным, что вы поймете любое «ручное» или не прослушиваемое изменение.

0 голосов
/ 15 мая 2018

Я сосредоточусь на опции ElasticSearch.Если вы используете ES, вам не нужно индексировать сущность «как есть».Вместо этого вы можете создать денормализованную версию и индексировать эту версию, чтобы вы могли использовать все расширенные опции фильтрации ES (и нет, вы не можете обойтись без традиционных QueryBuilder и DQL).Идентификатор объекта будет связующим звеном между объектом БД и данными ES.

Если у вас есть продвинутая логика фильтрации и / или много данных, это путь, который вы, возможно, захотите изучить.ES очень мощный и очень быстрый, и (при правильной интеграции с Doctrine) он будет возвращать ваши исходные объекты в качестве результатов, поэтому он прозрачен с точки зрения потребителя API.

0 голосов
/ 15 мая 2018

Я думаю, что самым простым и быстрым решением было бы хранение кэшированных версий объектов ответа API. Конечно, у вас будут дублированные данные. Согласно вашему случаю, вы можете решить, является ли это приемлемым или нет. Я не буду беспокоиться, если у вас нет / не будет размера базы данных более нескольких гигабайт. (Относительно) * +1001 *

Если вы используете упругий поиск, вы должны абстрагировать свою логику фильтрации, как вы и думали. Но вы также можете использовать стратегию таблиц mysql (id = data) в ES, но таким способом вы будете использовать ES только для извлечения данных.

И просто напоминание. Вам действительно нужна эта оптимизация? Вам это может не понадобиться. Это не производительность, это точно. Но зачем усложнять кодовую базу, если она вам не нужна?

...