Postgre(no)SQL или снова о хранении данных с гибкой структурой

    Когда вопрос заходит о хранении в БД гибких (заранее не известных, часто изменяемых) структур данных, разработчики обычно обращаются к «великому и ужасному» EAV-паттерну, либо к ныне модным NOSQL базам данных.
    Не так давно такая задача стала и передо мной.
    EAV. Вызывает у меня стойкую неприязнь, да и сказано и написано об этом было очень много всего негативного (Кайт, Фаулер, Карвин, Горман). Главный минус в том, что при написании запросов приходится оперировать уже не реальными сущностями («Сотрудник», «Дом», «Клиент», то для чего и предназначен SQL), а объектами, орагнизованными на более низком уровне (извините за сумбур). Поэтому это был самый не желательный вариант.
    NOSQL. Поначалу очень заинтересовал этот вариант (в частности MongoDB). После продолжительного использования реляционок, первое время начинаешь испытывать чувство тотальной свободы, от которого захватывает дыхание. Хранение документов любой структуры, моментальное создание новых коллекций, запросы к ним — красота! Но после непродолжительного использования эйфория начала спадать, а проблемы обнаруживаться:
    — Бедный язык запросов (ИМХО) + отсутствие джойнов;
    — Отсутствие схем (хорошая статья недавно была на эту тему (и не только на эту) habrahabr.ru/post/164361);
    — Отсутствие встроенной поддержки ссылочной целостности;
    — Отсутствие прибамбасов в виде хранимых процедур/функций, триггеров, представлений и многого другого.
    — В моем приложении помимо данных с гибкой(изменяемой) структурой также необходимо хранить обычные статические данные — таблица пользователей, посещений, сотрудников и т.д. Работать с которыми (опять же имхо) гораздо проще и (самое главное) надежнее в обычной реляционной базе (та же самая ссылочная целостность и пр.).



    Первую проблему (частично) я пытался решать с помощью ORM (это был Spring Data), он позволял писать сносные запросы к объектам, однако для этого нужно заранее создать и скомпилить все классы (соответствующие нужным коллекциям) и в запросах оперировать уже ими. Для меня это не подходило, т.к. коллекции должны создаваться и изменяться часто и оперативно — «на ходу».
    Вторую — с помощью создания отдельной коллекции для хранения структур всех остальных коллекций, чтобы проверять корректность вводимых данных и т.д.
    До решения остальных проблемм дело не дошло, бросил…
    Уже на данном этапе моя база стала напоминать очень хрупкое сооружение, полностью зависящее от приложения, плюс я должен был реализовывать вручную многие вещи, которые большинство реляционок могут делать и так, из коробки. Может это и нормально, но как то не привык я к этому, как то не по себе стало.

    Далее я задумался о том, как здорово было бы совместить реляционную и NOSQL СУБД. С одной стороны вся мощь реляционки со всеми прилагающимися, с другой — легкость и элегантность документоориентированного решения. И действительно, что мешает хранить объекты с гибкой структурой в некой отдельной специальной таблице (таблицах) например в формате xml, а обращаться к ним с помощью XPATH, тем более, что многие современные СУБД имеют развитые средства работы с XML (включая индексирование).
    Решил попробовать на небольшом примере с использованием Postgresql, что из этого получится, как будут выглядеть запросы:

    Для начала хватит двух служебных таблиц, думаю комментарии излишни:

    CREATE TABLE classes
    (
      id integer NOT NULL,
      name text,
      is_closed boolean,
      obects_count integer,
      CONSTRAINT classes_pk PRIMARY KEY (id )
    );
    
    CREATE TABLE objects
    (
      id integer NOT NULL,
      body xml,
      id_classes integer,
      CONSTRAINT objects_pk PRIMARY KEY (id ),
      CONSTRAINT classes_objects FOREIGN KEY (id_classes)
          REFERENCES classes (id) MATCH SIMPLE
          ON UPDATE NO ACTION ON DELETE NO ACTION
    );
    
    CREATE INDEX fki_classes_objects
      ON objects
      USING btree
      (id_classes );
    
    


    Создаем две сущности для экспериметнов:

    INSERT INTO classes(
                id, name, is_closed, obects_count)
        VALUES (1, 'customers', FALSE, 0);
    
    INSERT INTO classes(
                id, name, is_closed, obects_count)
        VALUES (2, 'orders', FALSE, 0);
    


    Подготовим две функции для генерации тестовых случайных данных (взяты на просторах Интернета):

    CREATE OR REPLACE FUNCTION random(numeric, numeric)
      RETURNS numeric AS
    $BODY$
       SELECT ($1 + ($2 - $1) * random())::numeric;
    $BODY$
      LANGUAGE sql VOLATILE
      COST 100;
    
    CREATE OR REPLACE FUNCTION random_string(length integer)
      RETURNS text AS
    $BODY$
    declare
      chars text[] := '{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}';
      result text := '';
      i integer := 0;
    begin
      if length < 0 then
        raise exception 'Given length cannot be less than 0';
      end if;
      for i in 1..length loop
        result := result || chars[1+random()*(array_length(chars, 1)-1)];
      end loop;
      return result;
    end;
    $BODY$
      LANGUAGE plpgsql VOLATILE
      COST 100;
    


    Заполнение таблицы случайными данными, объектами классов «Клиент» и «Заказ» (связь один ко многим, каждый клиент сделал по пять заказов):

    DO $$
    DECLARE
    	customer_pk integer;
    	order_pk integer;
    BEGIN
    	FOR i in 1..10000 LOOP
    		customer_pk := nextval('objects_id_seq');
    		order_pk := nextval('objects_id_seq');
    
    		insert into objects (body, id_classes) values((
    		'<Customers>
    			<Customer>
    				<ID>' || customer_pk || '</ID>
    				<Name>' || random_string('10') || '</Name>
    				<Partners>' || random_string('10') || '</Partners>
    			</Customer>
    		</Customers>')::xml, 1);
    
    
    		for j in 1..5 LOOP
    
    			insert into objects (body, id_classes) values((
    			'<Orders>
    				<Order>
    					<ID>' || order_pk || '</ID>
    					<Customer_id>' || customer_pk || '</Customer_id>
    					<Cost>' || random(1, 1000) || '</Cost>
    				</Order>
    			</Orders>')::xml, 2);
    
    		end loop;
    
    	END LOOP;
    END$$;
    


    Первым запросом выберем максимальную стоимость заказа:

    explain select max(((xpath('/Orders/Order/Cost/text()', O.body))[1])::text::float) as cost_of_order
      from Objects O
     where O.id_classes = 2;
    

    /*
    Aggregate  (cost=2609.10..2609.11 rows=1 width=32)
      ->  Seq Scan on objects o  (cost=0.00..2104.50 rows=50460 width=32)
            Filter: (id_classes = 2)
    */
    
    

    Запрос получился немного заковыристым, но все же вполне понятным: сразу понятно к какой сущности выпоняется запрос и по какому атрибуту. Как ни странно получился фулл скан, однако ничто не мешает построить индекс по атрибуту Cost:

    create index obj_orders_cost_idx on objects using btree (((xpath('/Orders/Order/Cost/text()', body))[1]::text::float));
    


    И теперь запрос отрабатывает горазд быстрее и использует индекс:
    /*
    Result  (cost=0.15..0.16 rows=1 width=0)
      InitPlan 1 (returns $0)
        ->  Limit  (cost=0.00..0.15 rows=1 width=32)
              ->  Index Scan Backward using obj_orders_cost_idx on objects o  (cost=0.00..7246.26 rows=50207 width=32)
                    Index Cond: ((((xpath('/Orders/Order/Cost/text()'::text, body, '{}'::text[]))[1])::text)::double precision IS NOT NULL)
                    Filter: (id_classes = 2)
    */
    


    Теперь попробуем выбрать информацию о заказах нескольких конкретных сотрудников, т.е. связку двух таблиц:

    explain select (xpath('/Customers/Customer/Name/text()', C.body))[1] as customer
         , (xpath('/Orders/Order/Cost/text()', O.body))[1] as cost_of_order
      from objects C
         , objects O
     where C.id_classes = 1
       and O.id_classes = 2
       and (xpath('/Orders/Order/Customer_id/text()', O.body))[1]::text::int = (xpath('/Customers/Customer/ID/text()', C.body))[1]::text::int
       and ((xpath('/Customers/Customer/ID/text()' ,C.body))[1])::text::int between 1997585 and 1997595;
    


    /*
    Hash Join  (cost=1873.57..6504.85 rows=12867 width=64)
      Hash Cond: ((((xpath('/Orders/Order/Customer_id/text()'::text, o.body, '{}'::text[]))[1])::text)::integer = (((xpath('/Customers/Customer/ID/text()'::text, c.body, '{}'::text[]))[1])::text)::integer)
      ->  Seq Scan on objects o  (cost=0.00..2104.50 rows=50460 width=32)
            Filter: (id_classes = 2)
      ->  Hash  (cost=1872.93..1872.93 rows=51 width=32)
            ->  Bitmap Heap Scan on objects c  (cost=196.38..1872.93 rows=51 width=32)
                  Recheck Cond: (id_classes = 1)
                  Filter: (((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer >= 1997585) AND ((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer <= 1997595))
                  ->  Bitmap Index Scan on fki_classes_objects  (cost=0.00..196.37 rows=10140 width=0)
                        Index Cond: (id_classes = 1)
    */
    


    Ожидаемый фуллскан, теперь слегка проиндексируем:

    create index obj_customers_id_idx on objects using btree (((xpath('/Customers/Customer/ID/text()', body))[1]::text::int));
    create index obj_orders_id_idx on objects using btree (((xpath('/Orders/Order/ID/text()', body))[1]::text::int));
    create index obj_orders_customerid_idx on objects using btree (((xpath('/Orders/Order/Customer_id/text()', body))[1]::text::int));
    


    Теперь получается веселее:

    /*
    Hash Join  (cost=380.52..5011.80 rows=12867 width=64)
      Hash Cond: ((((xpath('/Orders/Order/Customer_id/text()'::text, o.body, '{}'::text[]))[1])::text)::integer = (((xpath('/Customers/Customer/ID/text()'::text, c.body, '{}'::text[]))[1])::text)::integer)
      ->  Seq Scan on objects o  (cost=0.00..2104.50 rows=50460 width=32)
            Filter: (id_classes = 2)
      ->  Hash  (cost=379.88..379.88 rows=51 width=32)
            ->  Bitmap Heap Scan on objects c  (cost=204.00..379.88 rows=51 width=32)
                  Recheck Cond: (((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer >= 1997585) AND ((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer <= 1997595) AND (id_classes = 1))
                  ->  BitmapAnd  (cost=204.00..204.00 rows=51 width=0)
                        ->  Bitmap Index Scan on obj_customers_id_idx  (cost=0.00..7.35 rows=303 width=0)
                              Index Cond: (((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer >= 1997585) AND ((((xpath('/Customers/Customer/ID/text()'::text, body, '{}'::text[]))[1])::text)::integer <= 1997595))
                        ->  Bitmap Index Scan on fki_classes_objects  (cost=0.00..196.37 rows=10140 width=0)
                              Index Cond: (id_classes = 1)
    */
    


    Этот запрос так же не потерял своей наглядности, однако его можно еще больше причесать: разобраться с преобразованием типов, оптимизировать xml структуру и т.д. Работы еше много, это просто небольшой пример.

    Что можно сделать еще:

    1. Гибкий поиск по атрибутам объектов любых классов;
    2. Таблицу objects можно партицировать (хотябы частично), например хранить объекты больших классов физически отдельно.

    Хранение данных в БД в формате xml естествеено не является новинкой, однако при поиске решения данного моего вопроса информации об этом было очень мало, не говоря уже о конкретных примерах. Надеюсь кому нибудь пригодиться или(и) услышать в комментариях отзывы и мнения людей, поработавших с похожей схемой.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +4
      Главный минус такого решения — сложность конкурентных изменений.
        0
        Не больше, чем в EAV.
          +2
          Значительно больше. При EAV меняется строка в таблице, при XML — надо целиком переписывать документ. Таким образом, два разных запроса могут поменять при EAV без проблем конкурентно, а при XML — один переписывает документ целиком, потом второй.
            0
            Согласен.
            Но многие приложения, которые я видел, в любом случае выполняли UPDATE всей строчки, даже если данные во ходной форме изменились только в одном поле, или вообще не изменились. Это конечно не правильно.
              0
              Теоретически и xml-документ можно обновлять только частично, новыми данными (например контроль на триггерах и т.д.).
                0
                обновлять частично можно, вынеся read-update-save цикл на сервер, но это не меняет факта, того что это будут только последовательные, а не конкурентные обновления.
            0
            А в чем сложность то?
            При изменении объекта необходимо следить за целостностью целого объекта. Поэтому хоть ты изменяешь одно поле, хоть весь объект целиком, механизму конкурентных изменений должно быть все-равно.
            Допустим, у нас optimistic concurrency. В обоих случаях это будет ровно одна проверка одного поля объекта. И, конечно, это поле нужно в XML варианте, хранить не в самом XML (чтобы его чтение было дешево).

            Или имелось ввиду, что при XML потребуется больший объем трафика при небольших изменениях?

            +14
            Поскольку первые 4 абзаца у вас состоят из ИМХО и эмоций, разрешите спростить так же:
            «Ну почему из трёх вариантов EAV, NoSQL, XML. Вы выбрали самый неподходящий и отстойный. Да ещё и пример привели который решается проще на SQL.»
              0
              Эмоции присутствуют, не скрываю, праздники все таки…
              На sql проще, само собой, но только когда заранее известна структура данных и заранее можно определить и продумать запросы к ней. Я же описывал ситуацию обратную, когда структура заранее не определена и периодически изменяется.
                +2
                В выборе архитектуры хранения очень важны детали. Собственно поэтому в РСУБД применяются такие противоестевтвенные для неё вещи как например денормализация и хинты. А вы не привели никаких подробностей про динамическую часть схемы. И все «минусы» более вменяемых решений выглядят в такой ситуации не просто надуманными, а фиктивными.

                «EAV. Вызывает у меня стойкую неприязнь, да и сказано и написано об этом было очень много всего негативного (Кайт, Фаулер, Карвин, Горман). Главный минус в том, что при написании запросов приходится оперировать уже не реальными сущностями («Сотрудник», «Дом», «Клиент», то для чего и предназначен SQL), а объектами, орагнизованными на более низком уровне (извините за сумбур). Поэтому это был самый не желательный вариант.»

                Про хранение в XML всех полей записи вы найдёте не меньше негатива.

                "— Бедный язык запросов (ИМХО) + отсутствие джойнов;"

                Джойны там не всегда нужны. А язык запросов как правило там не на стороне сервера а на стороне приложения (хоть и выполняются они и на сервере тоже)

                "— Отсутствие схем (хорошая статья недавно была на эту тему "

                Учитывая динамическую природу у гипотетического примера — её необходимость в классическом смысле под сомнением.

                "— Отсутствие встроенной поддержки ссылочной целостности;"

                Согласен, что это массово не решённая проблема.

                "— Отсутствие прибамбасов в виде хранимых процедур/функций, триггеров, представлений и многого другого."

                Вы сильно отстали от жизни, это есть, но опять же, не везде.

                "— В моем приложении помимо данных с гибкой(изменяемой) структурой также необходимо хранить обычные статические данные — таблица пользователей, посещений, сотрудников и т.д. Работать с которыми (опять же имхо) гораздо проще и (самое главное) надежнее в обычной реляционной базе (та же самая ссылочная целостность и пр.)."

                И без деталей примера вообще не понятно можно было ли обойтись одним SQL.

                  +2
                  Согласен со всеми пунктами. Однако целью моей статьи было показать один из возможных вариантов решения популярной проблемы, саму идею, и небольшой пример использования. Вопрос об использовании данного способа для какой либо конкретной задачи остается открытым, никакой пропаганды нет. Однако для своей задачи я (пока) остановился именно на нем. Описание ее (задачи) целью статьи не являлось, но, если это будет интересно, могу описать ее и причины выбора данного подхода.
                0
                О решаемой задаче в кратце.
                Сущности (за исключением системных) создаются полностью пользователем (через пользовательский интерфейс), т.е. ядро системы ничего не знает (и не может знать) о них. Далее пользователь должен иметь возможность работать со своими сущностями, выбирать данные из них (выборки могут быть любой сложности, любой степени вложенности и т.д.). Для этого используется QBE, который автоматически преобразуется к обычному sql-запросу, так же остается возможность (для продвинутых пользователей) составлять запросы самостоятельно (для сложных, нетривиальных выборок) на стандартном, хорошо известном многим языке SQL.
                При этом пользователь в любом случае (даже при ручном написании запросов) работает со своими логическими объектами, тонкости технической реализации (насколько это возможно) остаются для него скрытыми (чего не скажешь о EAV).
                  +2
                  что мешает при этом создать tablespace, внутри которого создавать просто таблицы?
                    0
                    Создание таблицы в РСУБД это дорогое действие, и совершать его при каждом неосторожном действии пользователя, по любому поводу — думаю не самое лучшее решение (хотя такой вариант я тоже рассматривал).
                    Так же при малейшем изменении структуры таблицы(таблиц) придется изменять все пользовательские таблицы, потеряется необходимая гибкость.
                      0
                      опечатался: «изменять все пользовательские запросы»
                  +2
                  а чем плох EAV?

                  Простая реляционная структура типа goods<-parameters->categories. Запросы вполне наглядные, будут выглядеть примерно так:

                  select price.goods_id
                  	from parameters price
                  	join categories priceCategory 
                  		on priceCategory.id=price.category_id 
                  		and priceCategory.name="Цена" 
                  		and price.numericValue>100.0 and price.numericValue<200.0
                  	join parameters color
                  	join categories colorCategory 
                  		on colorCategory.id=color.category_id 
                  		and colorCategory.name="Цвет" 
                  		and (color.stringValue="Поносный" or color.stringValue="Жёлтый")


                  т.е. выбрать иды товаров с ценой от 100 до 200 и цветом «Поносный» либо «Жёлтый». Компактные запросы вполне поддающиеся оптимизации и разграничению прав на элементы справочника параметров. И просто и работает быстро.
                    0
                    Проблемы начнутся, когда нужно будет организовать поиск не по всем товарам, а только по конкретным, например среди всех чайников и самоваров и когда сложность запроса немного увеличится. Тогда в ход пойдут айдишники (т.к. в боле менее сложный запрос достаточно накладно все время приджойнивать таблицы с названиями классов и типами параметров (в Вашем случае categories)), а для удобства можно будет держать в голове, что 493832 — это силикатный кипич, а 2345638 — детское питание.
                    Так же такую структуру сложно проиндексировать, частенько приходится делать мат представления для того или иного конкретного участка (в тех базах где они есть конечно), но от этого беспорядок только увеличивается.
                      –1
                      нормально всё будет. Добавится ещё условие и всё

                      select price.goods_id
                      	from parameters price
                      	join categories priceCategory 
                      		on priceCategory.id=price.category_id 
                      		and priceCategory.name="Цена" 
                      		and price.numericValue>100.0 and price.numericValue<200.0
                      	join parameters color
                      	join categories colorCategory 
                      		on colorCategory.id=color.category_id 
                      		and colorCategory.name="Цвет" 
                      		and (color.stringValue="Поносный" or color.stringValue="Жёлтый")
                      	join parameters kind
                      	join categories kindCategory 
                      		on kindCategory.id=kind.category_id 
                      		and kindCategory.name="Изделие" 
                      		and (kind.stringValue="Чайник" or kind.stringValue="Самовар")
                      
                        +1
                        Запросы EAV имеют свойства увеличиваться от сложности задачи гораздо быстрее, чем обычные реляционные.

                        Вот аналог с применением xml:
                        select *
                          from objects C
                         where ((xpath('/Изделие/Наименование/text()', O.body))[1]::text::int in ('Чайник', 'Самовар')
                           and ((xpath('/Изделие/Цвет/text()', O.body))[1]::text::int in ('Желтый', 'Поносный')
                           and ((xpath('/Изделие/Цена/text()', O.body))[1]::text::int between 100.0 and 200.0;
                        
                          +2
                          ну и в чём разница? Только в том что SQL-сервер выполнит запрос мгновенно а все известные мне XML-движки дадут огромный провал в производительности.
                            0
                            16 строчек против 5 плюс абсолютная нечитаемость, и это на элементарном запросе.
                            К EAV невозможно применить нормальной индексации, при среднем и большом количестве записей «мгновенно» не получится.
                              0
                              нормально в постгре eav оптимизируется и индексируется за счет битмапов.
                                +1
                                А разве использовать битмапы на редактируемых данных не преступление? :)
                                  0
                                  битмаповые карты индексов позволяют использовать индекс по двум независимым колонкам для выборок OR/AND и вместо совместного индекса
                    +1
                    На softwaremaniacs проскакивало, что если postgresql и нужен EAV, то надо глядеть в сторону hstore www.postgresql.org/docs/9.2/interactive/hstore.html
                      0
                      кстати очень переспективная штукенция.
                      но не всё прозрачно с индексами.
                      уже пару недель эксперименты ставлю.
                      +1
                      В-нулевых, для EAV можно и нужно написать хороший ORM, который будет вас абстрагировать от низкоуровневых объектов.
                      Во-первых, для производительности нужно грамотно настроить индексы и саму db а так же можно делать view's (в оракле) или аналоги в других db.
                      Во-вторых, три таблицы это самый минимум, на самом деле для удобства использования их нужно иметь несколько десятков.
                      В-третьих, для очень сложной enterprise-системы вы ничего гибче не придумаете. Не зря, например Magento, на EAV работает.

                      Моя компания делает продукт, для которого данные хранятся в EAV, спрашивайте вопросы, постараюсь ответить по мере знаний (я не пишу ядро и очень многих тонкостей не знаю) и соблюдения NDA.
                        0
                        а есть что-то нетривиальное? расскажите.
                          0
                          Так как ядром я не занимаюсь то навскидку ничего хитрого вспомнить не могу. Разве что проблему с ANSI джоинами в оракле, у которых есть ограничение то ли в 1000 то ли около того, в результате чего на больших запросах оракл тормозил и падал с ошибкой, пока все не переписали на нативные джоины.
                          +1
                          В-нулевых, для EAV можно и нужно написать хороший ORM, который будет вас абстрагировать от низкоуровневых объектов.

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

                          Во-первых, для производительности нужно грамотно настроить индексы и саму db а так же можно делать view's (в оракле) или аналоги в других db.

                          Хотел бы посмотреть как строится индексы для EAV-модели.
                          Насчет вьюх (и мат вьюх) согласен, т.к. это остается единственным способом хоть как то управлять производительностью и читаемостью запросов. Получется, что вместо таблиц приходится создавать представления.

                          Не зря, например Magento, на EAV работает.

                          И поэтому, начиная со 2-й версии, от него избавляются:
                          dimitrigatowski.com/2011/06/19/magento-2-preview/
                          +1
                          Я всем рекомендую расширение hstore (http://blog.evtuhovich.ru/blog/2012/01/23/hstore/). За счет его введения устраняется один из основных недостатков реляционных баз данных.

                          Автору. Не морочьте голову себе и людям.
                            –1
                            hstore интересное решение, но (как и любое другое) не панацея.
                            0
                            Не так давно решали аналогичную задачу, с некоторыми отличиями — динамическая схема нужна только для части колонок, и важна производительность на довольно сложных выборках (много критериев фильтрации + сортировка).

                            EAV был отброшен как непрактичный (и вероятно ещё и очень медленный). XML и hstore как слишком медленные и требующие много памяти для хранения. Остановились на JSON, накидали функции для его поддержки на perl, а позднее на c.

                            Если интересно могу написать пост об этом.
                              0
                              +1 за написание поста.
                                –1
                                Интересно уточнить чем ваше решение отличается от той же mongodb?
                                  0
                                  Вопрос не мне (так что извиняюсь), но как минимум, поддержка транзакций и нормальных sql-запросов.
                                    0
                                    Основная причина — для наших задач хороши реляционные бд: транзакции, ссылки на другие таблицы, нецелесообразность переноса всех остальных данных приложения, готовая, заточенная инфраструктура — ORM, кеширование, репликация и партицирование. Выборки по большому количеству непредсказуемых критериев, т.е. то, для чего старые добрые реляционные дб хороши.

                                    Так что добавлять новую технологию в свой стек особого резона не было.
                                      0
                                      Прошу прощения, невнимательно прочитал вопрос и ответил на «почему вы не использовали MongoDB».

                                      А отличается наше решение тем, что мы продолжаем использовать PostgreSQL со всеми его фишками и наработками со стороны приложения, и тем, что не надо ничего мигрировать, кроме нескольких таблиц, для которых гибкие схемы и нужны
                                        0
                                        спасибо за ответ. я также +1 за написание Вами поста.
                                        0
                                        а в 9.2 вроде json штатно прикрутили. даже с индексами
                                          0
                                          Индексы там на текущий момент только через подключаемый v8/python/perl/etc
                                          Нативно только валидация json и row2json

                                          wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#JSON_datatype
                                            0
                                            хм. надо покопать. давненько хотелось что то такое абстрактное для логов, например.
                                        0
                                        — Бедный язык запросов (ИМХО) + отсутствие джойнов;

                                        язык запросов не бедный, просто нужно в нем разобраться, относительно джоинов:
                                        docs.mongodb.org/manual/applications/database-references/
                                        группировка:
                                        docs.mongodb.org/manual/reference/command/group/

                                        согласен, все не так просто как хотелось бы, но это есть!
                                          –1
                                          Да, я в курсе, и про эту штуку тоже, но ведь это все равно «был не Нескафе».
                                            0
                                            не Нескафе, не Чибо и не Якобс, а Несквик, такой же веселый и свободный, выражаясь аллегорично. Просто большинство тех, кто работал с реляционками пытается применить тот же подход к Mongo, но она принципиально другая, дает больше свободы, которой нужно воспользоваться с умом, в начале от нее кружится голова, а потом понимаешь, что зашел далеко, все плохо, и нет, она тебе не подходит, но дело в том, что просто нужно научиться сначала планировать базу а потом ее реализовывать. Ничто не мешает написать обертку, которая будет проверять целостность данных. Как писалось в другой недавней статье, все мы мыслим схемами, в реляционных субд база хранит эту схему за нас, а в не реляционных ее нужно хранить в голове, ну или реализовать самостоятельно алгоритм ее поддержания.
                                              –1
                                              Обертку — можно (это я кстати и пытался сделать, когда начинал с Mongo), но во первых это придется делать самому (и работать это будет наверняка медленне, а в реляционке уже все в коробке), а во вторых к такой базе не подойдешь ни слева ни справа, только из конкретного приложения, малейший порыв даже самого легкого ветра со стороны — и что то пошло не так.
                                              Но это конечно от задачи зависит.

                                              ПС продолжая аллегорию, можно вспомнить, что Несквик это вроде детский напиток…
                                                +1
                                                Несквик не кофе, их нельзя сравнивать, это все-равно что сравнивать молоток и отвертку, каждый делает свою работу и подходит для конкретного случая.

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

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое