Технические заметки одного Евтуховича

Рассказ о серых трудовых буднях инженера программных систем

Hstore — key-value расширение для postgresql

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

Наверное, не все знают, что для postgresql существует большое количество расширений, которые называются contrib модулями.

Рассмотрим один из таких модулей - hstore. Этот модуль нужен для того, чтобы в одном поле в БД хранить много значений key/value, фактически, просто какой-то хеш. При этом и ключи и значения могут быть только строками. О том, чем это лучше, нежели просто хранить в текстовом поле сериализованный хеш, я расскажу чуть-чуть попозже. Понадобится это может в том случае, если у вас есть модели с произвольным набором полей.

Вначале поставим это расширения в Ubuntu 11.10

sudo apt-get install postgresql-contrib

Потом поставим этот модуль. Для этого в 9.1 зайдем в консоль psql.

CREATE EXTENSION hstore;
WARNING:  => is deprecated as an operator name
DETAIL:  This name may be disallowed altogether in future versions of PostgreSQL.

Для версии младше 9.1 процедура установки немного другая. Далее создадим тестовую таблицу и заполним ее данными.

CREATE table test(id serial, value hstore);

INSERT INTO test(value)
SELECT hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text) 
FROM
    generate_series(1, 1000);

UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);
UPDATE test SET value = value || 
  hstore(((random() * 1000)::integer)::text, ((random() * 1000)::integer)::text);

SELECT * FROM test LIMIT 1;
 id |                                                    value                                                     
----+--------------------------------------------------------------------------------------------------------------
  1 | "33"=>"923", "262"=>"181", "471"=>"658", "554"=>"916", "579"=>"472", "649"=>"287", "891"=>"620", "897"=>"24"

Ваш результат, понятно, будет отличаться, но это непринципиально.

Пока, конечно, это ничем не отличается от хранения сериализованного хеша в поле таблицы. Но тут мы добавим немного особой postgresql магии.

CREATE INDEX ON test USING GIN(value);
" ну, или вот так
CREATE INDEX ON test USING GIST(value);

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

Итак, попробуем.

" Найдем все упячечные ключи
explain SELECT value -> '111' FROM test WHERE value ? '111';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=12.01..16.02 rows=1 width=114)
   Recheck Cond: (value ? '111'::text)
   ->  Bitmap Index Scan on test_value_idx  (cost=0.00..12.01 rows=1 width=0)
         Index Cond: (value ? '111'::text)

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

Конечно, уже нашлись коллеги, которые написали плагин для activerecord. Судя по комитам от октября 2011 проект живой, но сам я им не пользовался, если у кого-то есть опыт — было бы интересно услышать.

Так что в следующий раз, когда мне понадобится метод serialize, я попробую вначале hstore, и обязательно поделюсь с вами.

Комментарии