Как стать автором
Обновить

Комментарии 39

explain analize наверно не лучший вариант для теста, надо проверять генегируя таблицу и делая множество разных запросов.
Как точка отсчета explain очень даже хорош. Да, можно нагрузочные придумать и посмотреть на систему в динамике, а еще никадать не 10 параметров, а 100500 с неравномерным заполнением дабы было максимально близко с реальности (интернет-магазин всего и вся например).
Вот только есть одно НО: факт в том, что 99.9% проектов ни когда в жизни не достигнут таких объемов данных как в выборке у автора (смешные 10млн) и им куда важнее не скорость выолнения запроса, а скорость разработки этого самого проекта. И по классике жанра, все что тормозит — кладется в кэш и спят спокойно)
Если сущность имеют 2 атрибуты, то нужно уже 4 join-на!
Не нужно миллион джойнов, нужен один PIVOT (он в постгресе называется «crosstab» и работает вполне шустро).
И еще...
«join-на» — это пять! Почти как «2ва» и «3ри». Пишите хотя бы «join'а», если использовать слово «джойна» так уж не хочется.

Кроме того, все атрибуты обычно хранятся в виде строк, что приводит к приведению типов, как для результата, так и для условия WHERE.
Так заведите отдельные таблицы или столбцы под каждый тип.
Если вы пишете много запросов, то это достаточно расточительно, с точки зрения использования ресурсов.
EAV — это в принципе расточительно…
Не нужно миллион джойнов, нужен один PIVOT

Я всегда думал, что нужен один select, в котором в условии where будет id сущности. А джойнов надо всегда два, просто чтобы присоединить к таблице с сущностями таблицы с ключами и значениями. Хотя, честно говоря я не совсем понимаю, почему нельзя это всё сложить в одну таблицу.

Так єтот паттерн применяєтся при динамечских обьектах, где динамически создаются custom fields, типа как в каких-то CRM сервисах.
Для систем, написанного под одного конкретного клиента — конечно особого смьісла так делать нет, бьістрее и проще все в одной таблице по схеме "1 аттрибут обьекта — 1 колонка в таблице"

Может JSONB — это и круто, но это вообще никаким местом не полноценная замена EAV.
EAV, в отличие от JSON, это не полный schem-less, это sheme-as-data. Там можно иметь дополнительные метаданные у полей, проводить некоторые манипуляции со структурой Entity, не перетряхивая все значения и иметь где-то фиксированную доступную для просмотра структуру записей. Еще можно делать множественные привязки, но это штука уже довольно сомнительная.
-- JSONB
UPDATE entity_jsonb
SET properties = jsonb_set(properties, '{"color"}', '"blue"')
WHERE id = 120;

Надо иметь ввиду, что значение в колонке properties заменяется целиком. Теперь представим, что JSON-ы, которые мы храним, достаточно большие. И если типичная нагрузка — это частые обновления отдельно взятых полей JSON-а, то EAV может оказаться и на голову эффективнее.

В общем, в статье незаслуженно обойден вопрос о производительности записи и влиянии на WAL.
Надо иметь ввиду, что значение в колонке properties заменяется целиком. Теперь представим, что JSON-ы, которые мы храним, достаточно большие. И если типичная нагрузка — это частые обновления отдельно взятых полей JSON-а, то EAV может оказаться и на голову эффективнее.

Если мы говорим о PostgreSQL, тип jsonb там хранится в бинарном виде, не парсится каждый раз и позволяет без проблем и быстро выбирать/заменять любое свойство внутри json( www.postgresql.org/docs/9.4/datatype-json.html ) не заменяя его полностью.
Да, это быстро, но на физическом уровне создается новая версия строки а не перезаписывается существующая, т.е. мы получаем полную копию всего jsonb (с заменой одного поля), в то время, как в EAV — только копию строки, содержащей измененное значение. Как интенсивные обновления больших jsonb повлияют на размер WAL и TOAST-таблиц, на поведение VACUUM — вопрос, который я бы игнорировать поостерегся.

Хотя с основным посылом я согласен — jsonb во многих случаях выглядит предпочтительнее, чем EAV. Указываю только на то, что в общем случае нагрузка на БД не сводится к одним только select-ам.
А какой именно GIN индекс строился? В этой статье рассматриваются разные варианты. Например с параметром jsonb_path_ops. Не пробовали такие варианты?
Рассмотрим паттерн EAV (Entity-Attribute-Value), он встречается достаточно часто. Одна таблица содержит сущности (записи), другая таблица содержит имена свойств (атрибутов), а третья таблица связывает сущности с их атрибутами и содержит значение этих атрибутов для текущей сущности.

Небольшое замечание: это не структура данных EAV, а скорее реализация связи «многие-ко-многим». Для EAV достаточно одной таблицы с тремя столбцами: собственно сущность, атрибут сущности, значение атрибута. Может быть ещё справочник атрибутов, но не обязательно.
Не очень понятно зачем для EAV делать 2 таблицы помимо основной таблицы сущностей?

Я работал так:
1. таблица сущностей: entity
2. таблица атрибутов: attributes

у таблицы атрибутов поля:
— id
— entity_id
— key
— value

Все. Это работает. Храним любые атрибуты и их значения.

По любой сущности можно получить все атрибуты и значения 1 запросом с 1 join. Атрибутов моджет быть хоть 1000 штук. Доходило до 20 000 штук.

Хранить такое в JSONB — мб плачевно.

С другой сторону конечно же EAV это не серебрянная пуля. У нее есть как плюсы так и минусы.

Например чаще всего индекс есть только у ключей, а value хранятся без индекса. У этого тоже есть плюсы, но и весомый минус — при больших данных поиск значений в value становится долгим.

Есть много решений. Что то можно выдернуть в отдельные таблицы. Иногда подключается внешний индекс типа Эластика или Алголии. Например для создания поисковых индексов.

JSONB крутая штука. Где то она будет показывать себя лучше. Но не везде )

Если я не ошибаюсь, вынесения атрибутов в отдельную таблицу требует 3-я нормальная форма. И правильнее задаваться вопросом, что нам даст подобная денормализация.

Нет, 3НФ этого не требует. Вообще, ни одна из НФ не требует создавать отдельные суррогатные ключи для кодирования строк.

Нет, ну конечно, от части Вы правы. Все зависит от того, как эти атрибуты трактует бизнес логика. Если это просто строки, которые не несут особой смысловой нагрузки — тут согласен. Но если это, например, характеристики товара в магазине(как чаще всего бывает и именно этот кейс я имел в виду), по которым нужно организовывать фильтр, или если, не дай бог, от выбора характеристик зависит цена товара, то тут характеристика — это отдельное отношение и хранение ее в таком виде — очевидная денормализация.
Исправьте, если я неправ.
Проблема решена… до того момента, пока ваша таблица не содержит миллионы записей и у вас не возникнет необходимость добавить новую запись.

Я не понимаю, в чём проблема добавить новую запись к миллионам других. Или это опечатка, и автор имел в виду «добавить новую колонку»? Но здесь тоже нет никакой проблемы, потому что в современных движках БД строки не фиксированной длины.

Если записей миллионы — добавление колонки очень затратная операция в большинстве sql баз. Я бы даже скорее вынес новую колонку в отдельную таблицу 1х1 чем положил базу на пару часов добавлением новой колонки.

Если сделать колонку nullable, то никаких проблем не будет.

Спасибо, не знал этого. В Постгресе это так. Надо будет проверить как mysql будет реагировать.

Большой респект Постгресу. Mysql добавлял колонку как и ожидалось долго — 30с. для 1000000 строк

Немного узковатое понимание jsonb. Да, он неплохо подходит для хранения атрибутов, но это не та причина, по которой его создавали — до него в постгресе с этим успешно справлялся hstore. Собственно для хранения атрибутов kv хранилища как правило хватает за глаза.
jsonb же выделяется тем, что быстрее json — об этом написано в статье, а также тем, что это все тот же json, в котором можно хранить не просто атрибуты, а целые структуры с вложениями, kv и списками. Быстрый доступ к таким структурам и возможность работать с такими структурами без костылей и является киллер фичей jsonb в сравнении с hstore.
Но это все историческая ремарка, jsonb сейчас ничуть не медленнее hstore и имеет неплохие преимущества перед ним, потому вопрос о том, что использовать в новом продукте уже и не стоит. Буду рад услышать преимущества hstore, если не прав.

Единственное, чего хотелось бы — типизация на уровне бд, а не на логическом уровне, но это уже капризы)
НЛО прилетело и опубликовало эту надпись здесь

А если свойство может иметь несколько вариантов? {"Ram":[{"ddr3","sdram"}]}. Как проапдейтить или удалить определённое? Если на сотнях, тысячах записей?

В моём пет-проекте используется EAV. Наелся этого по самое нихочу.
Автор ничтоже сумняше не раскрыл один момент. Трёхтабличный EAV это не более чем пример практики, в реальности что-то адекватное можно построить не менее чем на 7. В моём проекте эта схема реализована на 9 и планирую расширить до 12 в ближайшей перспективе.
Благодаря этому я сохранил преимущества EAV плюс добился того что атрибуты хранятся в БД в своём типе, а не в строке. Недостатки остались конечно.
Вопрос производительности остаётся открытым и стоит остро. Метрики этого пока не собираю.

Статья от января 2016 года… У нас в проектах, тянущихся с тех времен, похожие решения используются. Но все-таки уже почти три года прошло с тех пор.


Заголовок спойлера

Хочется EAV — попробуй RDF-хранилище.
Тормозят JOIN'ы — используй графовую СУБД.
Любишь JSON — есть документные СУБД.

Не, ну… Я конечно за то чтобы подбирать инструменты под задачу, но в данном случае это зоопарк технологий непонятно для чего.

Хочется EAV — попробуй RDF-хранилище.

Зачем? Ну если только в пет-проекте попробовать…

Тормозят JOIN'ы — используй графовую СУБД.

Может лучше разобраться чего они тормозят?

Любишь JSON — есть документные СУБД.

Ну пусть дальше будут. Если основная СУБД у меня PostgreSQL я знаю что он вполне себе уже лет 10 как «Not Only SQL», и в текущем состоянии вряд-ли чем-то уступает какой-нибудь Монге( www.youtube.com/watch?v=SNzOZKvFZ68 )

Ну и да — Документо-ориентированные СУБД подразумевают немного другие подходы к декомпозиции в принципе, потому для стандартной модели с реляциями не подходят.
Не, ну… Я конечно за то чтобы подбирать инструменты под задачу, но в данном случае это зоопарк технологий непонятно для чего.

Да, здесь не под задачи, а под радости и боли автора статьи. Конечно, из таких соображений строить архитектуру хранения не стоит.


Что до зоопарка самого по себе…

«NoSQL Distilled» Фаулера и Садаладжа в оригинале имеет вовсе не такой заголовок, как в переводе. Зоопарк (polyglot persistence), видимо, неизбежное следствие NoSQL, о чем читатели оригинала были честно предупреждены. Возникающие проблемы авторы предвидели, но недооценили. В связи с чем придумывают мультимодельные СУБД (немного писал о них здесь). PostgreSQL в качестве мультимодельной СУБД скорее не рассматривают, чем рассматривают.


Зачем? Ну если только в пет-проекте попробовать…

Какой был самый большой бюджет у проекта, в котором вам доводилось участвовать? Я наверняка смогу указать «пет-проект» с большим бюджетом.


Может лучше разобраться чего они тормозят?

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




apapacy, a Couchbase не пробовали?

По документо-ориентированным DB — много вопросов возникает сейчас. Когда-то на волне хайпа я юзал монгу с nodejs. На тот момент сравнение производительности с теми же postgres+nodejs и mysql+nodejs выглядело сильно в пользу mongo+nodejs. Сейчас выигрыш по insert уже не наблюдается. (Причина неясна, скорее всего драйверы для nodejs были не очень производительные). Плюс у postgresql появился bson. Что имеем в итоге. Постгрес с bson перегрывает весь функционал mongodb и не уступает в производительности. Плюс дает возможность без проблем делать выборки с join, sum, like — чего не очень удобно делать в mongodb

Если взять интернет-магазин, то таблицу entity_attribute при использовании jsonb все таки заводить придется, т.к. нужно будет учесть, что значения в entity_attribute:
— могут выбираться из списка (цвет товара, класс пожарной опасности).
— могут быть произвольным текстовым полем (название материала подошвы кроссовок)
— могут быть числом с заранее заданным диапазоном (размер шкафа, положительное целое число со значениями от и до)
— хранимое значение и вывод этого значения могут отличаться (водостойкость/морозостойкость в БД хранится, как true/false, а на выходе должно быть отсутствует/присутствует или да/нет или 0/1 или еще как-нибудь)
— может быть обязательным к заполнению или нет
— возможно значение по умолчанию
— единица измерения значения (ширира обоев в см, длина обоев в метрах)

Подскажите, а есть ли какие-нибудь цифры по построению фасетных фильтров при использовании EAV и JSONB?

Это всё конечно хорошо, но одним sql сыт не будешь, а orm поддерживают jsonb плохо. Пытался мигрировать с eav на jsonb, но бросил т. к. каждый раз извлекаешь json целиком и парсишь его, чтобы извлечь/изменить нужный атрибут.
Хотя по месту выигрыш был сладкий — 10+ 2Gb индексов превратились в 2.5 +0.6 Gb.

Так есть же операторы позволяющие извлекать конкретные значения из JSON документа, и менять. А с 12 версии в Постгре вообще полноценная поддержка JSONPath.
Это все прекрасно и этим удобно пользоваться в рамках raw sql. Но если вы посмотрите на реализации поддержки JSONB в, например, Hibernate вы увидите использование jackson, а не ->, ->>, <@ и.т.д.
ЯННП, зачем вспоминать EAV, когда давно изобрели монгу?

В принципе статья хорошая в плане направления исследования. Я сам был уверен что индекс по bson работает дольше чем обычный.


Но.


Автор забыл определить индекс для поля value, чем сделал результаты просто неверными.


Я решил перепроверить — уж очень большая была разница и добавил индекс на поле value


Разница была и не очень существенная. Первый запрос по GIN было более продолжительое планирование. При повторных запросах планирование существенно (в 100 раз) сократилось и запрос по GIN был быстрее ( 1,2 мс против 1,3 мс). Но все же это не так у автора 1000-х разница

Автор забыл определить индекс для поля value, чем сделал результаты просто неверными.

Ха, я и смотрю, какие-то фантастические результаты у автора получились.
Ведь стоило добавить к таблице entity_attribute_value многоколоночный btree-индекс (value, entity_attribute_id, entity_id),
как снова производительность возрастает в несколько раз, но теперь уже не в пользу jsonb и gin-индекса.

Я не добавлял многоколоночный (добавил одноколоночный), скорость практически одинаковая. Но все же немного ниже на 0,1 мс. Возможно на моем конкретном комьютере и с моими конкретными данными.


Скорее всего скорость может немного просесть если будет большой json объект с большим количеством свойств.


Впрочем это (то есть равноценность) вполне ожидаема, т.к. для чего иначе это было разрабатывать.

На счёт равноценности Вы в целом правы, но после многих update производительность выборки из gin либо деградирует (при включенном fastupdate), либо сами update сильно замедляются (при выключенном fastupdate). Этого недостатка нет у btree.
А FullTextSearch для той же цели, что и EAV придуман — еще более быстрая штука и универсальная, чем JSONB

Токенами в FullTextSearch для данной цели будут является свойства объектов.

Бонусом — поиск по части названия/описания одновнеменно с фильтрацией по свойствам объектов (EAV — как правило для интернет-магазинов, так что такой поиск лишним не будет)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации