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

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

Спасибо за интересное решение. А производительность не сравнивали с более традиционным подходом?

Отчет о производительности для 200 000 групп,
4 000 000 записей Post,
каждая запись Post находитсья в 40 группах — https://github.com/shhavel/demo_group_ids_performance_test
Возмодно, 4 000 000 записей оказалось мало, так кhttps://github.com/shhavel/demo_group_ids_performance_testак, достаточно быстро работает и без индекса.
Это для случая при котором Post принадлежит многим группам.


Если же Post принадлежит только одной группе,
то способ выбора всех записей Post с использованием массива group_ids немного проще
в сравнении с использованием третьей таблицы.
Запрос получения записей Post доступных пользователю:


SELECT * FROM posts WHERE id IN (<user.group_ids>);

где <user.group_ids> — список ID групп пользователя.
При использованиии третьей таблицы этот список еще нужно получить,
а при использовании колонки group_ids список заведома известен (при условии, что выполнен запрос на получение пользователя)


Спасибо

Производительность у колонок массивов в pg неплохая, если построить индексы. Правда с построением индексов по некоторым полям бывают проблемы, например индекс по UUID[] потребует дополнительных действий.

Статья хорошая, спасибо, вот только больно смотреть, когда для объяснения работы с Ecto вдруг оказался нужен Phoenix.

Иначе заметка получилась бы в пять раз короче и проще же :)
Статья содержит только примеры кода моделей и тестов моделей
(ее суть — как раз показать использование колонки типа Array в моделях),
если убрать Phoenix статья короче не станет.

Спасибо
Сначала коммьюнити почти сделало из руби похапе-на-стероидах, потому что «ну с рельсами быстрее же». Теперь финикс (который, к счастью, гораздо меньше рассчитан на дебилов, чем рельсы) тащат в примеры использования Ecto.

Веб здесь избыточный, вредный и неуместный артефакт.
«больно» — это проявление чувства, что может быть связано с индивидуальными особенностями Вашего организма.
Но я так понимаю, Вы считаете использование Phoenix в данной статье неоправданным.
Phoenix был использован для генерации моделей и тестов, и возможно, это уменьшает объем работы для воспроизведения описанного решения. Поэтому я считаю использование Phoenix уместным.

Спасибо
Это всё, конечно, хорошо, но что будет если удалить группу? Для массивов нет поддержки foreign keys (2ndquadrant хотели добавить её в 9.3, но были проблемы с производительностью, потом, судя по всему, переключились на другое).

Я думаю, поддержка foreign keys будет еще добавлена в PostgreSQL.
На сегодняшний же день, можно просто удалять ID группы из всей записей posts
при удалении самой группы:


UPDATE posts SET group_ids = group_ids - 4 WHERE group_ids && ARRAY[4];

Несомненно, это создает дополнительное неудобство, но не отбрасывает вариант использования колонки типа Array как альтернативу третьей таблицы.


Спасибо

Часто возникает необходимость добавить дополнительные свойства в третью таблицу. По мере роста базы данных это очень даже вероятно. Потом можно очень сильно пожалеть что не сделал старую добрую таблицу и решение будет больше похоже на костыль чем на удобный вариант. А ведь может потребоваться и связи делать с третьей страницев в таком случае совсем сложно представить как это будет выглядить. Обычная реляционная структура БД части которой реализованны в одном поле массивом.

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


Спасибо

А как быть с целостностью данных?
Я пару раз так делал. Но оба раза закончилось тем, что понадобилось на эту связь m2m повесить дополнительные признаки и пришлось переделывать на класический m2m с третьей таблицей.
И не стоит забывать про проверку существования ключей. Например удаляете вы группу, а кто удалит её из списков у всех юзеров. Foregin key не даст просто так сделать не консистентную базу, а список интов — даст.
Интересно было бы посмотреть на механихм работы такого индекса. Не будет ли он просто аналогом той самой третьей таблицы?

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


Рассмотрим пример исользования в rails с исользованием cancan.
Пусть, Post принадлежит только одной группе
Третья таблица, для связи users и groupsmemberships


Определение выборки всех записей Post доступных пользователю с использованием третьей таблицы:


can :read, Post, group_id: user.memberships.pluck(:group_id)

Определение выборки всех записей Post доступных пользователю с использованием group_ids:


can :read, Post, group_id: user.group_ids

Спасибо

А конкретных бенчей нет?

Пока только это — https://github.com/shhavel/demo_group_ids_performance_test


200 000 групп
4 000 000 постов
каждый пост принадлежит 40-ка группам.


Выборка работает быстро и без индекса.
Постараюсь подготовить еще данные, чтобы было заметно разницу от использования индекса.

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

Это решение уже используется в production.
Я попытался описать его преимущества в некоторых ситуациях.


Спасибо

В статье все очень красиво. Но обратите внимание на свои ответы в комментариях, когда посыпались вопросы: «дополнительное неудобство»,
«подходит только для случая». Решение может и имеет право на жизнь, но при огромных лимитейшенах, а хорошего бенефита не видно. Видно только убийство масштабируемости.

«На один запрос меньше» при каждом обращении к приложению — это приимущество.


А также:


  • упрощение кода приложения при получении списка ID груп пользователя.
  • очевидная связь массивва group_ids с HTML multi select для выбора групп
    и только один UPDATE запрос при редактировании.

Спасибо

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

Если в таблице есть вторичный ключ, то это тожt можно назвать хранением свойств (бизнесс данных) и связок (служебных данных).
Кроме того Вы не вынуждены получать запись с информацией о всех связях если эта информация вам не нужна,
можно же выбирать только те поля которые нужны перечислив их в SELECT.


Например если нужно выбрать только id, name, email:


SELECT id, name, email FROM users;
В PostgreSQL появились некоторые возможности баз данных NoSQL, плюс еще и с индексами.
И иногда, это вполне может оказаться полезным.
Например, если таблица, которая хранит сущность данных массива, небольшая и из нее редко удаляются данные, то почему нет? :)
Спасибо за статью! Думал, что никто и не пользуется таким :)
Я в связке с Django использую поле-массив для тэгов. Пока вроде все нормально.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории