PostgreSQL и некоторые запросы

структура базы

Этот пост я пишу как частичную текстовую расшифровку видео с 1:14:42. Не знаю как Вам, но я не особо люблю смотреть видюшки, и легче воспринимаю написанное текстом с примерами. Поэтому делаю заметку для себя, но если оно окажется еще кому-то полезным — буду рад.

Создадим таблички вот такой структуры как на картинке и заполним их данными:

-- Таблица с постами
CREATE TABLE post(
			id bigserial PRIMARY KEY,
			person_id int8 NOT NULL,
			created_at timestamptz NOT NULL,
			something text
		);
-- Добавляем миллион записей с данными
INSERT INTO
		post(
			person_id,
			created_at,
			something
		) SELECT
			(random()* 10 ^5)::int8 AS person_id,
			now()- INTERVAL '1 minute' *(random()* 60 * 24 * 365 * 2) AS created_at,
			(
				SELECT
					string_agg( substr( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW XYZ0123456789 ',( random()* 72 )::INTEGER + 1, 1 ), '' )
				FROM
					generate_series(
						1,
						100 + i % 10 +(random()* 2000)::INTEGER
					)
			) AS something FROM generate_series(1, 1000000) AS g(i);
 
-- Удаляем записи записи одного автора у которых совпадает время
DELETE FROM post
WHERE
	(
		person_id,
		created_at
	) IN(
		SELECT
			person_id,
			created_at
		FROM
			post
		GROUP BY
			person_id,
			created_at
		HAVING
			COUNT(*) > 1
	);
 
-- Индекс по времени
CREATE
	INDEX i_post_created_at ON
	post
		USING btree(created_at);
 
-- Уникальный индекс по дате и автору
CREATE
	UNIQUE INDEX u_post_author_id_created_at ON
	post(
		person_id,
		created_at
	);
 
-- Табличка с авторами
CREATE
	TABLE
		person AS SELECT
			DISTINCT ON
			(person_id) person_id AS id,
			'person_' || person_id AS name
		FROM
			post;
 
-- Первичный ключ
ALTER TABLE
	person ADD PRIMARY KEY(id);
 
-- Внешний ключ. Не верьте тем, кто говорят что не нужно их использовать. С ними гораздо лучше.
ALTER TABLE
	post ADD CONSTRAINT fk_post_person_id FOREIGN KEY(person_id) REFERENCES person(id);
-- Собираем статистику
analyze post;
analyze person;

Далее пишем запросы которые выбирают какие-то данные из этих таблиц.
Первое и самое простое — посчитать количество постов, которое у нас есть в post

SELECT COUNT(*) FROM post;
COUNT  
---------
 1000000
(1 строка)

Этот запрос выполняется по следующему плану:

explain analyse select count(*) from post;
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=142283.69..142283.70 rows=1 width=8) (actual time=388.014..388.014 rows=1 loops=1)
   ->  Seq Scan on post  (cost=0.00..139784.35 rows=999735 width=0) (actual time=0.032..327.397 rows=1000000 loops=1)
 Planning time: 0.241 ms
 Execution time: 388.114 ms
(4 строки)

Видим сколько оно выполнялось. Это долго, если учесть что мы просто прочитали миллион строк и подсчитали их количество.

Самый быстрый и простой способ посмотреть сколько строчек в таблице — это заглянуть в статистику. Число будет не точным, но если у нас автовакуум настроен агрессивно и делает свою работу вовремя, то на полученное число можно ориентироваться:

explain select count(*) from post;
                             QUERY PLAN                             
--------------------------------------------------------------------
 Aggregate  (cost=142281.59..142281.60 rows=1 width=8)
   ->  Seq Scan on post  (cost=0.00..139782.67 rows=999567 width=0)
(2 строки)

Мы только что запускали analyze post; так что значение 999567 очень близко к точному значению.

В ситуации когда нам необходимо количество постов одного конкретного автора мы можем заставить его делать Index Only Scan и это будет очень быстро

explain analyze SELECT count(person_id) from post where person_id=1;
                                                                   QUERY PLAN                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=48.64..48.65 rows=1 width=8) (actual time=0.143..0.144 rows=1 loops=1)
   ->  Index Only Scan using u_post_author_id_created_at on post  (cost=0.42..48.61 rows=11 width=8) (actual time=0.060..0.115 rows=14 loops=1)
         Index Cond: (person_id = 1)
         Heap Fetches: 14
 Planning time: 0.395 ms
 Execution time: 0.277 ms
(6 строк)

Здесь мы в качестве параметра count() подставляем то же индексированное поле, по которому ограничиваем выборку в выражении where, таким образом, из индекса мы получаем все необходимое для формирования результата и postgres даже не обращается к страницам самой таблицы. Тут важную роль играет селективность person_id. Если бы выборка получалась больше некоторого значения, то, возможно, было бы дешевле сделать Bitmap Index Scan. В любом случае актуальность статистики и карты видимости очень важны для правильной оценки оптимального плана.
Больше про count() можно почитать в статье Joe Nelson Faster PostgreSQL Counting или тут

В следующем посте поговорим о пагинации и почему offset плохо.

Свое собственноe облако restfs

Я тут, тихим сапом, взялся осваивать c++. В результате моих изысканий родилось что то чуть более чем «Hello world». В качестве задачки я взялся за реализацию некоего REST API для облачного хранения файликов.
Первый прототип выложил на GitHub restfs.
Умеет оно совсем не много.
— создавать пользователей
— логиниться под этим пользователем (получать сессионный токен)
— создавать и просматривать директории
— заливать и скачивать файлики.

Работает как fcgi daemon, чтобы легко прятаться за nginx. Все это работает в потоках и настраивается простым ini файлом.
Файлики хранит в Elliptics on Yandex а информацию о них хранит в PostgreSQL.

Тут описание в формате RAML о том, как это использовать.

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

И это боюсь только начало…

Postgres + GSSAPI

secure-postgresql-deployment-17-728Чисто ради интереса, решил попробовать настроить аутентификацию при подключению к PostgreSQL из ActiveDirectory развернутом на базе Samba 4.3. В общем, оказалось ничего сложного.

1. Заводим обычного пользователя в AD, под которым наш сервер PostgreSQL будет обращаться в AD, и выгружаем keytab следующим образом (из винды, которая в домене):

C:\>ktpass -princ postgresql/srv.krb.local@KRB.LOCAL -mapuser pguser -pass pgpass -out pgpass.keytab

где postgresql — имя службы (обязательно именно такое для PostgreSQL),
srv.krb.local — FQDN сервера, на котором работает PostgreSQL,
KRB.LOCAL — REALM нашего домена,
pguser — пользователь, которого мы завели в домене,
pgpass — пароль этого пользователя в домене,
pgpass.keytab — имя файла куда выгружаем все эти данные.

2. На сервере srv.krb.local необходимо установить и настроить Kerberos клиента. Если этот сервер был ранее введен в домен, то все это у Вас уже настроено. В противном случае необходимо установить пакет krb5-workstation (у Вас же там Centos, правда?) и привести файл /etc/krb5.conf примерно к такому виду:

[libdefaults]
        default_realm = KRB.LOCAL
        dns_lookup_realm = false
        dns_lookup_kdc = true

а в /etc/resolv.conf nameserver должен указывать на DNS сервер, который обслуживает Ваш AD

3. Запишем файл pgpass.keytab куда-нибудь на сервер srv.krb.local, где его сможет прочитать PostgreSQL. Я например его записал в /var/lib/pgsql/krb/pgpass.keytab и выставил права:

$ chown postgres:postgres /var/lib/pgsql/krb/pgpass.keytab
$ chmod 600 /var/lib/pgsql/krb/pgpass.keytab

4. В Файле postgresql.conf задаем параметры:

listen_addresses = '*'
krb_server_keyfile = '/var/lib/pgsql/krb/pgpass.keytab'

а в файле pg_hba.conf пишем:

host  all all 0.0.0.0/0 gss include_realm=0 krb_realm=KRB.LOCAL

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

5. Перечитываем настройки PostgreSQL:

# service postgresql-9.4 reload

6. Пользователи с именами аналогичными тем, что в домене должены обязательно присутствовать в PostgreSQL или же Вам потребуется делать map, используя файл pg_ident.conf, но я на этом останавливаться не буду, а просто создам пользователя в PostgreSQL как в домене, например, administrator.

postgres=# CREATE ROLE administrator LOGIN ;

И если Вы нигде ничего не напутали, то под этим пользователем можно подключаться к серверу с любой машины, которая есть в Вашем домене:

$ whoami
administrator
 
$ klist 
Ticket cache: FILE:/tmp/krb5cc_500
Default principal: administrator@KRB.LOCAL
 
Valid starting     Expires            Service principal
03/02/16 18:09:22  03/03/16 04:09:22  krbtgt/KRB.LOCAL@KRB.LOCAL
        renew until 03/03/16 18:09:16
 
$ psql -h srv.krb.local postgres
psql (9.4.6)
Введите "help", чтобы получить справку.
 
postgres=> \conninfo
Вы подключены к базе данных "postgres" как пользователь "administrator" (сервер "srv.krb.local", порт "5432").
postgres=>

В общем это все.

PGConf 2016

PostgreSQLС 3 по 5 февраля в Москве на площадке Известия Холл проходила конференция PGConf 2016.

Были различные доклады и мастер-классы, однако я хочу отметить один доклад который мне особенно понравился про резервное копирование.

Автор доклада Michael Paquier рассказал про свою разработку менеджера резервного копирования кластера PostgreSQL.

Попробую кратко описать суть, как её понял я:

Сначала делается полная резервная копия файлов кластера как это делается обычно  pg_start_backup && tar -zcf /path/to/backup.tar.gz  $PGDATA && pg_stop_backup. При этом, запоминается номер транзакции или другая отметка этого момента.

Затем при повтором запуске оно делает не полную копию как в первый раз, а с момента отметки прошлой вычитывает номера страничек из WAL которые были изменены и копирует только их.

Все это обернуто в довольно удобную утилитку pg_arman  для сохранения и восстановления. Автор выкладывает все в GitHub. Для боевого использования оно пока не годится, зато потестировать можно.