Работа с PostgreSQL: настройка и
масштабирование
А. Ю. Васильев aka leopard
Creative Commons Attribution-Noncommercial 4.0 International
2017
Оглавление
Оглавление 1
1 Введение 2
1.1 Что такое PostgreSQL? . . . . . . . . . . . . . . . . . . . . . 2
2 Настройка производительности 4
2.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Настройка сервера . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Диски и файловые системы . . . . . . . . . . . . . . . . . . . 17
2.4 Утилиты для тюнинга PostgreSQL . . . . . . . . . . . . . . . 18
2.5 Оптимизация БД и приложения . . . . . . . . . . . . . . . . 22
2.6 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3 Индексы 34
3.1 Типы индексов . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.2 Возможности индексов . . . . . . . . . . . . . . . . . . . . . 42
4 Партиционирование 44
4.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.2 Теория . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.3 Практика использования . . . . . . . . . . . . . . . . . . . . 46
4.4 Pg_partman . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.5 Pgslice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.6 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5 Репликация 63
5.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.2 Потоковая репликация (Streaming Replication) . . . . . . . . 65
5.3 PostgreSQL Bi-Directional Replication (BDR) . . . . . . . . . 78
5.4 Pglogical . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.5 Slony-I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
1
Оглавление
5.6 Londiste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.7 Bucardo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.8 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6 Шардинг 117
6.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.2 PL/Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.3 Postgres-X2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.4 Postgres-XL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.5 Citus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.6 Greenplum Database . . . . . . . . . . . . . . . . . . . . . . . 143
6.7 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7 PgPool-II 158
7.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.2 Установка и настройка . . . . . . . . . . . . . . . . . . . . . 159
7.3 Настройка репликации . . . . . . . . . . . . . . . . . . . . . 161
7.4 Параллельное выполнение запросов . . . . . . . . . . . . . . 163
7.5 Master-slave режим . . . . . . . . . . . . . . . . . . . . . . . . 169
7.6 Онлайн восстановление . . . . . . . . . . . . . . . . . . . . . 170
7.7 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
8 Мультиплексоры соединений 174
8.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
8.2 PgBouncer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
8.3 PgPool-II vs PgBouncer . . . . . . . . . . . . . . . . . . . . . 176
9 Кэширование в PostgreSQL 177
9.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
9.2 Pgmemcache . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
9.3 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
10 Расширения 185
10.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
10.2 PostGIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
10.3 pgSphere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
10.4 HStore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
10.5 PLV8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
10.6 Pg_repack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
10.7 Pg_prewarm . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
10.8 Smlar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
10.9 Multicorn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
10.10 Pgaudit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
10.11 Ltree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
10.12 PostPic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
2
Оглавление
10.13 Fuzzystrmatch . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
10.14 Pg_trgm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
10.15 Cstore_fdw . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
10.16 Postgresql-hll . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
10.17 Tsearch2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
10.18 PL/Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
10.19 Texcaller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
10.20 Pgmemcache . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
10.21 Prefix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
10.22 Dblink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
10.23 Postgres_fdw . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
10.24 Pg_cron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
10.25 PGStrom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
10.26 ZomboDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
10.27 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
11 Бэкап и восстановление PostgreSQL 259
11.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
11.2 SQL бэкап . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
11.3 Бэкап уровня файловой системы . . . . . . . . . . . . . . . . 262
11.4 Непрерывное резервное копирование . . . . . . . . . . . . . 263
11.5 Утилиты для непрерывного резервного копирования . . . . 265
11.6 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
12 Стратегии масштабирования для PostgreSQL 281
12.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
12.2 Проблема чтения данных . . . . . . . . . . . . . . . . . . . . 282
12.3 Проблема записи данных . . . . . . . . . . . . . . . . . . . . 283
12.4 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
13 Утилиты для PostgreSQL 284
13.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
13.2 Pgcli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
13.3 Pgloader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.4 Postgres.app . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.5 pgAdmin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.6 PostgREST . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.7 Ngx_postgres . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.8 Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
14 Полезные мелочи 287
14.1 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
14.2 Мелочи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
Литература 298
3
1
Введение
Послушайте и Вы забудете,
посмотрите и Вы
запомните, сделайте и Вы
поймете
Конфуций
Данная книга не дает ответы на все вопросы по работе с PostgreSQL.
Главное её задание показать возможности PostgreSQL, методики на-
стройки и масштабируемости этой СУБД. В любом случае, выбор метода
решения поставленной задачи остается за разработчиком или администра-
тором СУБД.
1.1 Что такое PostgreSQL?
PostgreSQL (произносится «Пост-Грес-Кью-Эль») свободная
объектно-реляционная система управления базами данных (СУБД).
PostgreSQL ведёт свою «родословную» от некоммерческой СУБД
Postgres, разработанной, как и многие open-source проекты, в Калифор-
нийском университете в Беркли. К разработке Postgres, начавшейся в 1986
году, имел непосредственное отношение Майкл Стоунбрейкер, руководи-
тель более раннего проекта Ingres, на тот момент уже приобретённого ком-
панией Computer Associates. Само название «Postgres» расшифровывалось
как «Post Ingres», соответственно, при создании Postgres были применены
многие уже ранее сделанные наработки.
Стоунбрейкер и его студенты разрабатывали новую СУБД в течение
восьми лет с 1986 по 1994 год. За этот период в синтаксис были введены
процедуры, правила, пользовательские типы и многие другие компоненты.
Работа не прошла даром в 1995 году разработка снова разделилась: Сто-
унбрейкер использовал полученный опыт в создании коммерческой СУБД
4
1.1. Что такое PostgreSQL?
Illustra, продвигаемой его собственной одноимённой компанией (приобре-
тённой впоследствии компанией Informix), а его студенты разработали но-
вую версию Postgres Postgres95, в которой язык запросов POSTQUEL
наследие Ingres был заменен на SQL.
В этот момент разработка Postgres95 была выведена за пределы уни-
верситета и передана команде энтузиастов. С этого момента СУБД полу-
чила имя, под которым она известна и развивается в текущий момент
PostgreSQL.
На данный момент, в PostgreSQL имеются следующие ограничения:
Максимальный размер базы данных Нет ограничений
Максимальный размер таблицы 32 Тбайт
Максимальный размер записи 1,6 Тбайт
Максимальный размер поля 1 Гбайт
Максимум записей в таблице Нет ограничений
Максимум полей в записи 250—1600, в зависимости
от типов полей
Максимум индексов в таблице Нет ограничений
Согласно результатам автоматизированного исследования различного
ПО на предмет ошибок, в исходном коде PostgreSQL было найдено 20
проблемных мест на 775000 строк исходного кода среднем, одна ошибка
на 39000 строк кода). Для сравнения: MySQL 97 проблем, одна ошибка
на 4000 строк кода; FreeBSD (целиком) 306 проблем, одна ошибка на
4000 строк кода; Linux (только ядро) 950 проблем, одна ошибка на 10
000 строк кода.
5
2
Настройка производительности
Теперь я знаю тысячу
способов, как не нужно
делать лампу накаливания
Томас Алва Эдисон
2.1 Введение
Скорость работы, вообще говоря, не является основной причиной ис-
пользования реляционных СУБД. Более того, первые реляционные базы
работали медленнее своих предшественников. Выбор этой технологии был
вызван скорее:
возможностью возложить поддержку целостности данных на СУБД;
независимостью логической структуры данных от физической;
Эти особенности позволяют сильно упростить написание приложений,
но требуют для своей реализации дополнительных ресурсов.
Таким образом, прежде чем искать ответ на вопрос «как заставить
РСУБД работать быстрее в моей задаче?», следует ответить на вопрос
«нет ли более подходящего средства для решения моей задачи, чем
РСУБД?» Иногда использование другого средства потребует меньше уси-
лий, чем настройка производительности.
Данная глава посвящена возможностям повышения производитель-
ности PostgreSQL. Глава не претендует на исчерпывающее изложение
вопроса, наиболее полным и точным руководством по использованию
PostgreSQL является, конечно, официальная документация и официаль-
ный FAQ. Также существует англоязычный список рассылки postgresql-
performance, посвящённый именно этим вопросам. Глава состоит из двух
разделов, первый из которых ориентирован скорее на администратора,
6
2.1. Введение
второй на разработчика приложений. Рекомендуется прочесть оба раз-
дела: отнесение многих вопросов к какому-то одному из них весьма услов-
но.
Не используйте настройки по умолчанию
По умолчанию PostgreSQL сконфигурирован таким образом, чтобы он
мог быть запущен практически на любом компьютере и не слишком ме-
шал при этом работе других приложений. Это особенно касается исполь-
зуемой памяти. Настройки по умолчанию подходят только для следующе-
го использования: с ними вы сможете проверить, работает ли установка
PostgreSQL, создать тестовую базу уровня записной книжки и потрени-
роваться писать к ней запросы. Если вы собираетесь разрабатывать
тем более запускать в работу) реальные приложения, то настройки при-
дётся радикально изменить. В дистрибутиве PostgreSQL, к сожалению,
не поставляются файлы с «рекомендуемыми» настройками. Вообще го-
воря, такие файлы создать весьма сложно, т.к. оптимальные настройки
конкретной установки PostgreSQL будут определяться:
конфигурацией компьютера;
объёмом и типом данных, хранящихся в базе;
отношением числа запросов на чтение и на запись;
тем, запущены ли другие требовательные к ресурсам процессы (на-
пример, веб-сервер);
Используйте актуальную версию сервера
Если у вас стоит устаревшая версия PostgreSQL, то наибольшего уско-
рения работы вы сможете добиться, обновив её до текущей. Укажем лишь
наиболее значительные из связанных с производительностью изменений.
В версии 7.4 была ускорена работа многих сложных запросов (вклю-
чая печально известные подзапросы IN/NOT IN);
В версии 8.0 были внедрены метки восстановления, улучшение
управления буфером, CHECKPOINT и VACUUM улучшены;
В версии 8.1 был улучшен одновременный доступ к разделяемой па-
мяти, автоматическое использование индексов для MIN() и MAX(),
pg_autovacuum внедрен в сервер (автоматизирован), повышение
производительности для секционированных таблиц;
В версии 8.2 была улучшена скорость множества SQL запросов, усо-
вершенствован сам язык запросов;
В версии 8.3 внедрен полнотекстовый поиск, поддержка SQL/XML
стандарта, параметры конфигурации сервера могут быть установле-
ны на основе отдельных функций;
7
2.1. Введение
В версии 8.4 были внедрены общие табличные выражения, рекур-
сивные запросы, параллельное восстановление, улучшена произво-
дительность для EXISTS/NOT EXISTS запросов;
В версии 9.0 «асинхронная репликация из коробки»,
VACUUM/VACUUM FULL стали быстрее, расширены хранимые
процедуры;
В версии 9.1 «синхронная репликация из коробки», нелогируемые
таблицы (очень быстрые на запись, но при падении БД данные мо-
гут пропасть), новые типы индексов, наследование таблиц в запросах
теперь может вернуться многозначительно отсортированные резуль-
таты, позволяющие оптимизации MIN/MAX;
В версии 9.2 «каскадная репликация из коробки», сканирование по
индексу, JSON тип данных, типы данных на диапазоны, сортировка
в памяти улучшена на 25%, ускорена команда COPY;
В версии 9.3 materialized view, доступные на запись внешние табли-
цы, переход с использования SysV shared memory на POSIX shared
memory и mmap, cокращено время распространения реплик, а так-
же значительно ускорена передача управления от запасного сервера
к первичному, увеличена производительность и улучшена система
блокировок для внешних ключей;
В версии 9.4 появился новый тип поля JSONB (бинарный JSON с
поддержкой индексов), логическое декодирование для репликации,
GIN индекс в 2 раза меньше по размеру и в 3 раза быстрее, неблоки-
руюшие обновление materialized view, поддержка Linux Huge Pages;
В версии 9.5 добавлена поддержка UPSERT, Row Level Security,
CUBE, ROLLUP, GROUPING SETS функции, TABLESAMPLE,
BRIN индекс, ускорена скорость работы индексов для текстовых и
цифровых полей;
В версии 9.6 добавлена поддержка параллелизации некоторых за-
просов, что позволяет использование несколько ядер (CPU core) на
сервере, чтобы возвращать результаты запроса быстрее, полнотек-
стовый поиск поддерживает фразы, новая опция «remote_apply» для
синхронной репликации, которая позволяет дождаться, пока запрос
завершится на слейве;
Следует также отметить, что большая часть изложенного в статье ма-
териала относится к версии сервера не ниже 9.0.
Стоит ли доверять тестам производительности
Перед тем, как заниматься настройкой сервера, вполне естественно
ознакомиться с опубликованными данными по производительности, в том
числе в сравнении с другими СУБД. К сожалению, многие тесты слу-
жат не столько для облегчения вашего выбора, сколько для продвижения
8
2.2. Настройка сервера
конкретных продуктов в качестве «самых быстрых». При изучении опуб-
ликованных тестов в первую очередь обратите внимание, соответствует
ли величина и тип нагрузки, объём данных и сложность запросов в те-
сте тому, что вы собираетесь делать с базой? Пусть, например, обычное
использование вашего приложения подразумевает несколько одновремен-
но работающих запросов на обновление к таблице в миллионы записей. В
этом случае СУБД, которая в несколько раз быстрее всех остальных ищет
запись в таблице в тысячу записей, может оказаться не лучшим выбором.
Ну и наконец, вещи, которые должны сразу насторожить:
Тестирование устаревшей версии СУБД;
Использование настроек по умолчанию (или отсутствие информации
о настройках);
Тестирование в однопользовательском режиме (если, конечно, вы не
предполагаете использовать СУБД именно так);
Использование расширенных возможностей одной СУБД при игно-
рировании расширенных возможностей другой;
Использование заведомо медленно работающих запросов (раздел
«2.5 Оптимизация конкретных запросов»);
2.2 Настройка сервера
В этом разделе описаны рекомендуемые значения параметров, влияю-
щих на производительность СУБД. Эти параметры обычно устанавлива-
ются в конфигурационном файле postgresql.conf и влияют на все базы в
текущей установке.
Используемая память
Общий буфер сервера: shared_buffers
PostgreSQL не читает данные напрямую с диска и не пишет их сразу
на диск. Данные загружаются в общий буфер сервера, находящийся в
разделяемой памяти, серверные процессы читают и пишут блоки в этом
буфере, а затем уже изменения сбрасываются на диск.
Если процессу нужен доступ к таблице, то он сначала ищет нужные
блоки в общем буфере. Если блоки присутствуют, то он может продол-
жать работу, если нет делается системный вызов для их загрузки. За-
гружаться блоки могут как из файлового кэша ОС, так и с диска, и эта
операция может оказаться весьма «дорогой».
Если объём буфера недостаточен для хранения часто используемых
рабочих данных, то они будут постоянно писаться и читаться из кэша ОС
или с диска, что крайне отрицательно скажется на производительности.
9
2.2. Настройка сервера
В то же время не следует устанавливать это значение слишком боль-
шим: это НЕ вся память, которая нужна для работы PostgreSQL, это толь-
ко размер разделяемой между процессами PostgreSQL памяти, которая
нужна для выполнения активных операций. Она должна занимать мень-
шую часть оперативной памяти вашего компьютера, так как PostgreSQL
полагается на то, что операционная система кэширует файлы, и не стара-
ется дублировать эту работу. Кроме того, чем больше памяти будет отдано
под буфер, тем меньше останется операционной системе и другим прило-
жениям, что может привести к своппингу.
К сожалению, чтобы знать точное число shared_buffers, нужно учесть
количество оперативной памяти компьютера, размер базы данных, число
соединений и сложность запросов, так что лучше воспользуемся несколь-
кими простыми правилами настройки.
На выделенных серверах полезным объемом для shared_buffers будет
значение 1/4 памяти в системе. Если у вас большие активные порции ба-
зы данных, сложные запросы, большое число одновременных соединений,
длительные транзакции, вам доступен большой объем оперативной па-
мяти или большее количество процессоров, то можно подымать это зна-
чение и мониторить результат, чтобы не привести к «деградации» (па-
дению) производительности. Выделив слишком много памяти для базы
данных, мы можем получить ухудшение производительности, посколь-
ку PostgreSQL также использует кэш операционной системы (увеличение
данного параметра более 40% оперативной памяти может давать «нуле-
вой» прирост производительности).
Для тонкой настройки параметра установите для него большое значе-
ние и потестируйте базу при обычной нагрузке. Проверяйте использование
разделяемой памяти при помощи ipcs или других утилит(например, free
или vmstat). Рекомендуемое значение параметра будет примерно в 1,2 –2
раза больше, чем максимум использованной памяти. Обратите внимание,
что память под буфер выделяется при запуске сервера, и её объём при
работе не изменяется. Учтите также, что настройки ядра операционной
системы могут не дать вам выделить большой объём памяти (для версии
PostgreSQL < 9.3). В руководстве администратора PostgreSQL описано,
как можно изменить эти настройки.
Также следует помнить, что на 32 битной системе (Linux) каждый про-
цесс лимитирован в 4 ГБ адресного пространства, где хотя бы 1 ГБ за-
резервирован ядром. Это означает, что не зависимо, сколько на машине
памяти, каждый PostgreSQL инстанс сможет обратиться максимум к 3 ГБ
памяти. А значит максимум для shared_buffers в такой системе 2–2.5 ГБ.
Хочу обратить внимание, что на Windows, большие значения для
shared_buffers не столь эффективны, как на Linux системах, и в результате
лучшие результаты можно будет получить, если держать это значение от-
носительно небольшое (от 64 МБ до 512 МБ) и использовать кэш системы
вместо него.
10
2.2. Настройка сервера
Память для сортировки результата запроса: work_mem
work_mem параметр определяет максимальное количество оператив-
ной памяти, которое может выделить одна операция сортировки, агрега-
ции и др. Это не разделяемая память, work_mem выделяется отдельно
на каждую операцию (от одного до нескольких раз за один запрос). Ра-
зумное значение параметра определяется следующим образом: количество
доступной оперативной памяти (после того, как из общего объема вычли
память, требуемую для других приложений, и shared_buffers) делится на
максимальное число одновременных запросов умноженное на среднее чис-
ло операций в запросе, которые требуют памяти.
Если объём памяти недостаточен для сортировки некоторого результа-
та, то серверный процесс будет использовать временные файлы. Если же
объём памяти слишком велик, то это может привести к своппингу.
Объём памяти задаётся параметром work_mem в файле postgresql.conf.
Единица измерения параметра 1 кБ. Значение по умолчанию 1024. В
качестве начального значения для параметра можете взять 2–4% доступ-
ной памяти. Для веб-приложений обычно устанавливают низкие значения
work_mem, так как запросов обычно много, но они простые, обычно хва-
тает от 512 до 2048 КБ. С другой стороны, приложения для поддержки
принятия решений с сотнями строк в каждом запросе и десятками мил-
лионов столбцов в таблицах фактов часто требуют work_mem порядка 500
МБ. Для баз данных, которые используются и так, и так, этот параметр
можно устанавливать для каждого запроса индивидуально, используя на-
стройки сессии. Например, при памяти 1–4 ГБ рекомендуется устанавли-
вать 32–128 MB.
Максимальное количество клиентов: max_connections
Параметр max_connections устанавливает максимальное количество
клиентов, которые могут подключиться к PostgreSQL. Поскольку для
каждого клиента требуется выделять память (work_mem), то этот пара-
метр предполагает максимально возможное использование памяти для
всех клиентов. Как правило, PostgreSQL может поддерживать несколько
сотен подключений, но создание нового является дорогостоящей операци-
ей. Поэтому, если требуются тысячи подключений, то лучше использовать
пул подключений (отдельная программа или библиотека для продукта,
что использует базу).
Память для работы команды VACUUM: maintenance_work_mem
Этот параметр задаёт объём памяти, используемый командами
VACUUM, ANALYZE, CREATE INDEX, и добавления внешних ключей. Что-
бы операции выполнялись максимально быстро, нужно устанавливать
этот параметр тем выше, чем больше размер таблиц в вашей базе дан-
11
2.2. Настройка сервера
ных. Неплохо бы устанавливать его значение от 50 до 75% размера вашей
самой большой таблицы или индекса или, если точно определить невоз-
можно, от 32 до 256 МБ. Следует устанавливать большее значение, чем для
work_mem. Слишком большие значения приведут к использованию свопа.
Например, при памяти 1–4 ГБ рекомендуется устанавливать 128–512 MB.
Большие страницы: huge_pages
В PostgreSQL, начиная с версии 9.4, появилась поддержка больших
страниц. В ОС Linux работа с памятью основывается на обращении к стра-
ницам размер которых равен 4kB (на самом деле зависит от платформы,
проверить можно через getconf PAGE_SIZE). Так вот, когда объем памя-
ти переваливает за несколько десятков, а то и сотни гигабайт, управлять
ею становится сложнее, увеличиваются накладные расходы на адресацию
памяти и поддержание страничных таблиц. Для облегчения жизни и бы-
ли придуманы большие страницы, размер которых может быть 2MB, а то
и 1GB. За счет использования больших страниц можно получить ощути-
мый прирост скорости работы и увеличение отзывчивости в приложениях
которые активно работают с памятью.
Вообще запустить PostgreSQL с поддержкой больших страниц мож-
но было и раньше, с помощью libhugetlbfs. Однако теперь есть встроен-
ная поддержка. Итак, ниже описание процесса как настроить и запустить
PostgreSQL с поддержкой больших страниц.
Для начала следует убедиться, что ядро поддерживает боль-
шие страницы. Проверяем конфиг ядра на предмет наличия опций
CONFIG_HUGETLBFS и CONFIG_HUGETLB_PAGE.
Листинг 2.1 Проверка конфига ядра на поддержку huge pages
Line 1 $ grep HUGETLB /boot / c onf i g - $ (uname - r )
- CONFIG_CGROUP_HUGETLB=y
- CONFIG_HUGETLBFS=y
- CONFIG_HUGETLB_PAGE=y
В случае отсутствия этих опций, ничего не заработает и ядро следует
пересобрать.
Очевидно, что нам понадобится PostgreSQL версии не ниже 9.4. За
поддержку больших страниц отвечает параметр huge_page, который может
принимать три значения: off не использовать большие страницы, on
использовать большие страницы, try попытаться использовать большие
страницы и в случае недоступности откатиться на использование обычных
страниц. Значение try используется по умолчанию и является безопасным
вариантом. В случае on, PostgreSQL не запустится, если большие страницы
не определены в системе (или их недостаточно).
После перезапуска базы с параметром try потребуется включить под-
держку больших страниц в системе (по умолчанию они не задействованы).
12
2.2. Настройка сервера
Расчет страниц приблизительный и здесь следует опираться на то, сколь-
ко памяти вы готовы выделить под нужды СУБД. Отмечу, что значение
измеряется в страницах размером 2Mb, если вы хотите выделить 16GB,
то это будет 8000 страниц.
Официальная документация предлагает опираться на значение VmPeak
из status файла, который размещен в /proc/PID/ директории, соответ-
ствующей номеру процесса postmaster. VmPeak как следует из названия
это пиковое значение использования виртуальной памяти. Этот вариант
позволяет определить минимальную планку, от которой следует отталки-
ваться, но на мой взгляд такой способ определения тоже носит случайный
характер.
Листинг 2.2 Включаем поддержку huge pages в системе
Line 1 $ head -1 / var / l i b / pg sql / 9. 5 / data / pos tmast er . pid
- 3076
- $ grep ^VmPeak / proc /3076/ s t a t u s
- VmPeak : 4742563 kB
5 $ echo $ ((4742 563 / 2048 + 1) )
- 2316
- $ echo vm. nr_hugepages = 2316 >> / et c / s y s c t l . d/30 -
p o s t g r e s q l . con f
- $ s y s c t l -p - - system
В ОС Linux есть также система по менеджменту памяти под названи-
ем «Transparent HugePages», которая включена по умолчанию. Она может
вызывать проблему при работе с huge pages для PostgreSQL, поэтому ре-
комендуется выключать этот механизм:
Листинг 2.3 Отключаем Transparent HugePages
Line 1 $ echo ne ver > / sys / k er n e l /mm/ transparent_hugepage / d ef r ag
- $ echo never > / s y s / k e r n e l /mm/ transparent_hugepage / ena ble d
После этого перезапускаем PostgreSQL и смотрим использование боль-
ших страниц:
Листинг 2.4 Проверяем использование huge pages
Line 1 $ grep ^HugePages / proc /meminfo
- HugePages_Total : 2316
- HugePages_Free : 2301
- HugePages_Rsvd : 128
5 HugePages_Surp : 0
13
2.2. Настройка сервера
Прочие настройки
temp_buffers буфер под временные объекты, в основном для вре-
менных таблиц. Можно установить порядка 16 МБ;
max_prepared_transactions количество одновременно подготавлива-
емых транзакций (PREPARE TRANSACTION). Можно оставить по
умолчанию 5;
vacuum_cost_delay если у вас большие таблицы, и производит-
ся много одновременных операций записи, вам может пригодиться
функция, которая уменьшает затраты на I/O для VACUUM, растяги-
вая его по времени. Чтобы включить эту функциональность, нужно
поднять значение vacuum_cost_delay выше 0. Используйте разумную
задержку от 50 до 200 мс. Для более тонкой настройки повышайте
vacuum_cost_page_hit и понижайте vacuum_cost_limit. Это ослабит
влияние VACUUM, увеличив время его выполнения. В тестах с па-
раллельными транзакциями Ян Вик (Jan Wieck) получил, что при
значениях delay 200, page_hit 6 и limit —100 влияние VACUUM
уменьшилось более чем на 80%, но его длительность увеличилась
втрое;
max_stack_depth cпециальный стек для сервера, который в идеале
должен совпадать с размером стека, выставленном в ядре ОС. Уста-
новка большего значения, чем в ядре, может привести к ошибкам.
Рекомендуется устанавливать 2–4 MB;
max_files_per_process максимальное количество файлов, открыва-
емых процессом и его подпроцессами в один момент времени. Умень-
шите данный параметр, если в процессе работы наблюдается сооб-
щение «Too many open files»;
Журнал транзакций и контрольные точки
Для обеспечения отказоустойчивости СУБД PostgreSQL, как и многие
базы данных, использует специальный журнал, в котором ведет историю
изменения данных. Перед тем как записать данные в файлы БД, сервер
PostgreSQL аккумулирует изменения в оперативной памяти и записывает
в последовательный файл журнала, чтобы не потерять их из-за непредви-
денного отключения питания.
Данные в журнал пишутся до того как пользователь базы данных по-
лучит сообщение об успешном применении изменений. Этот журнал на-
зывается журналом упреждающей записи (Write-Ahead Log или просто
WAL), а файлы журнала хранятся в каталоге pg_xlog. Также периоди-
чески PostgreSQL сбрасывает измененные аккумулированные данные из
оперативной памяти на диск. Этот процесс согласования данных назы-
вается контрольной точкой (checkpoint). Контрольная точка выполняется
также при каждом штатном выключении PostgreSQL.
14
2.2. Настройка сервера
В этом случае нет необходимости сбрасывать на диск изменения дан-
ных при каждом успешном завершении транзакции: в случае сбоя БД
может быть восстановлена по записям в журнале. Таким образом, данные
из буферов сбрасываются на диск при проходе контрольной точки: либо
при заполнении нескольких (параметр checkpoint_segments, по умолчанию
3) сегментов журнала транзакций, либо через определённый интервал вре-
мени (параметр checkpoint_timeout, измеряется в секундах, по умолчанию
300).
Изменение этих параметров прямо не повлияет на скорость чтения, но
может принести большую пользу, если данные в базе активно изменяются.
Уменьшение количества контрольных точек: checkpoint_segments
Если в базу заносятся большие объёмы данных, то контрольные точ-
ки могут происходить слишком часто («слишком часто» можно опре-
делить как «чаще раза в минуту». Вы также можете задать параметр
checkpoint_warning секундах): в журнал сервера будут писаться преду-
преждения, если контрольные точки происходят чаще заданного). При
этом производительность упадёт из-за постоянного сбрасывания на диск
данных из буфера.
Для увеличения интервала между контрольными точками нужно
увеличить количество сегментов журнала транзакций через параметр
checkpoint_segments. Данный параметр определяет количество сегментов
аждый по 16 МБ) лога транзакций между контрольными точками. Этот
параметр не имеет особого значения для базы данных, предназначенной
преимущественно для чтения, но для баз данных со множеством тран-
закций увеличение этого параметра может оказаться жизненно необходи-
мым. В зависимости от объема данных установите этот параметр в диа-
пазоне от 12 до 256 сегментов и, если в логе появляются предупреждения
(warning) о том, что контрольные точки происходят слишком часто, по-
степенно увеличивайте его. Место, требуемое на диске, вычисляется по
формуле (checkpoint_segments * (2 + checkpoint_completion_target) + 1) * 16
МБ, так что убедитесь, что у вас достаточно свободного места. Например,
если вы выставите значение 32, вам потребуется больше 1 ГБ дискового
пространства.
Следует также отметить, что чем больше интервал между контроль-
ными точками, тем дольше будут восстанавливаться данные по журналу
транзакций после сбоя.
Начиная с версии 9.5 checkpoint_segments был заменен на параметры
min_wal_size и max_wal_size. Теперь система может автоматически сама
решать сколько checkpoint_segments требуется хранить (вычислять по ра-
нее приведенной формуле от указанного размера). Преимуществом этого
является то, что вы можете установить max_wal_size очень большим, но
система не будет на самом деле потреблять указанное количество места
на жестком диске, если в этом нет никакой необходимости. min_wal_size
15
2.2. Настройка сервера
устанавливает минимальный размер места, который будет использовать-
ся сегментами (можно отключить такую автонастройку, установив для
min_wal_size и max_wal_size одинаковое значение).
fsync, synchronous_commit и стоит ли их трогать
Для увеличения производительности наиболее радикальное из возмож-
ных решений выставить значение «off» параметру fsync. При этом запи-
си в журнале транзакций не будут принудительно сбрасываться на диск,
что даст большой прирост скорости записи. Учтите: вы жертвуете надёж-
ностью, в случае сбоя целостность базы будет нарушена, и её придётся
восстанавливать из резервной копии! Использовать этот параметр реко-
мендуется лишь в том случае, если вы всецело доверяете своему «железу»
и своему источнику бесперебойного питания. Ну или если данные в базе
не представляют для вас особой ценности.
Параметр synchronous_commit определяет нужно ли ждать WAL запи-
си на диск перед возвратом успешного завершения транзакции для под-
ключенного клиента. По умолчанию и для безопасности данный параметр
установлен в «on» (включен). При выключении данного параметра («off»)
может существовать задержка между моментом, когда клиенту будет со-
общено об успехе транзакции и когда та самая транзакция действительно
гарантированно и безопасно записана на диск (максимальная задержка
wal_writer_delay * 3). В отличие от fsync, отключение этого параметра не
создает риск краха базы данных: данные могут быть потеряны (послед-
ний набор транзакций), но базу данных не придется восстанавливать после
сбоя из бэкапа. Так что synchronous_commit может быть полезной альтер-
нативой, когда производительность важнее, чем точная уверенность в со-
гласовании данных (данный режим можно назвать «режимом MongoDB»:
изначально все клиенты для MongoDB не проверяли успешность записи
данных в базу и за счет этого достигалась хорошая скорость для бенчмар-
ков).
Прочие настройки
commit_delay микросекундах, 0 по умолчанию) и commit_siblings
(5 по умолчанию) определяют задержку между попаданием запи-
си в буфер журнала транзакций и сбросом её на диск. Если при
успешном завершении транзакции активно не менее commit_siblings
транзакций, то запись будет задержана на время commit_delay. Если
за это время завершится другая транзакция, то их изменения будут
сброшены на диск вместе, при помощи одного системного вызова.
Эти параметры позволят ускорить работу, если параллельно выпол-
няется много «мелких» транзакций;
16
2.2. Настройка сервера
wal_sync_method Метод, который используется для принудительной
записи данных на диск. Если fsync=off, то этот параметр не исполь-
зуется. Возможные значения:
open_datasync запись данных методом open() с параметром
O_DSYNC;
fdatasync вызов метода fdatasync() после каждого commit;
fsync_writethrough вызов fsync() после каждого commit, игно-
рируя параллельные процессы;
fsync вызов fsync() после каждого commit;
open_sync запись данных методом open() с параметром
O_SYNC;
Не все эти методы доступны на разных ОС. По умолчанию устанав-
ливается первый, который доступен для системы;
full_page_writes Установите данный параметр в «off», если fsync=off.
Иначе, когда этот параметр «on», PostgreSQL записывает содержи-
мое каждой записи в журнал транзакций при первой модификации
таблицы. Это необходимо, поскольку данные могут записаться лишь
частично, если в ходе процесса «упала» ОС. Это приведет к тому, что
на диске окажутся новые данные смешанные со старыми. Строкового
уровня записи в журнал транзакций может быть недостаточно, что-
бы полностью восстановить данные после «падения». full_page_writes
гарантирует корректное восстановление, ценой увеличения записы-
ваемых данных в журнал транзакций (Единственный способ сниже-
ния объема записи в журнал транзакций заключается в увеличении
checkpoint_interval);
wal_buffers Количество памяти, используемое в Shared Memory для
ведения транзакционных логов (буфер находится в разделяемой па-
мяти и является общим для всех процессов). Стоит увеличить буфер
до 256–512 кБ, что позволит лучше работать с большими транзакци-
ями. Например, при доступной памяти 1–4 ГБ рекомендуется уста-
навливать 256–1024 КБ;
Планировщик запросов
Следующие настройки помогают планировщику запросов правильно
оценивать стоимости различных операций и выбирать оптимальный план
выполнения запроса. Существуют 3 настройки планировщика, на которые
стоит обратить внимание:
default_statistics_target этот параметр задаёт объём статистики, со-
бираемой командой ANALYZE. Увеличение параметра заставит эту
команду работать дольше, но может позволить оптимизатору стро-
ить более быстрые планы, используя полученные дополнительные
данные. Объём статистики для конкретного поля может быть задан
командой ALTER TABLE ... SET STATISTICS;
17
2.2. Настройка сервера
effective_cache_size этот параметр сообщает PostgreSQL примерный
объём файлового кэша операционной системы, оптимизатор исполь-
зует эту оценку для построения плана запроса (указывает планиров-
щику на размер самого большого объекта в базе данных, который
теоретически может быть закеширован). Пусть в вашем компьютере
1.5 ГБ памяти, параметр shared_buffers установлен в 32 МБ, а па-
раметр effective_cache_size в 800 МБ. Если запросу нужно 700 МБ
данных, то PostgreSQL оценит, что все нужные данные уже есть в
памяти и выберет более агрессивный план с использованием индек-
сов и merge joins. Но если effective_cache_size будет всего 200 МБ, то
оптимизатор вполне может выбрать более эффективный для диско-
вой системы план, включающий полный просмотр таблицы.
На выделенном сервере имеет смысл выставлять effective_cache_size в
2/3 от всей оперативной памяти; на сервере с другими приложениями
сначала нужно вычесть из всего объема RAM размер дискового кэша
ОС и память, занятую остальными процессами;
random_page_cost — переменная, указывающая на условную стои-
мость индексного доступа к страницам данных. На серверах с быст-
рыми дисковыми массивами имеет смысл уменьшать изначальную
настройку до 3.0, 2.5 или даже до 2.0. Если же активная часть вашей
базы данных намного больше размеров оперативной памяти, попро-
буйте поднять значение параметра. Можно подойти к выбору опти-
мального значения и со стороны производительности запросов. Если
планировщик запросов чаще, чем необходимо, предпочитает после-
довательные просмотры (sequential scans) просмотрам с использова-
нием индекса (index scans), понижайте значение. И наоборот, если
планировщик выбирает просмотр по медленному индексу, когда не
должен этого делать, настройку имеет смысл увеличить. После из-
менения тщательно тестируйте результаты на максимально широком
наборе запросов. Никогда не опускайте значение random_page_cost
ниже 2.0; если вам кажется, что random_page_cost нужно еще пони-
жать, разумнее в этом случае менять настройки статистики плани-
ровщика.
Сбор статистики
У PostgreSQL также есть специальная подсистема сборщик статисти-
ки, которая в реальном времени собирает данные об активности сервера.
Поскольку сбор статистики создает дополнительные накладные расходы
на базу данных, то система может быть настроена как на сбор, так и не
сбор статистики вообще. Эта система контролируется следующими пара-
метрами, принимающими значения true/ false :
track_counts включать ли сбор статистики. По умолчанию вклю-
чён, поскольку autovacuum демону требуется сбор статистики. От-
18
2.3. Диски и файловые системы
ключайте, только если статистика вас совершенно не интересует ак
и autovacuum);
track_functions отслеживание использования определенных поль-
зователем функций;
track_activities передавать ли сборщику статистики информацию
о текущей выполняемой команде и времени начала её выполнения.
По умолчанию эта возможность включена. Следует отметить, что
эта информация будет доступна только привилегированным пользо-
вателям и пользователям, от лица которых запущены команды, так
что проблем с безопасностью быть не должно;
Данные, полученные сборщиком статистики, доступны через специаль-
ные системные представления. При установках по умолчанию собирается
очень мало информации, рекомендуется включить все возможности: до-
полнительная нагрузка будет невелика, в то время как полученные дан-
ные позволят оптимизировать использование индексов также помогут
оптимальной работе autovacuum демону).
2.3 Диски и файловые системы
Очевидно, что от качественной дисковой подсистемы в сервере БД за-
висит немалая часть производительности. Вопросы выбора и тонкой на-
стройки «железа», впрочем, не являются темой данной главы, ограничим-
ся уровнем файловой системы.
Единого мнения насчёт наиболее подходящей для PostgreSQL файло-
вой системы нет, поэтому рекомендуется использовать ту, которая лучше
всего поддерживается вашей операционной системой. При этом учтите,
что современные журналирующие файловые системы не намного медлен-
нее нежурналирующих, а выигрыш быстрое восстановление после сбо-
ев от их использования велик.
Вы легко можете получить выигрыш в производительности без побоч-
ных эффектов, если примонтируете файловую систему, содержащую базу
данных, с параметром noatime (но при этом не будет отслеживаться время
последнего доступа к файлу).
Перенос журнала транзакций на отдельный диск
При доступе к диску изрядное время занимает не только собственно
чтение данных, но и перемещение магнитной головки.
Если в вашем сервере есть несколько физических дисков (несколько
логических разделов на одном диске здесь, очевидно, не помогут: головка
всё равно будет одна), то вы можете разнести файлы базы данных и жур-
нал транзакций по разным дискам. Данные в сегменты журнала пишутся
19
2.4. Утилиты для тюнинга PostgreSQL
последовательно, более того, записи в журнале транзакций сразу сбра-
сываются на диск, поэтому в случае нахождения его на отдельном диске
магнитная головка не будет лишний раз двигаться, что позволит ускорить
запись.
Порядок действий:
Остановите сервер (!);
Перенесите каталоги pg_clog и pg_xlog, находящийся в каталоге с
базами данных, на другой диск;
Создайте на старом месте символическую ссылку;
Запустите сервер;
Примерно таким же образом можно перенести и часть файлов, содер-
жащих таблицы и индексы, на другой диск, но здесь потребуется больше
кропотливой ручной работы, а при внесении изменений в схему базы про-
цедуру, возможно, придётся повторить.
CLUSTER
CLUSTER table [ USING index ] команда для упорядочивания записей
таблицы на диске согласно индексу, что иногда за счет уменьшения досту-
па к диску ускоряет выполнение запроса. Возможно создать только один
физический порядок в таблице, поэтому и таблица может иметь только
один кластерный индекс. При таком условии нужно тщательно выбирать,
какой индекс будет использоваться для кластерного индекса.
Кластеризация по индексу позволяет сократить время поиска по дис-
ку: во время поиска по индексу выборка данных может быть значительно
быстрее, так как последовательность данных в таком же порядке, как и
индекс. Из минусов можно отметить то, что команда CLUSTER требует
«ACCESS EXCLUSIVE» блокировку, что предотвращает любые другие
операции с данными (чтения и записи) пока кластеризация не завершит
выполнение. Также кластеризация индекса в PostgreSQL не утвержда-
ет четкий порядок следования, поэтому требуется повторно выполнять
CLUSTER для поддержания таблицы в порядке.
2.4 Утилиты для тюнинга PostgreSQL
Pgtune
Для оптимизации настроек для PostgreSQL Gregory Smith создал ути-
литу pgtune в расчёте на обеспечение максимальной производительности
для заданной аппаратной конфигурации. Утилита проста в использовании
и во многих Linux системах может идти в составе пакетов. Если же нет,
можно просто скачать архив и распаковать. Для начала:
20
2.4. Утилиты для тюнинга PostgreSQL
Листинг 2.5 Pgtune
Line 1 $ pgtune - i $PGDATA/ p o s t g r e s q l . c on f - o $PGDATA/ p o s t g r e s q l .
conf . pgtune
опцией - i , --input-config указываем текущий файл postgresql.conf, а -o
, --output-config указываем имя файла для нового postgresql.conf.
Есть также дополнительные опции для настройки конфига:
-M, --memory используйте этот параметр, чтобы определить общий
объем системной памяти. Если не указано, pgtune будет пытаться
использовать текущий объем системной памяти;
-T, --type указывает тип базы данных. Опции: DW, OLTP, Web,
Mixed, Desktop;
-c, -- connections указывает максимальное количество соединений. Ес-
ли он не указан, то будет браться в зависимости от типа базы данных;
Существует также онлайн версия pgtune.
Хочется сразу добавить, что pgtune не «серебряная пуля» для опти-
мизации настройки PostgreSQL. Многие настройки зависят не только от
аппаратной конфигурации, но и от размера базы данных, числа соедине-
ний и сложности запросов, так что оптимально настроить базу данных
возможно, только учитывая все эти параметры.
pg_buffercache
Pg_buffercache расширение для PostgreSQL, которое позволяет по-
лучить представление об использовании общего буфера (shared_buffer) в
базе. Расширение позволяет взглянуть какие из данных кэширует база,
которые активно используются в запросах. Для начала нужно установить
расширение:
Листинг 2.6 pg_buffercache
Line 1 # CREATE EXTENSION pg_ buffercach e ;
Теперь доступно pg_buffercache представление, которое содержит:
bufferid ID блока в общем буфере;
relfilenode имя папки, где данные расположены;
reltablespace Oid таблицы;
reldatabase Oid базы данных;
relforknumber номер ответвления;
relblocknumber номер страницы;
isdirty грязная страница?;
usagecount количество LRU страниц;
21
2.4. Утилиты для тюнинга PostgreSQL
ID блока в общем буфере (bufferid) соответствует количеству использу-
емого буфера таблицей, индексом, прочим. Общее количество доступных
буферов определяется двумя вещами:
Размер буферного блока. Этот размер блока определяется опцией --
with-blocksize при конфигурации. Значение по умолчанию 8 КБ,
что достаточно в большинстве случаев, но его возможно увеличить
или уменьшить в зависимости от ситуации. Для того чтобы изменить
это значение, необходимо будет перекомпилировать PostgreSQL;
Размер общего буфера. Определяется опцией shared_buffers в
PostgreSQL конфиге.
Например, при использовании shared_buffers в 128 МБ с 8 КБ разме-
ра блока получится 16384 буферов. Представление pg_buffercache будет
иметь такое же число строк 16384. С shared_buffers в 256 МБ и разме-
ром блока в 1 КБ получим 262144 буферов.
Для примера рассмотрим простой запрос показывающий использова-
ние буферов объектами (таблицами, индексами, прочим):
Листинг 2.7 pg_buffercache
Line 1 # SELECT c . relname , count ( * ) AS b u f f e r s
- FROM p g_buffe rca che b INNER JOIN pg_ class c
- ON b . r e l f i l e n o d e = p g_r e la t i on _ f il e nod e ( c . oid ) AND
- b . r e l d a t a b a s e IN ( 0 , (SELECT oi d FROM pg_database WHERE
datname = current_database ( ) ) )
5 GROUP BY c . relname
- ORDER BY 2 DESC
- LIMIT 1 0;
-
- relname | b u f f e r s
10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - -
- pgbench_accounts | 4082
- pgbench_history | 53
- pg_ a t tribut e | 23
- pg_proc | 14
15 pg_operator | 11
- pg_proc_oid_index | 9
- p g_class | 8
- pg_attribute_relid_attnum_index | 7
- pg_proc_proname_args_nsp_index | 6
20 pg_class_oid_index | 5
- (10 rows )
Этот запрос показывает объекты (таблицы и индексы) в кэше:
22
2.4. Утилиты для тюнинга PostgreSQL
Листинг 2.8 pg_buffercache
Line 1 # SELECT c . relname , count ( * ) AS b u f f e r s , usa gecount
- FROM pg_clas s c
- INNER JOIN pg_bufferca che b
- ON b . r e l f i l e n o d e = c . r e l f i l e n o d e
5 INNER JOIN pg_database d
- ON ( b . r e l d a t a b a s e = d . oid AND d . datname = current_database
( ) )
- GROUP BY c . relname , usa gecount
- ORDER BY c . relname , usagecount ;
-
10 relname | b u f f e r s | usagecount
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - -
- pg_rewrite | 3 | 1
- pg_rewrite_rel_rulename_index | 1 | 1
- pg_rewrite_rel_rulename_index | 1 | 2
15 p g _ s t a t i s t i c | 1 | 1
- p g _ s t a t i s t i c | 1 | 3
- p g _ s t a t i s t i c | 2 | 5
- pg_stat i s t i c _ r e l id_att_inh_inde x | 1 | 1
- pg_stat i s t i c _ r e l id_att_inh_inde x | 3 | 5
20 pgbench_accounts | 4082 | 2
- pgbench_accounts_pkey | 1 | 1
- pgbench_history | 53 | 1
- p g b enc h _ te lle r s | 1 | 1
Это запрос показывает какой процент общего буфера используют
обьекты (таблицы и индексы) и на сколько процентов объекты находятся
в самом кэше (буфере):
Листинг 2.9 pg_buffercache
Line 1 # SELECT
- c . relname ,
- pg_size_pretty ( count ( * ) * 8192) as b u ff er ed ,
- round ( 10 0 .0 * count ( *) /
5 (SELECT s e t t i n g FROM p g _s e t t i n g s WHERE name= sh a r e d_ bu ff e rs
) : : i nt e g e r , 1 )
- AS bu ff e rs _ pe r c en t ,
- round ( 10 0 .0 * count ( *) * 8192 / p g_table _size ( c . o i d ) , 1 )
- AS per c e nt_o f _ rela t i on
- FROM pg_class c
10 INNER JOIN pg_bufferca che b
- ON b . r e l f i l e n o d e = c . r e l f i l e n o d e
- INNER JOIN pg_database d
- ON ( b . r e l d a t a b a s e = d . oid AND d . datname = current_database
( ) )
23
2.5. Оптимизация БД и приложения
- GROUP BY c . oid , c . relname
15 ORDER BY 3 DESC
- LIMIT 2 0;
-
- - [ RECORD 1 ] - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- relname | pgbench_accounts
20 b u f f e r ed | 32 MB
- b u ff e rs_ p erc e nt | 24 . 9
- p e r cent _ o f_re l a tion | 9 9 .9
- - [ RECORD 2 ] - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- relname | pgbench_history
25 b u f f e r ed | 424 kB
- b u ff e rs_ p erc e nt | 0 . 3
- p e r cent _ o f_re l a tion | 9 4 .6
- - [ RECORD 3 ] - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- relname | pg_operator
30 b u f f e r ed | 88 kB
- b u ff e rs_ p erc e nt | 0 . 1
- p e r cent _ o f_re l a tion | 6 1 .1
- - [ RECORD 4 ] - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- relname | pg_opclass_oid_index
35 b u f f e r e d | 16 kB
- b u ff e rs_ p erc e nt | 0 . 0
- p e r cent _ o f_re l a tion | 1 0 0 . 0
- - [ RECORD 5 ] - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- relname | p g _ s t a t i stic_relid_att_ i n h _ i n d e x
40 b u f f e r e d | 32 kB
- b u ff e rs_ p erc e nt | 0 . 0
- p e r cent _ o f_re l a tion | 1 0 0 . 0
Используя эти данные можно проанализировать для каких объектов
не хватает памяти или какие из них потребляют основную часть общего
буфера. На основе этого можно более правильно настраивать shared_buffers
параметр для PostgreSQL.
2.5 Оптимизация БД и приложения
Для быстрой работы каждого запроса в вашей базе в основном требу-
ется следующее:
1. Отсутствие в базе мусора, мешающего добраться до актуальных дан-
ных. Можно сформулировать две подзадачи:
a) Грамотное проектирование базы. Освещение этого вопроса вы-
ходит далеко за рамки этой книги;
b) Сборка мусора, возникающего при работе СУБД;
2. Наличие быстрых путей доступа к данным индексов;
24
2.5. Оптимизация БД и приложения
3. Возможность использования оптимизатором этих быстрых путей;
4. Обход известных проблем;
Поддержание базы в порядке
В данном разделе описаны действия, которые должны периодически
выполняться для каждой базы. От разработчика требуется только настро-
ить их автоматическое выполнение (при помощи cron) и опытным путём
подобрать оптимальную частоту.
Команда ANALYZE
Служит для обновления информации о распределении данных в таб-
лице. Эта информация используется оптимизатором для выбора наиболее
быстрого плана выполнения запроса.
Обычно команда используется в связке с VACUUM ANALYZE. Если в
базе есть таблицы, данные в которых не изменяются и не удаляются, а
лишь добавляются, то для таких таблиц можно использовать отдельную
команду ANALYZE. Также стоит использовать эту команду для отдельной
таблицы после добавления в неё большого количества записей.
Команда REINDEX
Команда REINDEX используется для перестройки существующих ин-
дексов. Использовать её имеет смысл в случае:
порчи индекса;
постоянного увеличения его размера;
Второй случай требует пояснений. Индекс, как и таблица, содержит
блоки со старыми версиями записей. PostgreSQL не всегда может заново
использовать эти блоки, и поэтому файл с индексом постепенно увеличи-
вается в размерах. Если данные в таблице часто меняются, то расти он
может весьма быстро.
Если вы заметили подобное поведение какого-то индекса, то стоит на-
строить для него периодическое выполнение команды REINDEX. Учтите:
команда REINDEX, как и VACUUM FULL, полностью блокирует таблицу,
поэтому выполнять её надо тогда, когда загрузка сервера минимальна.
Использование индексов
Опыт показывает, что наиболее значительные проблемы с производи-
тельностью вызываются отсутствием нужных индексов. Поэтому столк-
нувшись с медленным запросом, в первую очередь проверьте, существуют
ли индексы, которые он может использовать. Если нет постройте их.
Излишек индексов, впрочем, тоже чреват проблемами:
25
2.5. Оптимизация БД и приложения
Команды, изменяющие данные в таблице, должны изменить также
и индексы. Очевидно, чем больше индексов построено для таблицы,
тем медленнее это будет происходить;
Оптимизатор перебирает возможные пути выполнения запросов. Ес-
ли построено много ненужных индексов, то этот перебор будет идти
дольше;
Единственное, что можно сказать с большой степенью определённо-
сти поля, являющиеся внешними ключами, и поля, по которым объеди-
няются таблицы, индексировать надо обязательно.
Команда EXPLAIN [ANALYZE]
Команда EXPLAIN запрос[] показывает, каким образом PostgreSQL со-
бирается выполнять ваш запрос. Команда EXPLAIN ANALYZE запрос[] вы-
полняет запрос поэтому EXPLAIN ANALYZE DELETE . . . не слиш-
ком хорошая идея) и показывает как изначальный план, так и реальный
процесс его выполнения.
Чтение вывода этих команд искусство, которое приходит с опытом.
Для начала обращайте внимание на следующее:
Использование полного просмотра таблицы (seq scan);
Использование наиболее примитивного способа объединения таблиц
(nested loop);
Для EXPLAIN ANALYZE: нет ли больших отличий в предполагае-
мом количестве записей и реально выбранном? Если оптимизатор
использует устаревшую статистику, то он может выбирать не самый
быстрый план выполнения запроса;
Следует отметить, что полный просмотр таблицы далеко не всегда
медленнее просмотра по индексу. Если, например, в таблице–справочнике
несколько сотен записей, умещающихся в одном-двух блоках на диске, то
использование индекса приведёт лишь к тому, что придётся читать ещё
и пару лишних блоков индекса. Если в запросе придётся выбрать 80%
записей из большой таблицы, то полный просмотр опять же получится
быстрее.
При тестировании запросов с использованием EXPLAIN ANALYZE
можно воспользоваться настройками, запрещающими оптимизатору ис-
пользовать определённые планы выполнения. Например,
Листинг 2.10 enable_seqscan
Line 1 SET enable_seq scan=f a l s e ;
26
2.5. Оптимизация БД и приложения
запретит использование полного просмотра таблицы, и вы сможете
выяснить, прав ли был оптимизатор, отказываясь от использования ин-
декса. Ни в коем случае не следует прописывать подобные команды в
postgresql.conf! Это может ускорить выполнение нескольких запросов, но
сильно замедлит все остальные!
Использование собранной статистики
Результаты работы сборщика статистики доступны через специальные
системные представления. Наиболее интересны для наших целей следую-
щие:
pg_stat_user_tables содержит для каждой пользовательской табли-
цы в текущей базе данных общее количество полных просмотров
и просмотров с использованием индексов, общие количества запи-
сей, которые были возвращены в результате обоих типов просмотра,
а также общие количества вставленных, изменённых и удалённых
записей;
pg_stat_user_indexes содержит для каждого пользовательского ин-
декса в текущей базе данных общее количество просмотров, ис-
пользовавших этот индекс, количество прочитанных записей, коли-
чество успешно прочитанных записей в таблице (может быть меньше
предыдущего значения, если в индексе есть записи, указывающие на
устаревшие записи в таблице);
pg_statio_user_tables содержит для каждой пользовательской таб-
лицы в текущей базе данных общее количество блоков, прочитан-
ных из таблицы, количество блоков, оказавшихся при этом в буфере
(см. пункт 2.1.1), а также аналогичную статистику для всех индексов
по таблице и, возможно, по связанной с ней таблицей TOAST;
Из этих представлений можно узнать, в частности:
Для каких таблиц стоит создать новые индексы (индикатором слу-
жит большое количество полных просмотров и большое количество
прочитанных блоков);
Какие индексы вообще не используются в запросах. Их имеет смысл
удалить, если, конечно, речь не идёт об индексах, обеспечивающих
выполнение ограничений PRIMARY KEY и UNIQUE;
Достаточен ли объём буфера сервера;
Также возможен «дедуктивный» подход, при котором сначала созда-
ётся большое количество индексов, а затем неиспользуемые индексы уда-
ляются.
27
2.5. Оптимизация БД и приложения
Перенос логики на сторону сервера
Этот пункт очевиден для опытных пользователей PostrgeSQL и предна-
значен для тех, кто использует или переносит на PostgreSQL приложения,
написанные изначально для более примитивных СУБД.
Реализация части логики на стороне сервера через хранимые проце-
дуры, триггеры, правила
1
часто позволяет ускорить работу приложения.
Действительно, если несколько запросов объединены в процедуру, то не
требуется
пересылка промежуточных запросов на сервер;
получение промежуточных результатов на клиент и их обработка;
Кроме того, хранимые процедуры упрощают процесс разработки и под-
держки: изменения надо вносить только на стороне сервера, а не менять
запросы во всех приложениях.
Оптимизация конкретных запросов
В этом разделе описываются запросы, для которых по разным причи-
нам нельзя заставить оптимизатор использовать индексы, и которые бу-
дут всегда вызывать полный просмотр таблицы. Таким образом, если вам
требуется использовать эти запросы в требовательном к быстродействию
приложении, то придётся их изменить.
SELECT count(*) FROM <огромная таблица>
Функция count() работает очень просто: сначала выбираются все за-
писи, удовлетворяющие условию, а потом к полученному набору запи-
сей применяется агрегатная функция считается количество выбранных
строк. Информация о видимости записи для текущей транзакции кон-
курентным транзакциям может быть видимо разное количество записей
в таблице!) не хранится в индексе, поэтому, даже если использовать для
выполнения запроса индекс первичного ключа таблицы, всё равно потре-
буется чтение записей собственно из файла таблицы.
Проблема Запрос вида
Листинг 2.11 SQL
Line 1 SELECT count ( *) FROM f oo ;
осуществляет полный просмотр таблицы foo, что весьма долго для таб-
лиц с большим количеством записей.
Решение Простого решения проблемы, к сожалению, нет. Возможны
следующие подходы:
1
RULE реализованное в PostgreSQL расширение стандарта SQL, позволяющее, в
частности, создавать обновляемые представления
28
2.5. Оптимизация БД и приложения
1. Если точное число записей не важно, а важен порядок
1
, то можно ис-
пользовать информацию о количестве записей в таблице, собранную
при выполнении команды ANALYZE:
Листинг 2.12 SQL
Line 1 SELECT r e l t u p l e s FROM pg _class WHERE relname = fo o ;
2. Если подобные выборки выполняются часто, а изменения в табли-
це достаточно редки, то можно завести вспомогательную таблицу,
хранящую число записей в основной. На основную же таблицу пове-
сить триггер, который будет уменьшать это число в случае удаления
записи и увеличивать в случае вставки. Таким образом, для полу-
чения количества записей потребуется лишь выбрать одну запись из
вспомогательной таблицы;
3. Вариант предыдущего подхода, но данные во вспомогательной таб-
лице обновляются через определённые промежутки времени (cron);
Медленный DISTINCT
Текущая реализация DISTINCT для больших таблиц очень медлен-
на. Но возможно использовать GROUP BY взамен DISTINCT. GROUP BY
может использовать агрегирующий хэш, что значительно быстрее, чем
DISTINCT (актуально до версии 8.4 и ниже).
Листинг 2.13 DISTINCT
Line 1 po st gr e s=# s e l e c t count ( *) from ( s e l e c t d i s t i n c t i from g ) a
;
- count
- - - - - - - -
- 19125
5 (1 row )
-
- Time : 580 ,553 ms
-
-
10 p o s t g r e s=# s e l e c t count ( * ) from ( s e l e c t d i s t i n c t i from g ) a
;
- count
- - - - - - - -
- 19125
- (1 row )
15
- Time : 36 ,281 ms
1
«на нашем форуме более 10000 зарегистрированных пользователей, оставивших
более 50000 сообщений!»
29
2.5. Оптимизация БД и приложения
Листинг 2.14 GROUP BY
Line 1 p os tg re s=# s e l e c t count ( * ) from ( s e l e c t i from g group by i )
a ;
- count
- - - - - - - -
- 19125
5 (1 row )
-
- Time : 26 ,562 ms
-
-
10 p o s t g r e s=# s e l e c t count ( * ) from ( s e l e c t i from g group by i )
a ;
- count
- - - - - - - -
- 19125
- (1 row )
15
- Time : 25 ,270 ms
Утилиты для оптимизации запросов
pgFouine
pgFouine это анализатор log-файлов для PostgreSQL, используемый
для генерации детальных отчетов из log-файлов PostgreSQL. pgFouine по-
может определить, какие запросы следует оптимизировать в первую оче-
редь. pgFouine написан на языке программирования PHP с использовани-
ем объектно-ориентированных технологий и легко расширяется для под-
держки специализированных отчетов, является свободным программным
обеспечением и распространяется на условиях GNU General Public License.
Утилита спроектирована таким образом, чтобы обработка очень больших
log-файлов не требовала много ресурсов.
Для работы с pgFouine сначала нужно сконфигурировать PostgreSQL
для создания нужного формата log-файлов:
Чтобы включить протоколирование в syslog
Листинг 2.15 pgFouine
Line 1 l o g_ d es t in a ti o n = s y s l o g
- r e d i r e c t _ s t d e r r = o f f
- silent_mode = on
-
Для записи запросов, длящихся дольше n миллисекунд:
30
2.5. Оптимизация БД и приложения
Листинг 2.16 pgFouine
Line 1 log_min_duration_statement = n
- log_ dura tion = o f f
- log_statement = none
-
Для записи каждого обработанного запроса установите
log_min_duration_statement на 0. Чтобы отключить запись запросов,
установите этот параметр на -1.
pgFouine простой в использовании инструмент командной строки.
Следующая команда создаёт HTML-отчёт со стандартными параметрами:
Листинг 2.17 pgFouine
Line 1 pgf o u in e . php - f i l e your / l og / f i l e . l og > your - r e po rt . html
С помощью этой строки можно отобразить текстовый отчёт с 10 запро-
сами на каждый экран на стандартном выводе:
Листинг 2.18 pgFouine
Line 1 pgf o u in e . php - f i l e your / l og / f i l e . l og - top 10 - format t e xt
Более подробно о возможностях, а также много полез-
ных примеров, можно найти на официальном сайте проекта
pgfouine.projects.pgfoundry.org.
pgBadger
pgBadger аналогичная утилита, что и pgFouine, но написанная на
Perl. Еще одно большое преимущество проекта в том, что он более активно
сейчас разрабатывается (на момент написания этого текста последний ре-
лиз pgFouine был в 24.02.2010, а последняя версия pgBadger 24.01.2017).
Установка pgBadger проста:
Листинг 2.19 Установка pgBadger
Line 1 $ t ar x zf pgbadger - 2 . x . t ar . gz
- $ cd pgbadger - 2 . x/
- $ p e r l M a k ef il e . PL
- $ make && sudo make i n s t a l l
Как и в случае с pgFouine нужно настроить PostgreSQL логи:
Листинг 2.20 Настройка логов PostgreSQL
Line 1 l o g g i n g _ c o l l e c t o r = on
31
2.5. Оптимизация БД и приложения
- log_min_messages = debug1
- log_min_error_statement = debug1
- log_min_duration_statement = 0
5 l o g_ l in e _p r ef i x = ’%t [%p ] : [% l - 1 ] u s e r=%u , db=%d
- l o g _ checkpoints = on
- l o g _ c o n n e c t i o n s = on
- lo g _d i sco n ne c tio n s = on
- log_lock_waits = on
10 log _tem p_fi les = 0
Парсим логи PostgreSQL через pgBadger:
Листинг 2.21 Запуск pgBadger
Line 1 $ . / pgbadger ~/ p g sq l / master /pg_log/ p o s t g re sq l -2 0 12 -08 -30 _132
*
- [========================>] Parsed 10485768 bytes o f
10485768 (100.00%)
- [========================>] Parsed 10485828 bytes o f
10485828 (100.00%)
- [========================>] Parsed 10485851 bytes o f
10485851 (100.00%)
5 [========================>] Parsed 10485848 bytes o f
10485848 (100.00%)
- [========================>] Parsed 10485839 bytes o f
10485839 (100.00%)
- [========================>] Parsed 982536 bytes o f 982536
(100.00%)
В результате получится HTML файлы, которые содержат статистику
по запросам к PostgreSQL. Более подробно о возможностях можно найти
на официальном сайте проекта http://dalibo.github.io/pgbadger/.
pg_stat_statements
Pg_stat_statements расширение для сбора статистики выполнения
запросов в рамках всего сервера. Преимущество данного расширения в
том, что ему не требуется собирать и парсить логи PostgreSQL, как это
делает pgFouine и pgBadger. Для начала установим и настроим его:
Листинг 2.22 Настройка pg_stat_statements в postgresql.conf
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = pg_stat_statements
- c u s t o m _ v a r i a b l e _ c lasses = pg_stat_statements # данная
настройка нужна для PostgreSQL 9 . 1 и ниже
-
- pg_stat_statements . max = 10000
5 pg_stat_statements . t r ac k = a l l
32
2.5. Оптимизация БД и приложения
После внесения этих параметров PostgreSQL потребуется перегрузить.
Параметры конфигурации pg_stat_statements:
1. pg_stat_statements.max (integer)» максимальное количество sql за-
просов, которое будет храниться расширением даляются записи с
наименьшим количеством вызовов);
2. pg_stat_statements.track (enum)» какие SQL запросы требуется за-
писывать. Возможные параметры: top (только запросы от приложе-
ния/клиента), all (все запросы, например в функциях) и none (от-
ключить сбор статистики);
3. pg_stat_statements.save (boolean)» следует ли сохранять собранную
статистику после остановки PostgreSQL. По умолчанию включено;
Далее активируем расширение:
Листинг 2.23 Активация pg_stat_statements
Line 1 # CREATE EXTENSION pg_stat_statements ;
Пример собранной статистики:
Листинг 2.24 pg_stat_statements статистика
Line 1 # SELECT query , c a l l s , total_time , rows , 1 0 0 . 0 *
shared_blks_hit /
- n u l l i f ( shared_blks_hit + shared_blks_read , 0)
AS hi t _ percent
- FROM pg_stat_statements ORDER BY total _time DESC
LIMIT 1 0;
- - [ RECORD 1 ] - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5 query | SELECT query , c a l l s , total_time , rows , ? *
shared_blks_hit /
- | n u l l i f ( shared_blks_hit +
shared_blks_read , ?) AS h i t _ p ercent
- | FROM pg_stat_statements ORDER BY
tot al_ti me DESC LIMIT ? ;
- c a l l s | 3
- tota l_tim e | 0.994
10 rows | 7
- h it_percen t | 100. 00000 00000000 000
- - [ RECORD 2 ] - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- query | i n s e r t i n t o x ( i ) s e l e c t g e n e r a t e _ s e r i e s ( ? , ? ) ;
- c a l l s | 2
33
2.5. Оптимизация БД и приложения
15 tota l_tim e | 0.591
- rows | 110
- h it_percen t | 100. 00000 00000000 000
- - [ RECORD 3 ] - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- query | s e l e c t * from x where i = ? ;
20 c a l l s | 2
- tota l_tim e | 0.157
- rows | 6
- h it_percen t | 100. 00000 00000000 000
- - [ RECORD 4 ] - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25 query | SELECT pg_stat_statements_reset ( ) ;
- c a l l s | 1
- tota l_tim e | 0.102
- rows | 1
- h it_percen t |
Для сброса статистики есть команда pg_stat_statements_reset:
Листинг 2.25 Сброс статистики
Line 1 # SELECT pg_stat_statements_reset ( ) ;
- - [ RECORD 1 ] - - - - - - - - - - - - + -
- pg_stat_statements_reset |
-
5 # SELECT query , c a l l s , total_time , rows , 1 0 0 . 0 *
shared_blks_hit /
- n u l l i f ( shared_blks_hit + shared_blks_read , 0)
AS hi t _ percent
- FROM pg_stat_statements ORDER BY total _time DESC
LIMIT 1 0;
- - [ RECORD 1 ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- query | SELECT pg_stat_statements_reset ( ) ;
10 c a l l s | 1
- tota l_tim e | 0.175
- rows | 1
- h it_percen t |
Хочется сразу отметить, что расширение только с версии PostgreSQL
9.2 contrib нормализирует SQL запросы. В версиях 9.1 и ниже SQL запросы
сохраняются как есть, а значит «select * from table where id = и «select
* from table where id = 21» буду разными записями, что почти бесполезно
для сбора полезной статистики.
34
2.6. Заключение
2.6 Заключение
К счастью, PostgreSQL не требует особо сложной настройки. В боль-
шинстве случаев вполне достаточно будет увеличить объём выделенной
памяти, настроить периодическое поддержание базы в порядке и прове-
рить наличие необходимых индексов. Более сложные вопросы можно об-
судить в специализированном списке рассылки.
35
3
Индексы
«Ну у вас и запросики»
сказала база данных и зависла
интернет
Что такое таблица в реляционной СУБД? Это такой список из корте-
жей (tuple). Каждый кортеж состоит из ячеек (row). Количество ячеек в
кортеже и их тип совпадают со схемой колонки, нескольких колонок. Этот
список имеет сквозную нумерацию RowId порядковый номер. Таким об-
разом, таблицы можно осознавать как список пар (RowId, Кортеж).
Индексы это обратные отношения (Кортеж, RowId). Кортеж обя-
зан содержать хотя бы одну ячейку . е. быть построенным минимум по
одной колонке). Для индексов, которые индексируют более одной колон-
ки они ещё называются составными, и участвуют в отношениях вида
«многие-ко-многим» всё написанное верно в равной степени. Очевидно,
если кортеж не уникален колонке существует два одинаковых кор-
тежа), то эти отношения выглядят как (Кортеж, Список RowId) т. е.
кортежу сопоставляется список RowId.
Индексы могут использоваться для таких операций в базе данных:
Поиск данных абсолютно все индексы поддерживают поиск зна-
чений по равенству. А B-Tree по произвольным диапазонам;
Like B-Tree и Bitmap индексы можно использовать для ускорения
префиксных Like-предикатов (вида abc%);
Оптимизатор B-Tree и R-Tree индексы представляют из себя ги-
стограмму произвольной точности;
Join индексы могут быть использованы для Merge, Index алгорит-
мов;
Relation индексы могут быть использованы для операций
except/intersect;
Aggregations индексы позволяют эффективно вычислять неко-
торые агрегатные функции COUNT, MIN, MAX, а также их
DISTINCT версии;
36
3.1. Типы индексов
Grouping индексы позволяют эффективно вычислять группировки
и произвольные агрегатные функции (sort-group алгоритм);
3.1 Типы индексов
В зависимости от структуры, используемой в реализации индексов, су-
щественно различаются поддерживаемые операции, их стоимости, а также
свойства читаемых данных. Давайте рассмотрим какие существуют типы
индексов в PostgreSQL.
B-Tree
B-Tree (Boeing/Bayer/Balanced/Broad/Bushy-Tree) называют упорядо-
ченное блочное дерево. Узлы в дереве представляют из себя блоки фикси-
рованного размера. У каждого узла фиксированное число детей. Струк-
тура B-Tree представлена на рисунке 3.1.
Рис. 3.1: B-Tree индекс
B-Tree для индексов отличается от представленной на Википедии
есть дублированные данных в промежуточных блоках. Для i-ой записи в
блоке сохраняется не значение, которое больше максимума i-го поддерева,
и меньше минимума (i+1) поддерева, а максимум i-го поддерева. Различия
проистекают из того, что википедия приводит пример B-Tree для множе-
ства, а нам нужен ассоциативный массив.
В индексном B-Tree значения и RowId размещаются совместно на ниж-
нем слое дерева. Каждый узел дерева представляет из себя одну страницу
(page) в некотором формате. В начале страницы всегда идёт некоторый за-
головок. Для корневого и промежуточного узла в страницах хранятся па-
ры (Значение, Номер страницы). Для листовых пары (Значение ,RowId)
либо (Значение, Список RowId) зависимости от свойств значения уни-
кально или нет). B-Tree деревья имеют крайне маленькую высоту по-
37
3.1. Типы индексов
рядка 𝐻 = log
𝑚
𝑁, где m количество записей в блоке, N количество
элементов. B-Tree деревья являются упорядоченными все элементы в
любой странице (блоке) дерева лежат последовательно. Предыдущие два
свойства позволяют крайне эффективно производить поиск начиная с
первой страницы, половинным делением (binary search) выделяются де-
ти, в которых лежат границы поиска. Таким образом, прочитав всего H,
2H страниц мы находим искомый диапазон. Важным нюансом является
также факт, что страницы в листьях связаны в односвязный либо дву-
связный список - это означает, что, выполнив поиск, мы можем дальше
просто последовательно читать страницы, и эффективность чтения боль-
шего объёма данных (длинного диапазона) сравнима с эффективностью
чтения данных из таблицы.
Сильные стороны B-Tree индексов:
сохраняют сортированность данных;
поддерживают поиск по унарным и бинарным предикатам (<a; = b
; >c and <d; <e and >f) за O(log
𝑚
𝑁), где m количество записей в
блоке, N количество элементов;
позволяют не сканируя последовательность данных целиком оценить
cardinality оличество записей) для всего индекса следовательно
таблицы), диапазона, причём с произвольной точностью. Посмотре-
ли корневую страницу получили одну точность. Посмотрели сле-
дующий уровень дерева получили точность получше. Просмотрели
дерево до корня получили точное число записей;
самобалансируемый, для внесения изменения не требуется полного
перестроения, происходит не более O(log
𝑚
𝑁) действий, где m ко-
личество записей в блоке, N количество элементов;
Слабые стороны B-Tree индексов:
занимают много места на диске. Индекс по уникальным Integer-ам,
к примеру, весит в два раза больше аналогичной колонки .к. хра-
нятся ещё и RowId);
при постоянной записи дерево начинает хранить данные разреженно
(сразу после построения они могут лежать очень плотно), и время
доступа увеличивается за счёт увеличения объёма дисковой инфор-
мации. Поэтому B-Tree индексы требуют присмотра и периодическо-
го перепостроения (REBUILD);
R-Tree
R-Tree (Rectangle-Tree) предназначен для хранения пар (X, Y) значе-
ний числового типа (например, координат). По способу организации R-
Tree очень похоже на B-Tree. Единственное отличие это информация,
записываемая в промежуточные страницы в дереве. Для i-го значения в
38
3.1. Типы индексов
узле в B-Tree мы пишем максимум из i-го поддерева, а в R-Tree мини-
мальный прямоугольник, покрывающий все прямоугольники из ребёнка.
Подробней можно увидеть на рисунке 3.2.
Рис. 3.2: R-Tree индекс
Сильные стороны:
поиск произвольных регионов, точек за O(log
𝑚
𝑁), где m количе-
ство записей в блоке, N количество элементов;
позволяет оценить количество точек в некотором регионе без полного
сканирования данных;
Слабые стороны:
существенная избыточность в хранении данных;
медленное обновление данных;
В целом, плюсы-минусы очень напоминают B-Tree.
39
3.1. Типы индексов
Hash индекс
Hash индекс по сути является ассоциативным хеш-контейнером. Хеш-
контейнер это массив из разряженных значений. Адресуются отдель-
ные элементы этого массива некоторой хеш-функцией которая отобража-
ет каждое значение в некоторое целое число. Т. е. результат хеш-функции
является порядковым номером элемента в массиве. Элементы массива в
хеш-контейнере называются бакетами (bucket). Обычно один бакет од-
на страница. Хеш-функция отображает более мощное множество в менее
мощное, возникают так называемые коллизии — ситуация, когда одно-
му значению хеш-функции соответствует несколько разных значений. В
бакете хранятся значения, образующие коллизию. Разрешение коллизий
происходит посредством поиска среди значений, сохранённых в бакете.
Рис. 3.3: Hash индекс
Сильные стороны:
очень быстрый поиск O(1);
стабильность индекс не нужно перестраивать;
Слабые стороны:
хеш очень чувствителен к коллизиям хеш-функции. В случае «пло-
хого» распределения данных, большинство записей будет сосредото-
чено в нескольких бакетах, и фактически поиск будет происходить
путем разрешения коллизий;
40
3.1. Типы индексов
из-за нелинейности хэш-функций данный индекс нельзя сортировать
по значению, что приводит к невозможности использования в срав-
нениях больше/меньше и «IS NULL»;
данный индекс в PostgreSQL транзакционно небезопасен, нужно
перестраивать после краха и не реплицируется через потоковую
(streaming) репликацию (разработчики обещают это исправить к 10
версии);
Битовый индекс (bitmap index)
Битовый индекс (bitmap index) метод битовых индексов заключа-
ется в создании отдельных битовых карт (последовательность 0 и 1) для
каждого возможного значения столбца, где каждому биту соответствует
строка с индексируемым значением, а его значение равное 1 означает, что
запись, соответствующая позиции бита содержит индексируемое значение
для данного столбца или свойства (алгоритм Хаффмана).
Рис. 3.4: Битовый индекс
Сильные стороны:
компактность представления (занимает мало места);
быстрое чтение и поиск по предикату «равно»;
Слабые стороны:
невозможность изменить способ кодирования значений в процессе
обновления данных;
41
3.1. Типы индексов
У PostgreSQL нет возможности создать постоянный битовый индекс,
но база может на лету создавать данные индексы для объединения разных
индексов. Чтобы объединить несколько индексов, база сканирует каждый
необходимый индекс и готовит битовую карту в памяти с расположением
строк таблицы. Битовые карты затем обрабатываются AND/OR опера-
цией по мере требования запроса и после этого выбираются колонки с
данными.
GiST индекс
GiST (Generalized Search Tree) обобщение B-Tree, R-Tree дерево по-
иска по произвольному предикату. Структура дерева не меняется, по-
прежнему в каждом нелистовом узле хранятся пары (Значения, Номер
страницы), а количество детей совпадает с количеством пар в узле. Суще-
ственное отличие состоит в организации ключа. B-Tree деревья заточены
под поиск диапазонов и хранят максимумы поддерева-ребёнка. R-Tree
региона на координатной плоскости. GiST предлагает в качестве значе-
ний в нелистовых узлах хранить ту информацию, которую мы считаем
существенной, и которая позволит определить, есть ли интересующие нас
значения довлетворяющие предикату) в поддереве-ребёнке. Конкретный
вид хранимой информации зависит от вида поиска, который мы желаем
проводить. Таким образом параметризовав R-Tree и B-Tree дерево преди-
катами и значениями мы автоматически получаем специализированный
под задачу индекс (PostGiST, pg_trgm, hstore, ltree, прочее).
Сильные стороны:
эффективный поиск;
Слабые стороны:
большая избыточность;
необходимость специализированной реализации под каждую группу
запросов;
Остальные плюсы-минусы совпадают с B-Tree и R-Tree индексами.
GIN индекс
GIN (Generalized Inverted Index) — обратный индекс, используемым
полнотекстовым поиском PostgreSQL. Это означает, что в структуре ин-
дексов с каждой лексемой сопоставляется отсортированный список номе-
ров документов, в которых она встречается. Очевидно, что поиск по та-
кой структуре намного эффективнее, чем при использовании GiST, однако
процесс добавления нового документа достаточно длителен.
42
3.1. Типы индексов
Cluster индекс
Не является индексом, поскольку производит кластеризацию табли-
цы по заданному индексу. Более подробно можно почитать в разделе
«2.3 CLUSTER».
BRIN индекс
Версия PostgreSQL 9.5 привнесла с собой новый вид индексов BRIN
(Block Range Index, или индекс блоковых зон).
Рис. 3.5: BRIN индекс
В отличие от привычного B-Tree, этот индекс намного эффективнее
для очень больших таблиц, и в некоторых ситуациях позволяет заменить
собой партицирование (подробно можно почитать в разделе «4 Партицио-
нирование»). BRIN-индекс имеет смысл применять для таблиц, в которых
часть данных уже по своей природе как-то отсортирована. Например, это
характерно для логов или для истории заказов магазина, которые пишут-
ся последовательно, а потому уже на физическом уровне упорядочены по
дате/номеру, и в то же время таблицы с такими данными обычно разрас-
таются до гигантских размеров.
Под блоковой зоной (Block Range) подразумевается набор страниц, фи-
зически расположенных по соседству в таблице. Для каждой такой зоны
создается некий идентификатор, отвечающий за «место» этой зоны в таб-
лице. Для лога это может быть дата создания записи. Поиск по такому
индексу осуществляется с потерями информации, то есть выбираются все
записи, входящие в блоковые зоны с идентификаторами, соответствующи-
ми запросу, но среди записей в этих зонах могут попадаться такие, которые
43
3.2. Возможности индексов
на следующем этапе надо будет отфильтровать. Размер индекса при этом
очень маленький, и он почти не нагружает базу. Размер индекса обратно
пропорционален параметру pages_per_range, отвечающему за количество
страниц на зону. В то же время, чем меньше размер зоны, тем меньше
«лишних» данных попадёт в результат поиска (надо подходить к этому
параметру с умом).
Индексы BRIN могут иметь один из нескольких встроенных классов
операторов, по которым будет осуществляться разбивка на зоны и при-
своение идентификаторов. Например, int8_minmax_ops применяется для
операций сравнения целых чисел, а date_minmax_ops для сравнения дат.
3.2 Возможности индексов
Функциональный индекс (functional index)
Вы можете построить индекс не только по полю/нескольким полям
таблицы, но и по выражению, зависящему от полей. Пусть, например,
в вашей таблице foo есть поле foo_name, и выборки часто делаются по
условию «первая буква из поля foo_name в любом регистре». Вы можете
создать индекс
Листинг 3.1 Индекс
Line 1 CREATE INDEX foo_name_first_idx ON f o o ( ( lower ( s u bs t r (
foo_name , 1 , 1) ) ) ) ;
и запрос вида
Листинг 3.2 Запрос
Line 1 SELECT * FROM f o o WHERE low e r ( s ub s tr ( foo_name , 1 , 1) ) = а ;
будет его использовать.
Частичный индекс (partial index)
Под частичным индексом понимается индекс с предикатом WHERE.
Пусть, например, у вас есть в базе таблица scheta с параметром uplocheno
типа boolean. Записей, где uplocheno = false меньше, чем записей с uplocheno
= true, а запросы по ним выполняются значительно чаще. Вы можете
создать индекс
Листинг 3.3 Индекс
Line 1 CREATE INDEX scheta_neuplocheno ON s c he ta ( i d ) WHERE NOT
uplocheno ;
44
3.2. Возможности индексов
который будет использоваться запросом вида
Листинг 3.4 Запрос
Line 1 SELECT * FROM s c h et a WHERE NOT uplocheno AND . . . ;
Достоинство подхода в том, что записи, не удовлетворяющие условию
WHERE, просто не попадут в индекс.
Уникальный индекс (unique index)
Уникальный индекс гарантирует, что таблица не будет иметь более
чем одну строку с тем же значением. Это удобно по двум причинам: це-
лостность данных и производительность. Поиск данных с использованием
уникального индекса, как правило, очень быстрый.
Индекс нескольких столбцов (multi-column index)
В PostgreSQL возможно создавать индексы на несколько столбцов, но
нам главное нужно понять когда имеет смысл создавать такой индекс,
поскольку планировщик запросов PostgreSQL может комбинировать и ис-
пользовать несколько индексов в запросе путем создания битового индекса
3.1 Битовый индекс (bitmap index)»). Можно, конечно, создать индек-
сы, которые охватят все возможные запросы, но за это придется платить
производительностью (индексы нужно перестраивать при запросах на мо-
дификацию данных). Нужно также помнить, что индексы на несколько
столбцов могут использоваться только запросами, которые ссылаются на
эти столбцы в индексе в том же порядке. Индекс по столбцам (a, b) мо-
жет быть использован в запросах, которые содержат a = x and b = y или
a = x, но не будет использоваться в запросе вида b = y. Если это подходит
под запросы вашего приложения, то данный индекс может быть полезен.
В таком случае создание индекса на поле a было бы излишним. Индекс
нескольких столбцов с указанием уникальности (unique) может быть так-
же полезен для сохранения целостности данных . е. когда набор данных
в этих стобцах должен быть уникальным).
45
4
Партиционирование
Решая какую-либо проблему,
всегда полезно заранее знать
правильный ответ. При
условии, конечно, что вы
уверены в наличии самой
проблемы
Народная мудрость
4.1 Введение
Партиционирование (partitioning, секционирование) это разбиение
больших структур баз данных (таблицы, индексы) на меньшие кусочки.
Звучит сложно, но на практике все просто.
Скорее всего у Вас есть несколько огромных таблиц (обычно всю на-
грузку обеспечивают всего несколько таблиц СУБД из всех имеющихся).
Причем чтение в большинстве случаев приходится только на самую по-
следнюю их часть . е. активно читаются те данные, которые недавно
появились). Примером тому может служить блог на первую страницу
(это последние 5. . . 10 постов) приходится 40. . . 50% всей нагрузки, или
новостной портал (суть одна и та же), или системы личных сообщений,
впрочем понятно. Партиционирование таблицы позволяет базе данных де-
лать интеллектуальную выборку сначала СУБД уточнит, какой парти-
ции соответствует Ваш запрос (если это реально) и только потом сделает
этот запрос, применительно к нужной партиции (или нескольким парти-
циям). Таким образом, в рассмотренном случае, Вы распределите нагруз-
ку на таблицу по ее партициям. Следовательно выборка типа SELECT *
FROM articles ORDER BY id DESC LIMIT 10 будет выполняться только над
последней партицией, которая значительно меньше всей таблицы.
Итак, партиционирование дает ряд преимуществ:
46
4.2. Теория
На определенные виды запросов (которые, в свою очередь, создают
основную нагрузку на СУБД) мы можем улучшить производитель-
ность;
Массовое удаление может быть произведено путем удаления одной
или нескольких партиций (DROP TABLE гораздо быстрее, чем мас-
совый DELETE);
Редко используемые данные могут быть перенесены в другое храни-
лище;
4.2 Теория
На текущий момент PostgreSQL поддерживает два критерия для со-
здания партиций:
Партиционирование по диапазону значений (range) таблица разби-
вается на «диапазоны» значений по полю или набору полей в табли-
це, без перекрытия диапазонов значений, отнесенных к различным
партициям. Например, диапазоны дат;
Партиционирование по списку значений (list) таблица разбивается
по спискам ключевых значений для каждой партиции.
Чтобы настроить партиционирование таблицы, достаточно выполнить
следующие действия:
Создается «мастер» таблица, из которой все партиции будут насле-
доваться. Эта таблица не будет содержать данные. Также не нужно
ставить никаких ограничений на таблицу, если конечно они не будут
дублироваться на партиции;
Создайте несколько «дочерних» таблиц, которые наследуют от «ма-
стер» таблицы;
Добавить в «дочерние» таблицы значения, по которым они будут
партициями. Стоить заметить, что значения партиций не должны
пересекаться. Например:
Листинг 4.1 Пример неверного задания значений партиций
Line 1 CHECK ( o u tlet I D BETWEEN 100 AND 200 )
- CHECK ( o u tl etI D BETWEEN 200 AND 300 )
неверно заданы партиции, поскольку непонятно какой партиции при-
надлежит значение 200;
Для каждой партиции создать индекс по ключевому полю (или
нескольким), а также указать любые другие требуемые индексы;
При необходимости, создать триггер или правило для перенаправле-
ния данных с «мастер» таблицы в соответствующую партицию;
47
4.3. Практика использования
Убедиться, что параметр constraint_exclusion не отключен в
postgresql.conf. Если его не включить, то запросы не будут оптими-
зированы при работе с партиционированием;
4.3 Практика использования
Теперь начнем с практического примера. Представим, что в нашей си-
стеме есть таблица, в которую мы собираем данные о посещаемости нашего
ресурса. На любой запрос пользователя наша система логирует действия
в эту таблицу. И, например, в начале каждого месяца (неделю) нам нуж-
но создавать отчет за предыдущий месяц (неделю). При этом логи нужно
хранить в течение 3 лет. Данные в такой таблице накапливаются быстро,
если система активно используется. И вот, когда в таблице уже миллионы,
а то и миллиарды записей, создавать отчеты становится все сложнее (да и
чистка старых записей становится нелегким делом). Работа с такой табли-
цей создает огромную нагрузку на СУБД. Тут нам на помощь и приходит
партиционирование.
Настройка
Для примера, мы имеем следующую таблицу:
Листинг 4.2 «Мастер» таблица
Line 1 CREATE TABLE my_logs (
- id SERIAL PRIMARY KEY,
- user_id INT NOT NULL,
- l o gd a te TIMESTAMP NOT NULL,
5 data TEXT,
- some_state INT
- ) ;
Поскольку нам нужны отчеты каждый месяц, мы будем делить пар-
тиции по месяцам. Это поможет нам быстрее создавать отчеты и чистить
старые данные.
«Мастер» таблица будет my_logs, структуру которой мы указали выше.
Далее создадим «дочерние» таблицы (партиции):
Листинг 4.3 «Дочерние» таблицы
Line 1 CREATE TABLE my_logs2010m10 (
- CHECK ( l og d ate >= DATE 2010 -10 -01 AND lo g da t e < DATE
2010 -11 -01 )
- ) INHERITS ( my_logs ) ;
- CREATE TABLE my_logs2010m11 (
48
4.3. Практика использования
5 CHECK ( l og d ate >= DATE 2010 -11 -01 AND lo g da t e < DATE
2010 -12 -01 )
- ) INHERITS ( my_logs ) ;
- CREATE TABLE my_logs2010m12 (
- CHECK ( l og d ate >= DATE 2010 -12 -01 AND lo g da t e < DATE
2011 -01 -01 )
- ) INHERITS ( my_logs ) ;
10 CREATE TABLE my_logs2011m01 (
- CHECK ( l og d ate >= DATE 2011 -01 -01 AND lo g da t e < DATE
2010 -02 -01 )
- ) INHERITS ( my_logs ) ;
Данными командами мы создаем таблицы my_logs2010m10,
my_logs2010m11 и т. д., которые копируют структуру с «мастер» таблицы
(кроме индексов). Также с помощью «CHECK» мы задаем диапазон зна-
чений, который будет попадать в эту партицию (хочу опять напомнить,
что диапазоны значений партиций не должны пересекаться!). Поскольку
партиционирование будет работать по полю logdate, мы создадим индекс
на это поле на всех партициях:
Листинг 4.4 Создание индексов
Line 1 CREATE INDEX my_logs2010m10_logdate ON my_logs2010m10 (
l o gda t e ) ;
- CREATE INDEX my_logs2010m11_logdate ON my_logs2010m11 (
l o gda t e ) ;
- CREATE INDEX my_logs2010m12_logdate ON my_logs2010m12 (
l o gda t e ) ;
- CREATE INDEX my_logs2011m01_logdate ON my_logs2011m01 (
l o gda t e ) ;
Далее для удобства создадим функцию, которая будет перенаправлять
новые данные с «мастер» таблицы в соответствующую партицию.
Листинг 4.5 Функция для перенаправления
Line 1 CREATE OR REPLACE FUNCTION my_log s_inser t_t rigg er ( )
- RETURNS TRIGGER AS $$
- BEGIN
- IF ( NEW. l o gd a te >= DATE 2010 -10 -01 AND
5 NEW. l o gda t e < DATE 2010 -11 -01 ) THEN
- INSERT INTO my_logs2010m10 VALUES (NEW. * ) ;
- ELSIF ( NEW. l o gd a te >= DATE 2010 -11 -01 AND
- NEW. l o gda t e < DATE 2010 -12 -01 ) THEN
- INSERT INTO my_logs2010m11 VALUES (NEW. * ) ;
10 ELSIF ( NEW. l o gd a te >= DATE 2010 -12 -01 AND
- NEW. l o gda t e < DATE 2011 -01 -01 ) THEN
- INSERT INTO my_logs2010m12 VALUES (NEW. * ) ;
49
4.3. Практика использования
- ELSIF ( NEW. l o gd a te >= DATE 2011 -01 -01 AND
- NEW. l o gda t e < DATE 2011 -02 -01 ) THEN
15 INSERT INTO my_logs2011m01 VALUES (NEW. * ) ;
- ELSE
- RAISE EXCEPTION Date out o f range . Fix the
my _logs_i nsert_t rig ger ( ) f u nc ti o n ! ;
- END IF ;
- RETURN NULL;
20 END;
- $$
- LANGUAGE p l p g s q l ;
В функции ничего особенного нет: идет проверка поля logdate, по кото-
рой направляются данные в нужную партицию. При ненахождении требу-
емой партиции вызываем ошибку. Теперь осталось создать триггер на
«мастер» таблицу для автоматического вызова данной функции:
Листинг 4.6 Триггер
Line 1 CREATE TRIGGER insert_m y_logs_ tri gger
- BEFORE INSERT ON my_logs
- FOR EACH ROW EXECUTE PROCEDURE m y_logs_ insert_ trigger ( ) ;
Партиционирование настроено и теперь мы готовы приступить к те-
стированию.
Тестирование
Для начала добавим данные в нашу таблицу my_logs:
Листинг 4.7 Данные
Line 1 INSERT INTO my_logs ( user_id , logdate , data , some_state )
VALUES( 1 , 2010 -10 -30 , 3 0. 1 0. 2 01 0 data , 1) ;
- INSERT INTO my_logs ( user_id , l o g date , data , some_state )
VALUES( 2 , 2010 -11 -10 , 1 0. 1 1. 2 01 0 data2 , 1) ;
- INSERT INTO my_logs ( user_id , l o g date , data , some_state )
VALUES( 1 , 2010 -12 -15 , 1 5. 1 2. 2 01 0 data3 , 1) ;
Теперь проверим где они хранятся:
Листинг 4.8 «Мастер» таблица чиста
Line 1 p a r t i t i o n i n g _ t e s t=# SELECT * FROM ONLY my_logs ;
- i d | user_id | lo g da t e | data | some_state
- - - - -+ - - - - - - - - -+ - - - - - - - - -+ - - - - - -+ - - - - - - - - - - - -
- (0 rows )
50
4.3. Практика использования
Как видим, в «мастер» таблицу данные не попали она чиста. Теперь
проверим, а есть ли вообще данные:
Листинг 4.9 Проверка данных
Line 1 p a r t i t i o n i n g _ t e s t=# SELECT * FROM my_logs ;
- i d | user_id | l ogd a te | data |
some_state
- - -
- - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- 1 | 1 | 2010 -10 -30 0 0: 00 :0 0 | 3 0. 1 0. 2 01 0 data |
1
5 2 | 2 | 2010 -11 -10 0 0: 00 :0 0 | 1 0. 1 1. 2 01 0 data2 |
1
- 3 | 1 | 2010 -12 -15 0 0: 00 :0 0 | 1 5. 1 2. 2 01 0 data3 |
1
- (3 rows )
Данные при этом выводятся без проблем. Проверим партиции, пра-
вильно ли хранятся данные:
Листинг 4.10 Проверка хранения данных
Line 1 p a r t i t i o n i n g _ t e s t=# S e l e c t * from my_logs2010m10 ;
- i d | user_id | l ogd a te | data |
some_state
- - -
- - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- 1 | 1 | 2010 -10 -30 0 0: 00 :0 0 | 3 0. 1 0. 2 01 0 data |
1
5 (1 row )
-
- p a r t i t i o n i n g _ t e s t=# S e l e c t * from my_logs2010m11 ;
- i d | user_id | l ogd a te | data |
some_state
- - -
- - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
10 2 | 2 | 2010 -11 -10 0 0: 00 :0 0 | 1 0. 1 1. 2 01 0 data2 |
1
- (1 row )
Данные хранятся на требуемых нам партициях. При этом запросы к
таблице my_logs менять не требуется:
51
4.3. Практика использования
Листинг 4.11 Проверка запросов
Line 1 p a r t i t i o n i n g _ t e s t=# SELECT * FROM my_logs WHERE user_id = 2 ;
- i d | user_id | l ogd a te | data |
some_state
- - -
- - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- 2 | 2 | 2010 -11 -10 0 0: 00 :0 0 | 1 0. 1 1. 2 01 0 data2 |
1
5 (1 row )
-
- p a r t i t i o n i n g _ t e s t=# SELECT * FROM my_logs WHERE data LIKE
%0.1% ;
- i d | user_id | l ogd a te | data |
some_state
- - -
- - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
10 1 | 1 | 2010 -10 -30 0 0: 00 :0 0 | 3 0. 1 0. 2 01 0 data |
1
- 2 | 2 | 2010 -11 -10 0 0: 00 :0 0 | 1 0. 1 1. 2 01 0 data2 |
1
- (2 rows )
Управление партициями
Обычно при работе с партиционированием старые партиции переста-
ют получать данные и остаются неизменными. Это дает огромное пре-
имущество над работой с данными через партиции. Например, нам нужно
удалить старые логи за 2014 год, 10 месяц. Нам достаточно выполнить:
Листинг 4.12 Чистка логов
Line 1 DROP TABLE my_logs2014m10 ;
поскольку DROP TABLE работает гораздо быстрее, чем удаление мил-
лионов записей индивидуально через DELETE. Другой вариант, который
более предпочтителен, просто удалить партицию из партиционирования,
тем самым оставив данные в СУБД, но уже не доступные через «мастер»
таблицу:
Листинг 4.13 Удаляем партицию из партиционирования
Line 1 ALTER TABLE my_logs2014m10 NO INHERIT my_logs ;
Это удобно, если мы хотим эти данные потом перенести в другое хра-
нилище или просто сохранить.
52
4.3. Практика использования
Важность «constraint_exclusion» для партиционирования
Параметр constraint_exclusion отвечает за оптимизацию запросов, что
повышает производительность для партиционированых таблиц. Напри-
мер, выполним простой запрос:
Листинг 4.14 «constraint_exclusion» OFF
Line 1 p a r t i t i o n i n g _ t e s t=# SET c on s t r a i nt _e x c l u s i o n = o f f ;
- p a r t i t i o n i n g _ t e s t=# EXPLAIN SELECT * FROM my_logs WHERE
l o gda t e > 2010 -12 -01 ;
-
- QUERY PLAN
5 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Res u l t ( c os t = 6 . 8 1 . . 10 4. 6 6 rows =1650 width=52)
- -> Append ( c o s t = 6. 8 1 . . 1 0 4 . 6 6 rows=1650 width=52)
- -> Bitmap Heap Scan on my_logs ( c os t = 6 . 8 1. .2 0 . 9 3
rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
10 -> Bitmap Index Scan on my_logs_logdate (
c o s t = 0 .0 0 . . 6 . 7 3 rows=330 width=0)
- Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- -> Bitmap Heap Scan on my_logs2010m10 my_logs (
c o s t = 6 . 8 1 . . 2 0 . 93 rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
- -> Bitmap Index Scan on
my_logs2010m10_logdate ( c o s t = 0 . 0 0 . . 6 . 7 3 rows=330 width
=0)
15 Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- -> Bitmap Heap Scan on my_logs2010m11 my_logs (
c o s t = 6 . 8 1 . . 2 0 . 93 rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
- -> Bitmap Index Scan on
my_logs2010m11_logdate ( c o s t = 0 . 0 0 . . 6 . 7 3 rows=330 width
=0)
- Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
20 -> Bitmap Heap Scan on my_logs2010m12 my_logs (
c o s t = 6 . 8 1 . . 2 0 . 93 rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
53
4.3. Практика использования
: : timestamp without time zone )
- -> Bitmap Index Scan on
my_logs2010m12_logdate ( c o s t = 0 . 0 0 . . 6 . 7 3 rows=330 width
=0)
- Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- -> Bitmap Heap Scan on my_logs2011m01 my_logs (
c o s t = 6 . 8 1 . . 2 0 . 93 rows=330 width=52)
25 Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
- -> Bitmap Index Scan on
my_logs2011m01_logdate ( c o s t = 0 . 0 0 . . 6 . 7 3 rows=330 width
=0)
- Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- (22 rows )
Как видно через команду EXPLAIN, данный запрос сканирует все
партиции на наличие данных в них, что не логично, поскольку данное
условие logdate > 2010-12-01 говорит о том, что данные должны брать-
ся только с партиций, где подходит такое условие. А теперь включим
constraint_exclusion:
Листинг 4.15 «constraint_exclusion» ON
Line 1 p a r t i t i o n i n g _ t e s t=# SET c on s t r a i nt _e x c l u s i o n = on ;
- SET
- p a r t i t i o n i n g _ t e s t=# EXPLAIN SELECT * FROM my_logs WHERE
l o gda t e > 2010 -12 -01 ;
- QUERY PLAN
5 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Res u l t ( c os t = 6 .8 1. . 4 1 . 8 7 rows=660 width =52)
- -> Append ( c o s t = 6 . 8 1 . . 4 1 . 8 7 rows=660 width =52)
- -> Bitmap Heap Scan on my_logs ( c os t = 6 . 8 1. .2 0 . 9 3
rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
10 -> Bitmap Index Scan on my_logs_logdate (
c o s t = 0 .0 0 . . 6 . 7 3 rows=330 width=0)
- Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- -> Bitmap Heap Scan on my_logs2010m12 my_logs (
c o s t = 6 . 8 1 . . 2 0 . 93 rows=330 width=52)
- Recheck Cond : ( l og d a te > 2010 -12 -01 0 0 : 0 0 : 0 0
: : timestamp without time zone )
54
4.4. Pg_partman
- -> Bitmap Index Scan on
my_logs2010m12_logdate ( c o s t = 0 . 0 0 . . 6 . 7 3 rows=330 width
=0)
15 Index Cond : ( l o gd a te > 2010 -12 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone )
- (10 rows )
Как мы видим, теперь запрос работает правильно и сканирует
только партиции, что подходят под условие запроса. Но включать
constraint_exclusion не желательно для баз, где нет партиционирования,
поскольку команда CHECK будет проверяться на всех запросах, даже про-
стых, а значит производительность сильно упадет. Начиная с 8.4 версии
PostgreSQL constraint_exclusion может быть «on», «off» и «partition». По
умолчанию рекомендуется) ставить constraint_exclusion «partition», ко-
торый будет проверять CHECK только на партиционированых таблицах.
4.4 Pg_partman
Поскольку реализация партиционирования реализована неполноценно
в PostgreSQL (для управления партициями и данными в них приходится
писать функции, триггеры и правила), то существует расширение, кото-
рое автоматизирует полностью данный процесс. PG Partition Manager, он
же pg_partman, это расширение для создания и управления партиция-
ми и партициями партиций (sub-partitoning) в PostgreSQL. Поддерживает
партицирование по времени (time-based) или по последованности (serial-
based). Для партицирования по диапазону значений (range) существует
отдельное расширение Range Partitioning (range_partitioning).
Текущая реализация поддерживает только INSERT операции, кото-
рые перенаправляют данные в нужную партицию. UPDATE операции,
которые будут перемещать данные из одной партиции в другую, не под-
держиваются. При попытке вставить данные, на которые нет партиции,
pg_partman перемещает их в «мастер» (родительскую) таблицу. Данный
вариант предпочтительнее, чем создавать автоматически новые партиции,
поскольку это может привести к созданию десятков или сотен ненужных
дочерних таблиц из-за ошибки в самих данных. Функция check_parent
позволяет проверить попадение подобных данных в родительскую таб-
лицу и решить, что с ними требуется делать далить или использовать
partition_data_time/partition_data_id для создания и переноса этих данных
в партиции).
Данное расширение использует большинство атрибутов родительской
таблицы для создания партиций: индексы, внешние ключи (опционально),
tablespace, constraints, privileges и ownership. Под такое условие попадают
OID и UNLOGGED таблицы.
Партициями партиций (sub-partitoning) поддерживаются разных уров-
ней: time->time, id->id, time->id и id->time. Нет лимитов на создание та-
55
4.4. Pg_partman
ких партиций, но стоит помнить, что большое число партиций влияет на
производительность родительской таблицы. Если размер партиций станет
слишком большим, то придется увеличивать max_locks_per_transaction па-
раметр для базы данных (64 по умолчанию).
В PostgreSQL 9.4 появилась возможность создания пользовательских
фоновых воркеров и динамически загружать их во время работы базы.
Благодаря этому в pg_partman есть собственный фоновый воркер, задача
которого запускать run_maintenance функцию каждый заданный проме-
жуток времени. Если у Вас версия PostgreSQL ниже 9.4, то придется вос-
пользоваться внешним планировщиком для выполнения данной функции
(например cron). Задача данной функции - проверять и автоматически
создавать партиции и опционально чистить старые.
Пример использования
Для начала установим данное расширение:
Листинг 4.16 Установка
Line 1 $ g i t c lo n e ht t ps : // g ithub . com/ k e i t h f 4 /pg_partman . g i t
- $ cd pg_partman/
- $ make
- $ sudo make i n s t a l l
Если не требуется использовать фоновый воркер, то можно собрать без
него:
Листинг 4.17 Установка
Line 1 $ sudo make NO_BGW=1 i n s t a l l
Для работы фонового воркера нужно загружать его на старте
PostgreSQL. Для этого потребуется добавить настройки в postgresql.conf:
Листинг 4.18 Настройки воркера
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = pg_partman_bgw # ( change
r e q u i r e s r e s t a r t )
- pg_partman_bgw . i n t e r v a l = 3600
- pg_partman_bgw . r o l e = myrole
- pg_partman_bgw . dbname = mydatabase
где:
pg_partman_bgw.dbname база данных, в которой будет выполнять-
ся run_maintenance функция. Если нужно указать больше одной базы,
то они указываются через запятую. Без этого параметра воркер не
будет работать;
56
4.4. Pg_partman
pg_partman_bgw.interval количество секунд между вызовами
run_maintenance функции. По умолчанию 3600 (1 час);
pg_partman_bgw.role роль для запуска run_maintenance функции.
По умолчанию postgres. Разрешена только одна роль;
pg_partman_bgw.analyze запускать или нет ANALYZE после созда-
ния партиций на родительскую таблицу. По умолчанию включено;
pg_partman_bgw.jobmon разрешить или нет использовать
pg_jobmon расширение для мониторинга, что партицирование
работает без проблем. По умолчанию включено;
Далее подключаемся к базе данных и активируем расширение:
Листинг 4.19 Настройка расширения
Line 1 # CREATE SCHEMA partman ;
- CREATE SCHEMA
- # CREATE EXTENSION pg_partman SCHEMA partman ;
- CREATE EXTENSION
Теперь можно приступать к использованию расширения. Создадим и
заполним таблицу тестовыми данными:
Листинг 4.20 Данные
Line 1 # CREATE TABLE u s e rs (
- id s e r i a l primary key ,
- username t e xt not n u l l unique ,
- password text ,
5 created_on timestamptz not n u l l ,
- last_logged_on timestamptz not n u l l
- ) ;
-
- # INSERT INTO u s e r s ( username , password , created_on ,
last_logged_on )
10 SELECT
- md5( random ( ) : : t ex t ) ,
- md5( random ( ) : : t ex t ) ,
- now ( ) - 1 y ears : : i n t e r v a l * random ( ) ,
- now ( ) - 1 y ears : : i n t e r v a l * random ( )
15 FROM
- g e n e r a t e _ se ri es ( 1 , 10000) ;
Далее активируем расширение для поля created_on с партицией на каж-
дый год:
Листинг 4.21 Партицирование
57
4.4. Pg_partman
Line 1 # SELECT partman . c reate_ p arent ( p u bl i c . u s e r s , created_on ,
time , y ea r ly ) ;
- cr e ate_pa r ent
- - - - - - - - - - - - - - - -
- t
5 (1 row )
Указывание схемы в имени таблицы обязательно, даже если она
«public» (первый аргумент функции).
Поскольку родительская таблица уже была заполнена данными, пере-
несем данные из нее в партиции через partition_data_time функцию:
Листинг 4.22 Перенос данных в партиции
Line 1 # SELECT partman . check_parent ( ) ;
- check_parent
- - - - - - - - - - - - - - - - - - - - - - -
- ( p ub l ic . u s e r s , 100 0 0)
5 (1 row )
-
- # SELECT partman . partition_data_ti me ( p u b li c . u s e r s , 1000) ;
- p artition_da ta_time
- - - - - - - - - - - - - - - - - - - - - -
10 10000
- (1 row )
-
- # SELECT partman . check_parent ( ) ;
- check_parent
15 - - - - - - - - - - - - - -
- (0 rows )
-
- # SELECT * FROM ONLY u s e r s ;
- i d | username | password | created_on | last_logged_on
20 - - - - + - - - - - - - - - -+ - - - - - - - - - - + - - - - - - - - - - - -+ - - - - - - - - - - - - - - - -
- (0 rows )
-
- # \d+ u s e r s
-
Table " pu b l i c . u se rs "
25 Column | Type |
M o di fi e r s | S t o rage | S ta t s
t ar g e t | D e sc r i p ti o n
- - -
- - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - -
- i d | i n t e g e r | not n u l l
d e f a u l t n ex t va l ( users_id_seq : : r e g c l a s s ) | p l a i n |
|
58
4.5. Pgslice
- username | t ex t | not n u l l
| extended |
|
- password | t ex t |
| extended |
|
30 created_on | timestamp with time zone | not n u l l
| p l a i n |
|
- last_logged_on | timestamp with time zone | not n u l l
| p l a i n |
|
- I ndexes :
- " users_pkey " PRIMARY KEY, b t re e ( i d )
- " users_username_key" UNIQUE CONSTRAINT, b tre e ( username )
35 T r i g g er s :
- users_part_trig BEFORE INSERT ON u s e r s FOR EACH ROW
EXECUTE PROCEDURE users_part_t rig _fu nc ( )
- Child t a b l e s : users_p2012 ,
- users_p2013 ,
- users_p2014 ,
40 users_p2015 ,
- users_p2016 ,
- users_p2017 ,
- users_p2018 ,
- users_p2019 ,
45 users_p2020
В результате данные в таблице users содержатся в партициях благодаря
pg_partman. Более подробно по функционалу расширения, его настройках
и ограничениях доступно в официальной документации.
4.5 Pgslice
Pgslice утилита для создания и управления партициями в
PostgreSQL. Утилита разбивает на «куски» как новую, так и существую-
щую таблицу с данными c нулевым временем простоя («zero downtime»).
Утилита написана на Ruby, поэтому потребуется сначала установить
его. После этого устанавливаем pgslice через rubygems (многие ruby разра-
ботчики используют bundler для лучшего управления зависимостями, но
в этой главе это не рассматривается):
Листинг 4.23 Установка
Line 1 $ gem i n s t a l l p g s l i c e
Создадим и заполним таблицу тестовыми данными:
59
4.5. Pgslice
Листинг 4.24 Данные
Line 1 # CREATE TABLE u s e rs (
- id s e r i a l primary key ,
- username t e xt not n u l l unique ,
- password text ,
5 created_on timestamptz not n u l l ,
- last_logged_on timestamptz not n u l l
- ) ;
-
- # INSERT INTO u s e r s ( username , password , created_on ,
last_logged_on )
10 SELECT
- md5( random ( ) : : t ex t ) ,
- md5( random ( ) : : t ex t ) ,
- now ( ) + 1 month : : i n t e r v a l * random ( ) ,
- now ( ) + 1 month : : i n t e r v a l * random ( )
15 FROM
- g e n e r a t e _ se ri es ( 1 , 10000) ;
Настройки подключения к базе задаются через PGSLICE_URL пере-
менную окружения:
Листинг 4.25 PGSLICE_URL
Line 1 $ ex p ort PGSLICE_URL=p o s t g r e s : / / username : p a ssword@l o c a lhost /
mydatabase
Через команду pgslice prep <table> <column> <period> создадим таб-
лицу <table>_intermediate (users_intermediate в примере) с соответствую-
щим триггером для разбиения данных, где <table> - это название таб-
лицы (users в примере), <column> - поле, по которому будут создаваться
партиции, а <period> - период данных в партициях (может быть day или
month).
Листинг 4.26 Pgslice prep
Line 1 $ p g s l i c e prep u s e r s created_on month
- BEGIN ;
-
- CREATE TABLE u s e r s_in t e rm ed ia te (LIKE u s e r s INCLUDING ALL) ;
5
- CREATE FUNCTION u s er s _ i n s e r t _ t r ig g e r ( )
- RETURNS t r i g g e r AS $$
- BEGIN
- RAISE EXCEPTION Create p a r t i t i o n s f i r s t . ;
10 END;
60
4.5. Pgslice
- $$ LANGUAGE p lp g s q l ;
-
- CREATE TRIGGER u s e r s _ i n se r t _ t r i g g e r
- BEFORE INSERT ON u s ers_ i n te rm ed ia te
15 FOR EACH ROW EXECUTE PROCEDURE u s er s_ i n s e r t _ t r ig ge r ( ) ;
-
- COMMENT ON TRIGGER u s e r s _ i n se r t _ t r i g g e r ON
us e r s_ in te rmed i a te i s column : created_on , p eri o d : month ,
c a s t : timestamptz ;
-
- COMMIT;
Теперь можно добавить партиции:
Листинг 4.27 Pgslice add_partitions
Line 1 $ p g s l i c e add_ p a r t i t i o ns u se rs - - i nt e rm e di a te - - p ast 3 - -
f u t u r e 3
- BEGIN ;
-
- CREATE TABLE users_201611
5 (CHECK ( created_on >= 2016 -11 -01 0 0: 00 :0 0 UTC : :
timestamptz AND created_on < 2016 -12 -01 0 0 : 0 0 : 00 UTC : :
timestamptz ) )
- INHERITS ( u s ers_ i n te rm ed ia te ) ;
-
- ALTER TABLE users_201611 ADD PRIMARY KEY ( id ) ;
-
10 . . .
-
- CREATE OR REPLACE FUNCTION u s e r s _ i n s er t_ t r i g g e r ( )
- RETURNS t r i g g e r AS $$
- BEGIN
15 IF (NEW. created_on >= 2017 -02 -01 0 0 : 0 0 : 0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -03 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
- INSERT INTO users_201702 VALUES (NEW. * ) ;
- ELSIF (NEW. created_on >= 2017 -03 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -04 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
- INSERT INTO users_201703 VALUES (NEW. * ) ;
- ELSIF (NEW. created_on >= 2017 -04 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -05 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
20 INSERT INTO users_201704 VALUES (NEW. * ) ;
- ELSIF (NEW. created_on >= 2017 -05 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -06 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
61
4.5. Pgslice
- INSERT INTO users_201705 VALUES (NEW. * ) ;
- ELSIF (NEW. created_on >= 2017 -01 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -02 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
- INSERT INTO users_201701 VALUES (NEW. * ) ;
25 ELSIF (NEW. created_on >= 2016 -12 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2017 -01 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
- INSERT INTO users_201612 VALUES (NEW. * ) ;
- ELSIF (NEW. created_on >= 2016 -11 -01 0 0: 00 :0 0 UTC : :
timestamptz AND NEW. created_on < 2016 -12 -01 0 0: 00 :0 0 UTC
: : timestamptz ) THEN
- INSERT INTO users_201611 VALUES (NEW. * ) ;
- ELSE
30 RAISE EXCEPTION Date out o f range . Ensure
p a r t i t i o n s are c r ea t ed . ;
- END IF ;
- RETURN NULL;
- END;
- $$ LANGUAGE p lp g s q l ;
35
- COMMIT;
Через -- past и -- future опции указывается количество партиций. Далее
можно переместить данные в партиции:
Листинг 4.28 Pgslice fill
Line 1 $ p g s l i c e f i l l u se r s
- /* 1 o f 1 */
- INSERT INTO u s e rs _i nt er medi a t e ( " i d " , " username " , " password "
, " created_on " , " last_logged_on " )
- SELECT " id " , " username " , " password " , " created_on " , "
last_logged_on " FROM u s e r s
5 WHERE i d > 0 AND i d <= 10000 AND created_on >=
2016 -11 -01 0 0 : 0 0 : 0 0 UTC : : timestamptz AND created_on <
2017 -06 -01 0 0 : 0 0 : 0 0 UTC : : timestamptz
Через -- batch-size и -- sleep опции можно управлять скоростью переноса
данных.
После этого можно переключиться на новую таблицу с партициями:
Листинг 4.29 Pgslice swap
Line 1 $ p g s l i c e swap u s e rs
- BEGIN ;
-
- SET LOCAL lock_timeout = 5 s ;
62
4.5. Pgslice
5
- ALTER TABLE u s e r s RENAME TO u s e r s_ r e t i r e d ;
-
- ALTER TABLE u sers _ i nt er me di ate RENAME TO u s e r s ;
-
10 ALTER SEQUENCE users_id_seq OWNED BY u s e r s . i d ;
-
- COMMIT;
Если требуется, то можно перенести часть данных, что накопилась
между переключением таблиц:
Листинг 4.30 Pgslice fill
Line 1 $ p g s l i c e f i l l u se r s - - swapped
В результате таблица users будет работать через партиции:
Листинг 4.31 Результат
Line 1 $ p s ql - c "EXPLAIN SELECT * FROM u se rs "
- QUERY PLAN
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Append ( c os t = 0 . 0 0 .. 33 0. 0 0 rows =13601 width =86)
5 -> Seq Scan on u s e r s ( c o s t = 0 . 0 0 . . 0 . 0 0 rows=1 width =84)
- -> Seq Scan on users_201611 ( c o s t = 0 . 0 0 . . 1 7 . 20 rows=720
width =84)
- -> Seq Scan on users_201612 ( c o s t = 0 . 0 0 . . 1 7 . 20 rows=720
width =84)
- -> Seq Scan on users_201701 ( c o s t = 0 . 0 0 . . 1 7 . 20 rows=720
width =84)
- -> Seq Scan on users_201702 ( c o s t = 0. 00 . . 1 6 6 . 4 8 rows
=6848 width =86)
10 -> Seq Scan on users_201703 ( c o s t = 0 . 0 0 . . 7 7 . 52 rows=3152
width =86)
- -> Seq Scan on users_201704 ( c o s t = 0 . 0 0 . . 1 7 . 20 rows=720
width =84)
- -> Seq Scan on users_201705 ( c o s t = 0 . 0 0 . . 1 7 . 20 rows=720
width =84)
- (9 rows )
Старая таблица теперь будет называться <table>_retired (users_retired
в примере). Её можно оставить или удалить из базы.
Листинг 4.32 Удаление старой таблицы
Line 1 $ pg_dump - c - Fc - t u se r s _ r e t i r e d $PGSLICE_URL >
u s e rs _ r e t i r e d . dump
63
4.6. Заключение
- $ p s q l - c "DROP u s e r s _ r e ti r e d " $PGSLICE_URL
Далее только требуется следить за количеством партиций. Для этого
команду pgslice add_partitions можно добавить в cron:
Листинг 4.33 Cron
Line 1 # day
- 0 0 * * * p g s l i c e add_ p a r t i t i o ns <tab l e > - - f ut ur e 3 - - u r l
. . .
-
- # month
5 0 0 1 * * p g s l i c e add_ p a r t i t i o ns <tab l e > - - f ut ur e 3 - - u r l
. . .
4.6 Заключение
Партиционирование одна из самых простых и менее безболезненных
методов уменьшения нагрузки на СУБД. Именно на этот вариант стоит
посмотреть сперва, и если он не подходит по каким либо причинам —
переходить к более сложным.
64
5
Репликация
Когда решаете проблему, ни о
чем не беспокойтесь. Вот
когда вы её решите, тогда и
наступит время беспокоиться
Ричард Филлипс Фейнман
5.1 Введение
Репликация (англ. replication) механизм синхронизации содержимо-
го нескольких копий объекта (например, содержимого базы данных). Ре-
пликация это процесс, под которым понимается копирование данных из
одного источника на множество других и наоборот. При репликации из-
менения, сделанные в одной копии объекта, могут быть распространены в
другие копии. Репликация может быть синхронной или асинхронной.
В случае синхронной репликации, если данная реплика обновляется,
все другие реплики того же фрагмента данных также должны быть об-
новлены в одной и той же транзакции. Логически это означает, что су-
ществует лишь одна версия данных. В большинстве продуктов синхрон-
ная репликация реализуется с помощью триггерных процедур (возможно,
скрытых и управляемых системой). Но синхронная репликация имеет тот
недостаток, что она создаёт дополнительную нагрузку при выполнении
всех транзакций, в которых обновляются какие-либо реплики (кроме то-
го, могут возникать проблемы, связанные с доступностью данных).
В случае асинхронной репликации обновление одной реплики распро-
страняется на другие спустя некоторое время, а не в той же транзакции.
Таким образом, при асинхронной репликации вводится задержка, или вре-
мя ожидания, в течение которого отдельные реплики могут быть факти-
чески неидентичными (то есть определение реплика оказывается не со-
всем подходящим, поскольку мы не имеем дело с точными и своевременно
65
5.1. Введение
созданными копиями). В большинстве продуктов асинхронная реплика-
ция реализуется посредством чтения журнала транзакций или постоян-
ной очереди тех обновлений, которые подлежат распространению. Пре-
имущество асинхронной репликации состоит в том, что дополнительные
издержки репликации не связаны с транзакциями обновлений, которые
могут иметь важное значение для функционирования всего предприятия
и предъявлять высокие требования к производительности. К недостаткам
этой схемы относится то, что данные могут оказаться несовместимыми
(то есть несовместимыми с точки зрения пользователя). Иными словами,
избыточность может проявляться на логическом уровне, а это, строго го-
воря, означает, что термин контролируемая избыточность в таком случае
не применим.
Рассмотрим кратко проблему согласованности (или, скорее, несогласо-
ванности). Дело в том, что реплики могут становиться несовместимыми
в результате ситуаций, которые трудно (или даже невозможно) избежать
и последствия которых трудно исправить. В частности, конфликты могут
возникать по поводу того, в каком порядке должны применяться обновле-
ния. Например, предположим, что в результате выполнения транзакции
А происходит вставка строки в реплику X, после чего транзакция B уда-
ляет эту строку, а также допустим, что Y реплика X. Если обновления
распространяются на Y, но вводятся в реплику Y в обратном порядке (на-
пример, из-за разных задержек при передаче), то транзакция B не находит
в Y строку, подлежащую удалению, и не выполняет своё действие, после
чего транзакция А вставляет эту строку. Суммарный эффект состоит в
том, что реплика Y содержит указанную строку, а реплика X нет.
В целом задачи устранения конфликтных ситуаций и обеспечения со-
гласованности реплик являются весьма сложными. Следует отметить, что,
по крайней мере, в сообществе пользователей коммерческих баз данных
термин репликация стал означать преимущественно (или даже исключи-
тельно) асинхронную репликацию.
Основное различие между репликацией и управлением копированием
заключается в следующем: если используется репликация, то обновление
одной реплики в конечном счёте распространяется на все остальные авто-
матически. В режиме управления копированием, напротив, не существу-
ет такого автоматического распространения обновлений. Копии данных
создаются и управляются с помощью пакетного или фонового процесса,
который отделён во времени от транзакций обновления. Управление ко-
пированием в общем более эффективно по сравнению с репликацией, по-
скольку за один раз могут копироваться большие объёмы данных. К недо-
статкам можно отнести то, что большую часть времени копии данных не
идентичны базовым данным, поэтому пользователи должны учитывать,
когда именно были синхронизированы эти данные. Обычно управление
копированием упрощается благодаря тому требованию, чтобы обновления
применялись в соответствии со схемой первичной копии того или иного
66
5.2. Потоковая репликация (Streaming Replication)
вида.
Для репликации PostgreSQL существует несколько решений, как за-
крытых, так и свободных. Закрытые системы репликации не будут рас-
сматриваться в этой книге. Вот список свободных решений:
Slony-I асинхронная Master-Slave репликация, поддержива-
ет каскады(cascading) и отказоустойчивость(failover). Slony-I
использует триггеры PostgreSQL для привязки к событиям
INSERT/DELETE/UPDATE и хранимые процедуры для вы-
полнения действий;
Pgpool-I/II это замечательный инструмент для PostgreSQL (лучше
сразу работать с II версией). Позволяет делать:
– репликацию том числе, с автоматическим переключением на
резервный stand-by сервер);
– online-бэкап;
– pooling коннектов;
– очередь соединений;
– балансировку SELECT-запросов на несколько postgresql-
серверов;
– разбиение запросов для параллельного выполнения над боль-
шими объемами данных;
Bucardo асинхронная репликация, которая поддерживает Multi-
Master и Master-Slave режимы, а также несколько видов синхрони-
зации и обработки конфликтов;
Londiste асинхронная Master-Slave репликация. Входит в состав
Skytools. Проще в использовании, чем Slony-I;
Mammoth Replicator асинхронная Multi-Master репликация;
BDR (Bi-Directional Replication) асинхронная Multi-Master репли-
кация;
Pglogical асинхронная Master-Slave репликация;
Это, конечно, не весь список свободных систем для репликации, но
даже из этого есть что выбрать для PostgreSQL.
5.2 Потоковая репликация (Streaming Replication)
Потоковая репликация (Streaming Replication, SR) дает возможность
непрерывно отправлять и применять WAL (Write-Ahead Log) записи на
резервные сервера для создания точной копии текущего. Данная функ-
циональность появилась у PostgreSQL начиная с 9 версии. Этот тип ре-
пликации простой, надежный и, вероятней всего, будет использоваться
в качестве стандартной репликации в большинстве высоконагруженных
приложений, что используют PostgreSQL.
Отличительными особенностями решения являются:
67
5.2. Потоковая репликация (Streaming Replication)
репликация всего инстанса PostgreSQL;
асинхронный или синхронный механизм репликации;
простота установки;
мастер база данных может обслуживать огромное количество слей-
вов из-за минимальной нагрузки;
К недостаткам можно отнести:
невозможность реплицировать только определенную базу данных из
всех на PostgreSQL инстансе;
Установка
Для начала нам потребуется PostgreSQL не ниже 9 версии. Все работы,
как полагается, будут проводится на Linux.
Настройка
Обозначим мастер сервер как masterdb(192.168.0.10) и слейв как slavedb
(192.168.0.20).
Предварительная настройка
Для начала позволим определенному пользователю без пароля ходить
по ssh. Пусть это будет postgres юзер. Если же нет, то создаем набором
команд:
Листинг 5.1 Создаем пользователя userssh
Line 1 $ sudo groupadd u se rs s h
- $ sudo useradd -m - g u se r s s h - d /home/ u se rs s h - s / bin / bash \
- - c " user ssh a l lo w " u s er s s h
Дальше выполняем команды от имени пользователя данном случае
postgres):
Листинг 5.2 Логинимся под пользователем postgres
Line 1 $ su p o s t g r e s
Генерируем RSA-ключ для обеспечения аутентификации в условиях
отсутствия возможности использовать пароль:
Листинг 5.3 Генерируем RSA-ключ
Line 1 $ ssh - keygen - t r s a -P ""
- Ge nerating p u b li c / p r i v a te r sa key p a i r .
68
5.2. Потоковая репликация (Streaming Replication)
- Enter f i l e i n which to sav e the key (/ var / l i b / p o s t g r e s q l / .
ssh / id_rsa ) :
- Created d i r e c t o r y / var / l i b / p o s t g r e s q l / . ssh .
5 Your i d e n t i f i c a t i o n has been saved i n / var / l i b / p o s t g r e s q l / .
ssh / id_rsa .
- Your p ub l ic key has been saved in / var / l i b / p o s t g r e s q l / . s sh /
id_rsa . pub .
- The key f i n g e r p r i n t i s :
- 1 6 : 0 8 : 2 7 : 9 7 : 2 1 : 3 9 : b5 : 7 b : 8 6 : e1 : 4 6 : 9 7 : bf : 1 2 : 3 d : 76
p o s t g r e s @ l o c a l h o s t
И добавляем его в список авторизованных ключей:
Листинг 5.4 Добавляем его в список авторизованных ключей
Line 1 $ cat $HOME/ . s s h / id_rsa . pub >> $HOME/ . ssh / au tho riz ed_ keys
Проверить работоспособность соединения можно просто написав:
Листинг 5.5 Пробуем зайти на ssh без пароля
Line 1 $ ss h l o c a l h o s t
Не забываем предварительно инициализировать sshd:
Листинг 5.6 Запуск sshd
Line 1 $ $/ e tc / i n i t . d/ sshd s t a r t
После успешно проделанной операции скопируйте $HOME/.ssh на
slavedb. Теперь мы должны иметь возможность без пароля заходить с ма-
стера на слейв и со слейва на мастер через ssh.
Также отредактируем pg_hba.conf на мастере и слейве, разрешив им
друг к другу доступ без пароля (тут добавляется роль replication ):
Листинг 5.7 Мастер pg_hba.conf
Line 1 h o st r e p l i c a t i o n a l l 19 2. 1 6 8 . 0 .2 0/ 3 2 t r u s t
Листинг 5.8 Слейв pg_hba.conf
Line 1 h o st r e p l i c a t i o n a l l 19 2. 1 6 8 . 0 .1 0/ 3 2 t r u s t
Не забываем после этого перегрузить postgresql на обоих серверах.
Настройка мастера
Для начала настроим masterdb. Установим параметры в postgresql .conf
для репликации:
69
5.2. Потоковая репликация (Streaming Replication)
Листинг 5.9 Настройка мастера
Line 1 # To enable read - onl y q u e r i e s on a standby s e rve r , wal_le v e l
must be s et to
- # "hot_standby" . But you can choose " a r ch i v e " i f you n ever
co nnect to the
- # s e r ve r in standby mode .
- w a l_level = hot_standby
5
- # Set the maximum number o f co n c u rr e nt c o nne c ti o n s from the
standby s e r v e r s .
- max_wal_senders = 5
-
- # To preven t the primary s e r v e r from removing the WAL
segments r eq u i r e d f o r
10 # the standby s e r v e r b e f o r e s hip p i ng them , s e t the minimum
number o f segments
- # r e t a i n e d i n the pg_xlog d i r e c t o r y . At l e a s t
wal_keep_segments shou ld be
- # l a r g e r than the number o f segments g e n e r a t e d between the
beginning o f
- # on l i ne - backup and the s t ar t u p o f s tream i ng r e p l i c a t i o n . I f
you enable WAL
- # a r c h i v i n g to an a r ch i ve d i r e c t o r y a c c e s s i b l e from the
standby , t h i s may
15 # not be n e ce s s ar y .
- wal_keep_segments = 32
-
- # Enable WAL a r c h i v i n g on the primary to an a r ch i v e
d i r e c t o r y a c c e s s i b l e from
- # the standby . I f wal_keep_segments i s a high enough number
to r e t a i n the WAL
20 # segments r eq ui r e d f o r the standby s e r ver , t h i s may not be
n e ce s sa r y .
- archive_mode = on
- archive_command = cp %p /path_to/ a r c hi v e/%f
Давайте по порядку:
wal_level = hot_standby сервер начнет писать в WAL логи так же
как и при режиме «archiv, добавляя информацию, необходимую
для восстановления транзакции (можно также поставить archive, но
тогда сервер не может быть слейвом при необходимости);
max_wal_senders = 5 максимальное количество слейвов;
wal_keep_segments = 32 минимальное количество файлов c WAL
сегментами в pg_xlog директории;
70
5.2. Потоковая репликация (Streaming Replication)
archive_mode = on позволяем сохранять WAL сегменты в указанное
переменной archive_command хранилище. В данном случае в дирек-
торию /path/to/archive/;
По умолчанию репликация асинхронная. В версии 9.1 добавили пара-
метр synchronous_standby_names, который включает синхронную реплика-
цию. В данные параметр передается application_name, который использу-
ется на слейвах в recovery.conf:
Листинг 5.10 recovery.conf для синхронной репликации на слейве
Line 1 restore_command = cp /mnt/ s e r v e r / a r c h i v e d i r/%f %p
# e . g . cp /mnt/ s e r v e r / a r c h i v e d i r/%f %p
- standby_mode = on
- primary_conninfo = host=masterdb por t =59121 u se r=
r e p l i c a t i o n password=r e p l i c a t i o n application_name=
ne w clu s t er # e . g . h o st=l o c a l h o s t p o rt =5432
- t r i g g e r _ f i l e = /tmp/ tr ig _ f _n e wc l us t e r
После изменения параметров перегружаем PostgreSQL сервер. Теперь
перейдем к slavedb.
Настройка слейва
Для начала нам потребуется создать на slavedb точную копию masterdb.
Перенесем данные с помощью «Онлайн бэкапа».
Переместимся на masterdb сервер и выполним в консоли:
Листинг 5.11 Выполняем на мастере
Line 1 $ p s ql - c "SELECT pg_start_backup ( l a b el , t r u e ) "
Теперь нам нужно перенести данные с мастера на слейв. Выполняем
на мастере:
Листинг 5.12 Выполняем на мастере
Line 1 $ rs y n c -C - a - - d e l e t e - e ss h - - exclude p o s t g r e s q l . c o n f - -
exclud e pos tmas ter . pid \
- - - e x c l ud e pos tmast er . opt s - - e x c lu d e pg_log - - exclude pg_xlog
\
- - - e x c l ud e r eco v er y . c o n f master_db_datadir / slavedb_host :
slave_db_datadir /
где
master_db_datadir директория с postgresql данными на masterdb;
slave_db_datadir директория с postgresql данными на slavedb;
71
5.2. Потоковая репликация (Streaming Replication)
slavedb_host хост slavedb(в нашем случае - 192.168.1.20);
После копирования данных с мастера на слейв, остановим онлайн
бэкап. Выполняем на мастере:
Листинг 5.13 Выполняем на мастере
Line 1 $ p s ql - c "SELECT pg_stop_backup ( ) "
Для версии PostgreSQL 9.1+ можно воспользоваться командой
pg_basebackup (копирует базу на slavedb подобным образом):
Листинг 5.14 Выполняем на слейве
Line 1 $ pg_basebackup -R -D / srv / pg s q l / standby - - host = 1 9 2 . 1 68 . 0. 1 0
- - port =5432
Устанавливаем такие же данные в конфиге postgresql .conf, что и у ма-
стера (чтобы при падении мастера слейв мог его заменить). Так же уста-
новим дополнительный параметр:
Листинг 5.15 Конфиг слейва
Line 1 hot_standby = on
Внимание! Если на мастере поставили wal_level = archive, тогда пара-
метр оставляем по умолчанию (hot_standby = off).
Далее на slavedb в директории с данными PostgreSQL создадим файл
recovery.conf с таким содержимым:
Листинг 5.16 Конфиг recovery.conf
Line 1 # S p e c i f i e s whether to s t a r t the s e r v e r as a standby . In
str eamin g r e p l i c a t i o n ,
- # t h i s parameter must to be s e t to on .
- standby_mode = on
-
5 # S p e c i f i e s a c o n n e ct i on s t r i n g which i s used f o r the
standby s e r v e r t o con nect
- # with the primary .
- primary_conninfo = h ost = 1 9 2 .1 6 8. 0 . 10 p ort =5432 use r=
p o s t g r e s
-
- # S p e c i f i e s a t r i g g e r f i l e whose p r e se nce should cau se
str eamin g r e p l i c a t i o n to
10 # end ( i . e . , f a i l o v e r ) .
- t r i g g e r _ f i l e = / path_to/ t r i g g e r
-
72
5.2. Потоковая репликация (Streaming Replication)
- # S p e c i f i e s a command to load a r ch iv e segments from the WAL
a r ch i v e . I f
- # wal_keep_segments i s a high enough number to r e t a i n the
WAL segments
15 # r e q ui r e d f o r the standby s e r ver , t h i s may not be n ec e ssa r y
. But
- # a l a r g e workload can ca use segments to be r e c y c l e d b e f o r e
the standby
- # i s f u l l y syn chron ized , r e q u i r i n g you to s t a r t a g ain from a
new base backup .
- restore_command = scp masterdb_host : / path_to / a rc h iv e/%f "%p
"
где
standby_mode=’on’ указываем серверу работать в режиме слейв;
primary_conninfo настройки соединения слейва с мастером;
trigger_file указываем триггер файл, при наличии которого будет
остановлена репликация;
restore_command команда, которой будут восстанавливаться WAL
логи. В нашем случае через scp копируем с masterdb (masterdb_host
- хост masterdb);
Теперь можем запустить PostgreSQL на slavedb.
Тестирование репликации
В результате можем посмотреть отставание слейвов от мастера с помо-
щью таких команд:
Листинг 5.17 Тестирование репликации
Line 1 $ p s ql - c "SELECT pg_ current_x log_location ( ) " - h192 . 1 6 8 . 0 . 1 0
( masterdb )
- pg_ current_xlog_locat ion
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- 0/2000000
5 (1 row )
-
- $ p s q l - c " s e l e c t p g _last_xlo g _ r e ceive_loca t i o n ( ) " - h192
. 1 6 8 . 0 . 2 0 ( s l a v e d b )
- pg_l a s t _ xlog_rece i v e _ l ocation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 0/2000000
- (1 row )
-
- $ p s q l - c " s e l e c t pg_ las t_xlog_replay _lo cation ( ) " - h192
. 1 6 8 . 0 . 2 0 ( s l a v e d b )
73
5.2. Потоковая репликация (Streaming Replication)
- pg_last_xlog_r epl ay_locatio n
15 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 0/2000000
- (1 row )
Начиная с версии 9.1 добавили дополнительные view для просмотра
состояния репликации. Теперь master знает все состояния slaves:
Листинг 5.18 Состояние слейвов
Line 1 # SELECT * from p g _ sta t _ re pli c a tio n ;
- p r o c p id | u s e s ys id | usename | application_name |
clie n t _ a d d r | c lient_hos tname | c l ie n t _ po r t |
backend_start | s t a t e | s ent _ lo c a ti o n |
w r it e _l o c at i on | f l u s h _l oc a t i o n | r ep l ay _ lo c ati o n |
s y n c_ p rio r ity | s ync _st ate
- - -
- - - - - - - + - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - -+ - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ - - - - - - - - - - -+ - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - -+ - - - - - - - - - - - -
- 17135 | 16671 | r e p l i c a t i o n | ne w c lus t e r |
1 2 7 . 0 . 0 . 1 | | 43745 | 2011 -05 -22
18:13:0 4 . 1 9 2 8 3 + 0 2 | s tream ing | 1/30008750 |
1/30008750 | 1/30008750 | 1/30008750 |
1 | sync
Также с версии 9.1 добавили view pg_stat_database_conflicts, с помо-
щью которой на слейв базах можно просмотреть сколько запросов было
отменено и по каким причинам:
Листинг 5.19 Состояние слейва
Line 1 # SELECT * from p g _ s t at_databas e _ c o n flicts ;
- da t i d | datname | co n f l _ t a bl e sp a ce | c o nfl _ lo ck |
con f l_snap s h ot | c o n f l _ b u f f e r p i n | c o n f l _ deadlock
- - -
- - - - - + - - - - - - - - - - -+ - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -+ - - - - - - - - - - - - - - - -+ - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - -
- 1 | template 1 | 0 | 0 |
0 | 0 | 0
5 11979 | te mplate0 | 0 | 0 |
0 | 0 | 0
- 11987 | p o s t g r e s | 0 | 0 |
0 | 0 | 0
- 16384 | marc | 0 | 0 |
1 | 0 | 0
Еще проверить работу репликации можно с помощью утилиты ps:
74
5.2. Потоковая репликация (Streaming Replication)
Листинг 5.20 Тестирование репликации
Line 1 [ masterdb ] $ ps - e f | grep s e n d e r
- p o s t g r e s 6879 6831 0 10:31 ? 00 :0 0: 00 p o s t g r e s :
wal s e n d e r p ro c e s s p o s t g r e s 1 2 7 . 0 . 0 . 1 ( 4 4 6 6 3 ) s t reami ng
0/2000000
-
- [ slav e d b ] $ ps - e f | grep r e c e i v e r
5 p o s t g r e s 6878 6872 1 10:31 ? 00 :0 0: 01 p o s t g r e s :
wal r e c e i v e r p r oc es s str e aming 0/2000000
репликацию
Давайте проверим репликацию и выполним на мастере:
Листинг 5.21 Выполняем на мастере
Line 1 $ p s ql test_db
- test_db=# c r e a t e t ab le t e s t 3 ( i d i n t not n u l l primary key ,
name varch a r ( 20) ) ;
- NOTICE: CREATE TABLE / PRIMARY KEY w i l l c r e a t e i m p l i c i t
in dex " test3_pkey " f o r t ab le " t e s t 3 "
- CREATE TABLE
5 test_db=# i n s e r t i nt o t e s t 3 ( id , name ) v a lu e s ( 1 , t e s t 1 ) ;
- INSERT 0 1
- test_db=#
Теперь проверим на слейве результат:
Листинг 5.22 Выполняем на слейве
Line 1 $ p s ql test_db
- test_db=# s e l e c t * from t e s t 3 ;
- i d | name
- - - - -+ - - - - - - -
5 1 | t e s t 1
- (1 row )
Как видим, таблица с данными успешно скопирована с мастера на
слейв. Более подробно по настройке данной репликации можно почитать
из официальной wiki.
Общие задачи
Переключение на слейв при падении мастера
Достаточно создать триггер файл ( trigger_file ) на слейве, который пе-
рестанет читать данные с мастера.
75
5.2. Потоковая репликация (Streaming Replication)
Остановка репликации на слейве
Создать триггер файл ( trigger_file ) на слейве. Также с версии 9.1 доба-
вили функции pg_xlog_replay_pause() и pg_xlog_replay_resume() для оста-
новки и возобновления репликации.
Перезапуск репликации после сбоя
Повторяем операции из раздела «Настройка слейва». Хочется заме-
тить, что мастер при этом не нуждается в остановке при выполнении дан-
ной задачи.
Перезапуск репликации после сбоя слейва
Перезагрузить PostgreSQL на слейве после устранения сбоя.
Повторно синхронизировать репликации на слейве
Это может потребоваться, например, после длительного отключения
от мастера. Для этого останавливаем PostgreSQL на слейве и повторяем
операции из раздела «Настройка слейва».
Repmgr
Repmgr набор инструментов для управления потоковой репликацией
и восстановления после сбоя кластера PostgreSQL серверов. Он автомати-
зирует настройку резервных серверов, мониторинг репликации, а также
помогает выполнять задачи администрированию кластера, такие как отка-
зоустойчивость (failover) или переключение мастера-слейва (слейв стано-
вится мастером, а мастер - слейвом). Repmgr работает с версии PostgreSQL
9.3 и выше.
Repmgr состоит из двух утилит:
repmgr инструмент командной строки (cli), который используется
для административных задач, таких как:
– создание слейвов;
– переключение слейва в режим мастера;
– переключение между собой мастер и слейв серверов;
– отображение состояния кластера;
repmgrd демон, который мониторит кластер серверов и выполняет
такие задачи:
– мониторинг и логирование эффективности репликации;
– автоматическое переключение слейва в мастер при обнаруже-
нии проблем у текущего мастера (failover);
– посылка сообщений о событиях в кластере через заданые поль-
зователем скрипты;
76
5.2. Потоковая репликация (Streaming Replication)
Пример использования: автоматическое переключение слейва в мастер
Для использования failover потребуется добавить repmgr_funcs в
postgresql .conf:
Листинг 5.23 repmgr_funcs
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = repmgr_funcs
И добавить настройки в repmgr.conf:
Листинг 5.24 repmgr.conf
Line 1 f a i l o v e r=automatic
- promote_command= repmgr standby promote - f / e tc /repmgr . conf
- - log - to - f i l e
- follow_command= repmgr standby f o l l o w - f / e t c / repmgr . c o n f - -
log - to - f i l e
Для демонстрации автоматического failover, настроен кластер с тремя
узлами репликации дин мастер и два слейв сервера), так что таблица
repl_nodes выглядит следующим образом:
Листинг 5.25 repl_nodes
Line 1 # SELECT id , type , upstream_node_id , p r i o r i t y , a c t i v e FROM
repmgr_test . repl_nodes ORDER BY i d ;
- i d | type | upstream_node_id | p r i o r i t y | a c t i v e
- - - - - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - -+ - - - - - - - -
- 1 | master | | 100 | t
5 2 | standby | 1 | 100 | t
- 3 | standby | 1 | 100 | t
- (3 rows )
После запуска repmgrd демона на каждом сервере в режиме ожидания,
убеждаемся что он мониторит кластер:
Листинг 5.26 logs
Line 1 [2 01 6 -01 - 0 5 1 3 : 1 5 : 4 0 ] [ INFO ] checking c l u s t e r c o n f i g u r a t i o n
with schema repmgr_test
- [ 2 0 1 6 -01 - 0 5 1 3 : 1 5 : 4 0 ] [ INFO] c h e c k i n g node 2 in c l u s t e r
t e s t
- [ 2 0 1 6 -01 - 0 5 1 3 : 1 5 : 4 0 ] [ INFO] r e l o a d in g c o n f i g u r a t i o n f i l e
and updating repmgr t a b l e s
- [ 2 0 1 6 -01 - 0 5 1 3 : 1 5 : 4 0 ] [ INFO] s t a r t i n g continuous standby
node monit oring
Теперь остановим мастер базу:
77
5.2. Потоковая репликация (Streaming Replication)
Листинг 5.27 Остановка текущего мастера
Line 1 pg_ctl -D / path/ to / node1 / data -m immediate s t op
repmgrd автоматически замечает падение мастера и переключает один
из слейвов в мастер:
Листинг 5.28 Переключение слейва в мастер
Line 1 [2 01 6 -01 - 0 6 1 8 : 3 2 : 5 8 ] [WARNING] c on n ec ti o n to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 15 seconds b e f or e
f a i l o v e r d e c i s i o n
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 0 3 ] [WARNING] co n n e ct i on to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 10 seconds b e f or e
f a i l o v e r d e c i s i o n
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 0 8 ] [WARNING] co n n e ct i on to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 5 se c o nds b e f o r e f a i l o v e r
d e c i s i o n
- . . .
5 [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 1 8 ] [NOTICE] t h i s node i s the b es t
candidate to be the new master , promoting . . .
- . . .
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 2 0 ] [NOTICE] STANDBY PROMOTE s u c c e s s f u l
Также переключает оставшийся слейв на новый мастер:
Листинг 5.29 Переключение слейва на новый мастер
Line 1 [2 01 6 -01 - 0 6 1 8 : 3 2 : 5 8 ] [WARNING] c on n ec ti o n to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 15 seconds b e f or e
f a i l o v e r d e c i s i o n
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 0 3 ] [WARNING] co n n e ct i on to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 10 seconds b e f or e
f a i l o v e r d e c i s i o n
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 0 8 ] [WARNING] co n n e ct i on to upstream has
been l o s t , t r y i n g to r ec o v e r . . . 5 se c o nds b e f o r e f a i l o v e r
d e c i s i o n
- . . .
5 [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 2 3 ] [NOTICE] node 2 i s the b e s t c a n d i d a t e
f o r new master , attemp ting to f o l l o w . . .
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 2 3 ] [ INFO] changing standby s master
- . . .
- [ 2 0 1 6 -01 - 0 6 1 8 : 3 3 : 2 5 ] [NOTICE] node 3 now f o l l o w i n g new
upstream node 2
Таблица repl_nodes будет обновлена, чтобы отразить новую ситуацию
старый мастер node1 помечен как неактивный, и слейв node3 теперь рабо-
тает от нового мастера node2:
78
5.2. Потоковая репликация (Streaming Replication)
Листинг 5.30 Результат после failover
Line 1 # SELECT id , type , upstream_node_id , p r i o r i t y , a c t i v e from
repl_nodes ORDER BY i d ;
- i d | type | upstream_node_id | p r i o r i t y | a c t i v e
- - - - - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - -+ - - - - - - - -
- 1 | master | | 100 | f
5 2 | master | | 100 | t
- 3 | standby | 2 | 100 | t
- (3 rows )
В таблицу repl_events будут добавлены записи того, что произошло с
каждым сервером во время failover:
Листинг 5.31 Результат после failover
Line 1 # SELECT node_id , event , s u c c e s s f u l , d e t a i l s from
repmgr_test . r e p l _ e v e n t s where event_timestamp>=
2016 -01 -06 1 8 :3 0 ;
- node_id | event | s u c c e s s f u l |
d e t a i l s
- - -
- - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 2 | standby_promote | t | node 2
was s u c c e s s f u l l y promoted to master
5 2 | repmgrd_failover_promote | t | node 2
promoted to master ; o l d master 1 marked as f a i l e d
- 3 | r e pmgrd_failov e r _ f o l low | t | node 3
now f o l l o w i n g new upstream node 2
- (3 rows )
Заключение
Более подробно по функционалу, его настройках и ограничениях до-
ступно в официальном репозитории.
Patroni
Patroni это демон на Python, позволяющий автоматически обслужи-
вать кластеры PostgreSQL с потоковой репликацией.
Особенности:
Использует потоковую PostgreSQL репликацию (асинхронная и син-
хронная репликация);
Интеграция с Kubernetes;
79
5.3. PostgreSQL Bi-Directional Replication (BDR)
Поддержания актуальности кластера и выборов мастера использу-
ются распределенные DCS хранилища (поддерживаются Zookeeper,
Etcd или Consul);
Автоматическое «service discovery» и динамическая реконфигурация
кластера;
Состояние кластера можно получить как запросами в DCS, так и
напрямую к Patroni через HTTP запросы;
Информация по настройке и использованию Patroni находится в офи-
циальной документации проекта.
Stolon
Stolon это демон на Go, позволяющий автоматически обслуживать
кластеры PostgreSQL с потоковой репликацией.
Особенности:
Использует потоковую PostgreSQL репликацию (асинхронная и син-
хронная репликация);
Интеграция с Kubernetes;
Поддержания актуальности кластера и выборов мастера использу-
ются распределенные DCS хранилища (поддерживаются Etcd или
Consul);
Автоматическое «service discovery» и динамическая реконфигурация
кластера;
Состояние кластера можно получить как запросами в DCS, так и
через stolonctl клиент;
Stolon состоит из 3 основных компонентов:
keeper (хранитель): управляет экземпляром PostgreSQL;
sentinel: обнаруживает и контролирует keeper-ров и вычисляет опти-
мальное состояние кластера;
proxy: точка доступа клиента, обеспечивает подключение к верному
PostgreSQL мастеру в кластере;
Информация по настройке и использованию Stolon находится в офи-
циальной документации проекта.
5.3 PostgreSQL Bi-Directional Replication (BDR)
BDR (Bi-Directional Replication) это новая функциональность добав-
ленная в ядро PostgreSQL которая предоставляет расширенные средства
для репликации. На данный момент это реализовано в виде небольшого
патча и модуля для 9.4 версии. Заявлено что полностью будет только в
80
5.3. PostgreSQL Bi-Directional Replication (BDR)
Рис. 5.1: Stolon архитектура
PostgreSQL 9.6 (разработчики решили не заниматься поддержкой патча
для 9.5, а сосредоточиться на добавление патчей в сам PostgreSQL). BDR
позволяет создавать географически распределенные асинхронные мульти-
мастер конфигурации используя для этого встроенную логическую пото-
ковую репликацию LLSR (Logical Log Streaming Replication).
BDR не является инструментом для кластеризации, т.к. здесь нет
каких-либо глобальных менеджеров блокировок или координаторов тран-
закций. Каждый узел не зависит от других, что было бы невозможно в
случае использования менеджеров блокировки. Каждый из узлов содер-
жит локальную копию данных идентичную данным на других узлах. За-
просы также выполняются только локально. При этом каждый из узлов
81
5.4. Pglogical
внутренне консистентен в любое время, целиком же группа серверов явля-
ется согласованной в конечном счете (eventually consistent). Уникальность
BDR заключается в том что она непохожа ни на встроенную потоковую
репликацию, ни на существующие trigger-based решения (Londiste, Slony,
Bucardo).
Самым заметным отличием от потоковой репликации является то, что
BDR (LLSR) оперирует базами (per-database replication), а классическая
PLSR реплицирует целиком инстанс (per-cluster replication), т. е. все базы
внутри инстанса. Существующие ограничения и особенности:
Все изменения данных вызываемые INSERT/DELETE/UPDATE реп-
лицируются (TRUNCATE на момент написания статьи пока не реа-
лизован);
Большинство операции изменения схемы (DDL) реплицируются
успешно. Неподдерживаемые DDL фиксируются модулем реплика-
ции и отклоняются с выдачей ошибкой (на момент написания не ра-
ботал CREATE TABLE ... AS);
Определения таблиц, типов, расширений и т. п. должны быть иден-
тичными между upstream и downstream мастерами;
Действия которые отражаются в WAL, но не представляются в ви-
де логических изменений не реплицируются на другой узел (запись
полных страниц, вакуумация таблиц и т. п.). Таким образом логи-
ческая потоковая репликация (LLSR) избавлена от некоторой части
накладных расходов которые присутствуют в физической потоковой
репликации PLSR (тем не менее это не означает что LLSR требуется
меньшая пропускная способность сети чем для PLSR);
Небольшое примечание: временная остановка репликации осуществля-
ется выключением downstream мастера. Однако стоит отметить что оста-
новленная реплика приводит к тому что upstream мастер продолжит на-
капливать WAL журналы что в свою очередь может привести к некон-
тролируемому расходу пространства на диске. Поэтому крайне не реко-
мендуется надолго выключать реплику. Удаление реплики навсегда осу-
ществляется через удаление конфигурации BDR на downstream сервере с
последующим перезапуском downstream мастера. Затем нужно удалить со-
ответствующий слот репликации на upstream мастере с помощью функции
pg_drop_replication_slot(’slotname’). Доступные слоты можно просмотреть
с помощью функции pg_get_replication_slots.
На текущий момент собрать BDR можно из исходников по данному
мануалу. С официальным принятием данных патчей в ядро PostgreSQL
данный раздел про BDR будет расширен и дополнен.
5.4 Pglogical
Pglogical это расширение для PostgreSQL, которое использует ло-
82
5.4. Pglogical
гическое декодирование через publish/subscribe модель. Данное расшире-
ние базируется на BDR проекте (5.3 PostgreSQL Bi-Directional Replication
(BDR)). Расширение работает только начиная с версии PostgreSQL 9.4 и
выше (из-за логического декодирования). Для разных вариаций обнару-
жения и разрешения конфликтов требуется версия 9.5 и выше.
Используются следующие термины для описания pglogical:
Nodes(ноды, узлы) экземпляры баз данных PostgreSQL;
Provider и subscriber роли узлов. Provider выполняют выдачу дан-
ных и изменений для subscriber-ов;
Replication set (набор для репликации) коллекция таблиц и после-
довательностей для репликации;
Сценарии использования pglogical:
Обновление между версиями PostgreSQL (например c 9.4 на 9.5);
Полная репликация базы данных;
Выборочная репликация таблиц;
Сбор данных с нескольких баз данных в одну;
Архитектурные детали:
Pglogical работает на уровне каждой базы данных, а не на весь сер-
вер;
Provider (publisher) может «кормить» несколько subscriber-ов без до-
полнительных накладных расходов записи на диск;
Один subscriber может объединить изменения из нескольких
provider-ов и использовать систему обнаружения и разрешения кон-
фликтов между изменениями;
Каскадная репликация осуществляется в виде переадресации изме-
нений;
Установка и настройка
Установить pglogical можно по данной документации. Далее требуется
настроить логический декодинг в PostgreSQL:
Листинг 5.32 postgresql.conf
Line 1 w a l _ l e vel = l o g i c a l
- max_worker_processes = 10 # one per database needed on
p r ovi d er node
- # one per node needed on
s u b s c r i b e r node
- m a x _ re p l i c a ti o n _ s lo t s = 10 # one per node needed on
p r ovi d er node
83
5.4. Pglogical
5 max_wal_senders = 10 # one per node needed on
p r ovi d er node
- sh a re d _p re l o a d _ l i br a ri e s = p g l o g i c a l
Если используется PostgreSQL 9.5+ и требуются механизмы разреше-
ния конфликтов, то требуется добавить дополнительные опции:
Листинг 5.33 postgresql.conf
Line 1 track_commit_timestamp = on # needed f o r l a s t / f i r s t update
wins c o n f l i c t r e s o l u t i o n
- # p r o p e rty a v a i l a b l e i n
PostgreSQL 9.5+
В pg_hba.conf нужно разрешить replication соединения с локального хо-
ста для пользователя с привилегией репликации. После перезапуска базы
нужно активировать расширение на всех нодах:
Листинг 5.34 Активируем расширение
Line 1 CREATE EXTENSION p g l o g i c a l ;
Далее на master (мастер) создаем provider (процесс, который будет вы-
давать изменения для subscriber-ов) ноду:
Листинг 5.35 Создаем provider
Line 1 SELECT p g l o g i c a l . create_node (
- node_name := p r ovi d e r1 ,
- dsn := host=p ro v id e r h os t por t =5432 dbname=db
- ) ;
И добавляем все таблицы в public схеме:
Листинг 5.36 Добавляем в replication set все таблицы в public схеме
Line 1 SELECT p g l o g i c a l . r e p li ca ti on_s e t _a dd _a ll _tab l e s ( d e f a u l t ,
ARRAY[ p u b li c ] ) ;
Далее переходим на slave (слейв) и создаем subscriber ноду:
Листинг 5.37 Создаем subscriber
Line 1 SELECT p g l o g i c a l . create_node (
- node_name := s ub s c r i b e r 1 ,
- dsn := host=t h i s h o s t p ort =5432 dbname=db
- ) ;
84
5.4. Pglogical
После этого создаем «подписку» на provider ноду, которая начнет син-
хронизацию и репликацию в фоне:
Листинг 5.38 Активируем subscriber
Line 1 SELECT p g l o g i c a l . c r e a t e _ su bs c r i p t i o n (
- subscription_name := s u b s c r i p t i o n 1 ,
- provider_dsn := hos t=p ro v id e rh o s t p o rt =5432 dbname=db
- ) ;
Если все проделано верно, subscriber через определенный интервал вре-
мени subscriber нода должна получить точную копию всех таблиц в public
схеме с master хоста.
Разрешение конфликтов
Если используется схема, где subscriber нода подписана на данные из
нескольких provider-ов, или же на subscriber дополнительно производятся
локальные изменения данных, могут возникать конфликты для новых из-
менений. В pglogical встроен механизм для обнаружения и разрешения
конфликтов. Настройка данного механизма происходит через pglogical .
conflict_resolution ключ. Поддерживаются следующие значения:
error - репликация остановится на ошибке, если обнаруживается кон-
фликт и потребуется ручное действие для его разрешения;
apply_remote - всегда применить изменения, который конфликтуют
с локальными данными. Значение по умолчанию;
keep_local - сохранить локальную версию данных и игнорировать
конфликтующие изменения, которые исходят от provider-а;
last_update_wins - версия данных с самым новым коммитом (newest
commit timestamp) будет сохранена;
first_update_wins - версия данных с самым старым коммитом (oldest
commit timestamp) будет сохранена;
Когда опция track_commit_timestamp отключена, единственное допу-
стимое значение для pglogical . conflict_resolution может быть apply_remote.
Поскольку track_commit_timestamp не доступен в PostgreSQL 9.4, данная
опция установлена по умолчанию в apply_remote.
Ограничения и недостатки
Для работы требуется суперпользователь;
UNLOGGED и TEMPORARY таблицы не реплицируются;
Для каждой базы данных нужно настроить отдельный provider и
subscriber;
Требуется primary key или replica identity для репликации;
85
5.5. Slony-I
Разрешен только один уникальный индекс/ограничение/основной
ключ на таблицу (из-за возможных конфликтов). Возможно исполь-
зовать больше, но только в случае если subscriber читает только с
одного provider и не производится локальных изменений данных на
нем;
Автоматическая репликация DDL не поддерживается. У pglogical
есть команда pglogical .replicate_ddl_command для запуска DDL на
provider и subscriber;
Ограничения на foreign ключи не выполняются на subscriber-рах;
При использовании TRUNCATE ... CASCADE будет выполнен
CASCADE только на provider;
Последовательности реплицируются периодически, а не в режиме
реального времени;
5.5 Slony-I
Slony это система репликации реального времени, позволяющая
организовать синхронизацию нескольких серверов PostgreSQL по се-
ти. Slony использует триггеры PostgreSQL для привязки к событиям
INSERT/DELETE/UPDATE и хранимые процедуры для выполнения дей-
ствий.
Система Slony с точки зрения администратора состоит из двух глав-
ных компонент: репликационного демона slony и административной консо-
ли slonik. Администрирование системы сводится к общению со slonik-ом,
демон slon только следит за собственно процессом репликации.
Все команды slonik принимает на свой stdin. До начала выполнения
скрипт slonik-a проверяется на соответствие синтаксису, если обнаружи-
ваются ошибки, скрипт не выполняется, так что можно не волноваться
если slonik сообщает о syntax error, ничего страшного не произошло. И он
ещё ничего не сделал. Скорее всего.
Установка
Установка на Ubuntu производится простой командой:
Листинг 5.39 Установка
Line 1 $ sudo a p tit u d e i n s t a l l s lony1 - 2 - bin
Настройка
Рассмотрим установку на гипотетическую базу данных customers. Ис-
ходные данные:
86
5.5. Slony-I
customers база данных;
master_host хост master базы;
slave_host хост slave базы;
customers_rep имя кластера;
Подготовка master базы
Для начала нужно создать пользователя в базе, под которым будет
действовать Slony. По умолчанию, и отдавая должное системе, этого поль-
зователя обычно называют slony.
Листинг 5.40 Подготовка master-сервера
Line 1 $ c r e a t e u s e r - a -d s lony
- $ p s q l - d template1 - c "ALTER USER s lo ny WITH PASSWORD
slony_user_password ; "
Также на каждом из узлов лучше завести системного пользователя
slony, чтобы запускать от его имени репликационного демона slon. В даль-
нейшем подразумевается, что он пользователь и slon) есть на каждом
из узлов кластера.
Подготовка slave базы
Здесь рассматривается, что серверы кластера соединены посредством
сети. Необходимо чтобы с каждого из серверов можно было установить
соединение с PostgreSQL на master хосте, и наоборот. То есть, команда:
Листинг 5.41 Подготовка одного slave-сервера
Line 1 anyuser@customers_slave$ p s ql -d cus tomers \
- - h customers_master . com -U sl o n y
должна подключать нас к мастер-серверу (после ввода пароля, жела-
тельно).
Теперь устанавливаем на slave-хост сервер PostgreSQL. Следующего
обычно не требуется, сразу после установки Postgres «up and ready», но в
случае каких-то ошибок можно начать «с чистого листа», выполнив сле-
дующие команды (предварительно сохранив конфигурационные файлы и
остановив postmaster):
Листинг 5.42 Подготовка одного slave-сервера
Line 1 pgsql@custom ers_sla ve$ rm - r f $PGDATA
- pgsql@customers_slave$ mkdir $PGDATA
- pgsql@customers_slave$ i n i t db -E UTF8 -D $PGDATA
- pgsql@customers_slave$ c r e a t e u s e r - a -d s l ony
87
5.5. Slony-I
5 pgsql@customers_slave$ p s q l - d template1 - c " a l t e r \
- u se r s lony with password slony_user_password ; "
Далее запускаем postmaster. Обычно требуется определённый владелец
для реплицируемой БД. В этом случае необходимо создать его тоже:
Листинг 5.43 Подготовка одного slave-сервера
Line 1 pgsql@custom ers_sla ve$ c r e a t e u s e r - a - d customers_owner
- pgsql@customers_slave$ p s q l - d template1 - c " a l t e r \
- u se r customers_owner with password customers_owner_password
; "
Эти две команды можно запускать с customers_master, к командной
строке в этом случае нужно добавить -h customers_slave, чтобы все опера-
ции выполнялись на slave.
На slave, как и на master, также нужно установить Slony.
Инициализация БД и plpgsql на slave
Следующие команды выполняются от пользователя slony. Скорее
всего для выполнения каждой из них потребуется ввести пароль
(slony_user_password):
Листинг 5.44 Инициализация БД и plpgsql на slave
Line 1 slony@customers_master$ cr e a t e d b -O customers_owner \
- - h custome rs_ slave . com custo mers
- slony@customers_master$ c r e a t e l a n g - d customer s \
- - h custome rs_ slave . com p l p g s q l
Внимание! Все таблицы, которые будут добавлены в replication set
должны иметь primary key. Если какая-то из таблиц не удовлетворяет это-
му условию, задержитесь на этом шаге и дайте каждой таблице primary
key командой ALTER TABLE ADD PRIMARY KEY. Если столбца который
мог бы стать primary key не находится, добавьте новый столбец типа serial
(ALTER TABLE ADD COLUMN), и заполните его значениями. Настоятель-
но НЕ рекомендую использовать table add key slonik-a.
Далее создаём таблицы и всё остальное на slave базе:
Листинг 5.45 Инициализация БД и plpgsql на slave
Line 1 slony@customers_master$ pg_dump - s customer s | \
- ps q l -U s l on y -h cu stomers_sl ave . com cus tomers
pg_dump -s сдампит только структуру нашей БД.
pg_dump -s customers должен пускать без пароля, а вот для psql -U slony
-h customers_slave.com customers придётся набрать пароль (slony_user_pass
). Важно: подразумевается что сейчас на мастер-хосте ещё не установлен
88
5.5. Slony-I
Slony (речь не про make install), то есть в БД нет таблиц sl_*, триггеров и
прочего.
Инициализация кластера
Сейчас мы имеем два сервера PostgreSQL которые свободно «видят»
друг друга по сети, на одном из них находится мастер-база с данными, на
другом только структура базы. Далее мастер-хосте запускаем скрипт:
Листинг 5.46 Инициализация кластера
Line 1 #!/ bin / sh
-
- CLUSTER=customers_rep
-
5 DBNAME1=customer s
- DBNAME2=customer s
-
- HOST1=customers_master . com
- HOST2=cus tomers_ slave . com
10
- PORT1=5432
- PORT2=5432
-
- SLONY_USER=s l on y
15
- s l o n i k <<EOF
- c l u s t e r name = $CLUSTER;
- node 1 admin con n i n fo = dbname=$DBNAME1 h o st=$HOST1 po rt=
$PORT1
- u se r=s l o ny password=slony_user_password ;
20 node 2 admin con n i n fo = dbname=$DBNAME2 h o st=$HOST2
- port=$PORT2 user=s l o ny password=slony_user_password ;
- i n i t c l u s t e r ( i d = 1 , comment = Customers DB
- r e p l i c a t i o n c l u s t e r ) ;
-
25 echo Create s e t ;
-
- c r e a t e s e t ( i d = 1 , o r i g i n = 1 , comment = Customers
- DB r e p l i c a t i o n s e t ) ;
-
30 echo Adding t a b l e s to the s u b s c r i p t i o n s e t ;
-
- echo Adding t a b l e p ub l ic . customers_sales . . . ;
- s e t add t a b l e ( s e t i d = 1 , o r i g i n = 1 , i d = 4 , f u l l
q u a l i f i e d
89
5.5. Slony-I
- name = p u b l ic . customers_sale s , comment = Table p u bl ic .
customers_sale s ) ;
35 echo done ;
-
- echo Adding t a b l e p ub l ic . customers_something . . . ;
- s e t add t a b l e ( s e t i d = 1 , o r i g i n = 1 , i d = 5 , f u l l
q u a l i f i e d
- name = p u b l ic . customers_something ,
40 comment = Table p u bl i c . customers_something ) ;
- echo done ;
-
- echo done adding ;
- s t o r e node ( i d = 2 , comment = Node 2 , $HOST2 ) ;
45 echo s to r ed node ;
- s t o r e path ( s e r v e r = 1 , c l i e n t = 2 , c on ni nf o = dbname=
$DBNAME1 h o st=$HOST1
- port=$PORT1 user=s l o ny password=slony_user_password ) ;
- echo s to r ed path ;
- s t o r e path ( s e r v e r = 2 , c l i e n t = 1 , c on ni nf o = dbname=
$DBNAME2 h o st=$HOST2
50 port=$PORT2 u s e r=s l o ny password=slony_user_password ) ;
-
- s t o r e l i s t e n ( o r i g i n = 1 , p ro v ide r = 1 , r e c e i v e r = 2 ) ;
- s t o r e l i s t e n ( o r i g i n = 2 , p ro v ide r = 2 , r e c e i v e r = 1 ) ;
- EOF
Здесь инициализируется кластер, создается replication set, включаются
в него две таблицы. Нужно перечислить все таблицы, которые нужно реп-
лицировать. Replication set запоминается раз и навсегда. Чтобы добавить
узел в схему репликации не нужно заново инициализировать set. Если в
набор добавляется или удаляется таблица нужно переподписать все узлы.
То есть сделать unsubscribe и subscribe заново.
Подписываем slave-узел на replication set
Далее запускаем на слейве:
Листинг 5.47 Подписываем slave-узел на replication set
Line 1 #!/ bin / sh
-
- CLUSTER=customers_rep
-
5 DBNAME1=customer s
- DBNAME2=customer s
-
- HOST1=customers_master . com
90
5.5. Slony-I
- HOST2=cus tomers_ slave . com
10
- PORT1=5432
- PORT2=5432
-
- SLONY_USER=s l o ny
15
- s l o n i k <<EOF
- c l u s t e r name = $CLUSTER;
- node 1 admin con n i n fo = dbname=$DBNAME1 h o st=$HOST1
- port=$PORT1 user=s l o ny password=slony_user_password ;
20 node 2 admin con n i n fo = dbname=$DBNAME2 h o st=$HOST2
- port=$PORT2 user=s l o ny password=slony_user_password ;
-
- echo s u b s c r i b i n g ;
- s ub sc ri b e s e t ( i d = 1 , p r ov i d er = 1 , r e c e i v e r = 2 , fo rward
= no ) ;
25
- EOF
Старт репликации
Теперь, на обоих узлах необходимо запустить демона репликации.
Листинг 5.48 Старт репликации
Line 1 slony@customers_master$ s l o n customers_rep \
- "dbname=customers u se r=s l o ny "
и
Листинг 5.49 Старт репликации
Line 1 slony@custom ers_sla ve$ s lo n customers_rep \
- "dbname=customers u se r=s l o ny "
Cлоны обменяются сообщениями и начнут передачу данных. Началь-
ное наполнение происходит с помощью COPY команды, слейв база в это
время полностью блокируется.
Общие задачи
Добавление ещё одного узла в работающую схему репликации
Требуется выполнить 5.5 и 5.5 этапы. Новый узел имеет id = 3. Нахо-
дится на хосте customers_slave3.com, «видит» мастер-сервер по сети и ма-
стер может подключиться к его PostgreSQL. После дублирования струк-
туры (п 5.5.2) делается следующее:
91
5.5. Slony-I
Листинг 5.50 Общие задачи
Line 1 s l o n i k <<EOF
- c l u s t e r name = cust omers_slav e ;
- node 3 admin con n i n fo = dbname=custome rs host=
cu stomers_slave 3 . com
- port =5432 use r=s l o ny password=slony_user_pass ;
5 u n i n s t a l l node ( i d = 3) ;
- echo okay ;
- EOF
Это нужно чтобы удалить схему, триггеры и процедуры, которые были
сдублированы вместе с таблицами и структурой БД. Инициализировать
кластер не надо. Вместо этого записываем информацию о новом узле в
сети:
Листинг 5.51 Общие задачи
Line 1 #!/ bin / sh
-
- CLUSTER=customers_rep
-
5 DBNAME1=customer s
- DBNAME3=customer s
-
- HOST1=customers_master . com
- HOST3=cus tomers_sla ve3 . com
10
- PORT1=5432
- PORT2=5432
-
- SLONY_USER=s l on y
15
- s l o n i k <<EOF
- c l u s t e r name = $CLUSTER;
- node 1 admin con n i n fo = dbname=$DBNAME1 h o st=$HOST1
- port=$PORT1 user=s l o ny password=slony_user_pass ;
20 node 3 admin con n i n fo = dbname=$DBNAME3
- host=$HOST3 port=$PORT2 u se r=s l o ny password=slony_user_pass
;
-
- echo done adding ;
-
25 s t o r e node ( i d = 3 , comment = Node 3 , $HOST3 ) ;
- echo sored node ;
- s t o r e path ( s e r v e r = 1 , c l i e n t = 3 , c on ni nf o = dbname=
$DBNAME1
92
5.5. Slony-I
- host=$HOST1 port=$PORT1 u se r=s l o ny password=slony_user_pass
) ;
- echo s to r ed path ;
30 s t o r e path ( s e r v e r = 3 , c l i e n t = 1 , c on ni nf o = dbname=
$DBNAME3
- host=$HOST3 port=$PORT2 u se r=s l o ny password=slony_user_pass
) ;
-
- echo a gain ;
- s t o r e l i s t e n ( o r i g i n = 1 , p ro v ide r = 1 , r e c e i v e r = 3 ) ;
35 s t o r e l i s t e n ( o r i g i n = 3 , p ro v ide r = 3 , r e c e i v e r = 1 ) ;
-
- EOF
Новый узел имеет id 3, потому что 2 уже работает. Подписываем новый
узел 3 на replication set:
Листинг 5.52 Общие задачи
Line 1 #!/ bin / sh
-
- CLUSTER=customers_rep
-
5 DBNAME1=customer s
- DBNAME3=customer s
-
- HOST1=customers_master . com
- HOST3=cus tomers_sla ve3 . com
10
- PORT1=5432
- PORT2=5432
-
- SLONY_USER=s l on y
15
- s l o n i k <<EOF
- c l u s t e r name = $CLUSTER;
- node 1 admin con n i n fo = dbname=$DBNAME1 h o st=$HOST1
- port=$PORT1 user=s l o ny password=slony_user_pass ;
20 node 3 admin con n i n fo = dbname=$DBNAME3 h o st=$HOST3
- port=$PORT2 user=s l o ny password=slony_user_pass ;
-
- echo s u b s c r i b i n g ;
- s ub sc ri b e s e t ( i d = 1 , p r ov i d er = 1 , r e c e i v e r = 3 , fo rward
= no ) ;
25
- EOF
93
5.5. Slony-I
Теперь запускаем slon на новом узле, так же как и на остальных. Пе-
резапускать slon на мастере не надо.
Листинг 5.53 Общие задачи
Line 1 slony@customers_slave 3$ s l o n customers_rep \
- "dbname=customers u se r=s l o ny "
Репликация должна начаться как обычно.
Устранение неисправностей
Ошибка при добавлении узла в систему репликации
Периодически, при добавлении новой машины в кластер возникает сле-
дующая ошибка: на новой ноде всё начинает жужжать и работать, имею-
щиеся же отваливаются с примерно следующей диагностикой:
Листинг 5.54 Устранение неисправностей
Line 1 %s l o n customers_rep "dbname=customer s us e r=s lony_user "
- CONFIG main : s l o n v e rs io n 1 . 0 . 5 s t a r t i n g up
- CONFIG main : l o c a l node id = 3
- CONFIG main : l oad i ng c ur r en t c l u s t e r c o n f i g u r a t i o n
5 CONFIG storeNode : no_id=1 no_comment= CustomersDB
- r e p l i c a t i o n c l u s t e r
- CONFIG storeNode : no_id=2 no_comment= Node 2 ,
- node2 . example . com
- CONFIG storeNode : no_id=4 no_comment= Node 4 ,
10 node4 . example . com
- CONFIG s t o r e P ath : pa_server=1 pa_client=3
- pa_conninfo="dbname=custom ers
- host=mainhost . com p o rt =5432 u s e r=slony_user
- password=slony_user_pass " pa_connretry=10
15 CONFIG s t o r e L i s t e n : l i _ o r i g i n =1 l i _ r e c e i v e r=3
- l i _ p ro vi d e r =1
- CONFIG s t o r e S e t : set_id=1 s e t _ or ig i n=1
- set_comment= CustomersDB r e p l i c a t i o n s e t
- WARN remoteWorker_wakeup : node 1 - no worker thre ad
20 CONFIG s to r e S u b sc r i b e : sub_set=1 sub_provider=1 sub_forward=
f
- WARN remoteWorker_wakeup : node 1 - no worker thre ad
- CONFIG e n a b le S u b s c ri pt i o n : sub_set=1
- WARN remoteWorker_wakeup : node 1 - no worker thre ad
- CONFIG main : c o n f i g u r a t i o n complete - s t a r t i n g t h re a ds
25 CONFIG enableNode : no_id=1
- CONFIG enableNode : no_id=2
- CONFIG enableNode : no_id=4
94
5.5. Slony-I
- ERROR remoteWorkerThread_1 : " beg in t r a n s a c t i o n ; s e t
- t r a n s a c t i o n i s o l a t i o n l e v e l
30 s e r i a l i z a b l e ; l oc k t a b le " _customers_rep" . s l _ c on fi g_ lo ck ;
s e l e c t
- " _customers_rep" . e na bl e S u b s cr ip t i o n ( 1 , 1 , 4) ;
- n o t i f y " _customers_rep_Event " ; n o t i f y "
_customers_rep_Confirm" ;
- i n s e r t i nt o " _customers_rep " . s l_ev ent ( ev_origin , ev_seqno ,
- ev_timestamp , ev_minxid , ev_maxxid , ev_xip ,
35 ev_type , ev_data1 , ev_data2 , ev_data3 , ev_data4 ) v a lu e s
- ( 1 , 2 1 9 44 0 ,
- 200 5 -05 - 05 1 8 :5 2 : 4 2 . 7 0 8 3 5 1 , 5 2 5 0 1 2 8 3 , 5 2 5 0 1 2 9 2 ,
- 52 5 01 2 8 3 , ENABLE_SUBSCRIPTION ,
- 1 , 1 , 4 , f ) ; i n s e r t i nt o " _customers_rep " .
40 sl_con f i rm ( con_origin , con_received ,
- con_seqno , con_timestamp ) v a lu e s ( 1 , 3 , 2 1 9 4 4 0 ,
- CURRENT_TIMESTAMP) ; commit t r an s a c t i o n ; "
- PGRES_FATAL_ERROR ERROR: i n s e r t or update on t a bl e
- " s l _s u bs cr i b e " v i o l a t e s f o r e i g n key
45 c o n s t r a i n t " s l_ subs c r ibe - sl_path - r e f "
- DETAIL: Key ( sub_provider , s u b _r e c e iv e r ) =(1 ,4)
- i s not p r e se n t i n t a b l e " sl_path " .
- INFO remoteListenThread_1 : d i s co nn e c t i n g from
- dbname=cu stomers host=mainhost . com
50 port =5432 use r=slony_user password=slony_user_pass
- %
Это означает что в служебной таблице _имя< кластера>.sl_path, на-
пример _customers_rep.sl_path на уже имеющихся узлах отсутствует ин-
формация о новом узле. В данном случае, id нового узла 4, пара (1,4) в
sl_path отсутствует. Чтобы это устранить, нужно выполнить на каждом
из имеющихся узлов приблизительно следующий запрос:
Листинг 5.55 Устранение неисправностей
Line 1 $ p s ql -d cus tomer s -h _every_one_of_slaves -U slon y
- cu stomers=# i n s e r t i n t o _customers_rep . sl_path
- va l ues ( 1 , 4 , dbname=c ustomers hos t=mainhost . com
- port =5432 use r=slony_user password=slony_user_password , 10 )
;
Если возникают затруднения, то можно посмотреть на служебные таб-
лицы и их содержимое. Они не видны обычно и находятся в рамках про-
странства имён _имя< кластера>, например _customers_rep.
95
5.6. Londiste
Что делать если репликация со временем начинает тормозить
В процессе эксплуатации может наблюдаться как со временем растёт
нагрузка на master-сервере, в списке активных бекендов — постоянные
SELECT-ы со слейвов. В pg_stat_activity видны примерно такие запросы:
Листинг 5.56 Устранение неисправностей
Line 1 s e l e c t ev_origin , ev_seqno , ev_timestamp , ev_minxid ,
ev_maxxid , ev_xip ,
- ev_type , ev_data1 , ev_data2 , ev_data3 , ev_data4 , ev_data5 ,
ev_data6 ,
- ev_data7 , ev_data8 from "_customers_rep" . sl_ even t e where
- ( e . ev_origi n = 2 and e . ev_seqno > 336996 ) or
5 ( e . ev_origi n = 3 and e . ev_seqno > 1712871 ) or
- ( e . ev_origi n = 4 and e . ev_seqno > 721285 ) or
- ( e . ev_origi n = 5 and e . ev_seqno > 807715 ) or
- ( e . ev_origi n = 1 and e . ev_seqno > 3544763 ) or
- ( e . ev_origi n = 6 and e . ev_seqno > 2529445 ) or
10 ( e . ev_origi n = 7 and e . ev_seqno > 2512532 ) or
- ( e . ev_origi n = 8 and e . ev_seqno > 2500418 ) or
- ( e . ev_origi n = 10 and e . ev_seqno > 1692318 )
- o r de r by e . ev_origin , e . ev_seqno ;
где _customers_rep имя схемы из примера. Таблица sl_event почему-
то разрастается со временем, замедляя выполнение этих запросов до
неприемлемого времени. Удаляем ненужные записи:
Листинг 5.57 Устранение неисправностей
Line 1 d e l e t e from _customers_rep . sl_e vent where
- ev_timestamp<NOW() - 1 DAY : : i n t e r v a l ;
Производительность должна вернуться к изначальным значениям.
Возможно имеет смысл почистить таблицы _customers_rep.sl_log_* где
вместо звёздочки подставляются натуральные числа, по-видимому по ко-
личеству репликационных сетов, так что _customers_rep.sl_log_1 точно
должна существовать.
5.6 Londiste
Londiste представляет собой движок для организации репликации, на-
писанный на языке Python. Основные принципы: надежность и простота
использования. Из-за этого данное решение имеет меньше функциональ-
ности, чем Slony-I. Londiste использует в качестве транспортного механиз-
ма очередь PgQ (описание этого более чем интересного проекта остается
за рамками данной главы, поскольку он представляет интерес скорее для
96
5.6. Londiste
низкоуровневых программистов баз данных, чем для конечных пользова-
телей администраторов СУБД PostgreSQL). Отличительными особен-
ностями решения являются:
возможность потабличной репликации;
начальное копирование ничего не блокирует;
возможность двухстороннего сравнения таблиц;
простота установки;
К недостаткам можно отнести:
триггерная репликация, что ухудшает производительность базы;
Установка
Установка будет проводиться на Debian сервере. Поскольку Londiste
это часть Skytools, то нам нужно ставить этот пакет:
Листинг 5.58 Установка
Line 1 % sudo a p t itu d e i n s t a l l s k y t o o l s
В некоторых системах может содержаться пакет версии 2.x, который
не поддерживает каскадную репликацию, отказоустойчивость(failover) и
переключение между серверами (switchover). По этой причине он не бу-
дет рассматриваться. Скачать самую последнюю версию пакета можно с
официального сайта. На момент написания главы последняя версия была
3.2. Начнем установку:
Листинг 5.59 Установка
Line 1 $ wget http : / / pgfoundry . org / f r s / download . php /3622/ s k yto o ls
- 3 . 2 . t a r . gz
- $ t a r z xv f s k yto o ls - 3 . 2 . t a r . gz
- $ cd s k yt o ols - 3 . 2 /
- # пакеты для сборки deb
5 $ sudo a p tit u d e i n s t a l l bui l d - e s s e n t i a l a u t o c o nf \
- automake au t oto o ls - dev dh - make \
- d e b h e l p e r d e v s c r i p t s f a k e r o o t x u t i l s l i n t i a n p b ui l de r \
- python - a l l - dev python - su ppo rt xmlto a s c i i d o c \
- l i be v en t - dev libpq - dev l i b t o o l
10 # python - psycopg нужен для работы L o n d i s t e
- $ sudo a p tit u d e i n s t a l l python - psycopg2 p os t g r e s ql - s e r ver -
dev - a l l
- # данной командой собираем deb пакет
- $ make deb
- $ cd . . /
97
5.6. Londiste
15 # ставим s k y t o o l s
- $ dpkg - i * . deb
Для других систем можно собрать Skytools командами:
Листинг 5.60 Установка
Line 1 $ . / c o n f i g u r e
- $ make
- $ make i n s t a l l
Далее проверяем правильность установки:
Листинг 5.61 Установка
Line 1 $ l o n d i s t e 3 -V
- l o n d i s t e 3 , S k yt ool s v e r s i o n 3 .2
- $ pgqd -V
- bad switch : usage : pgq - t i c k e r [ s wi t ch e s ] c o n f i g . f i l e
5 Switch e s :
- -v I nc r e a s e v e r b os i t y
- -q No output to c o n s o l e
- -d Daemonize
- -h Show h elp
10 -V Show v e r s i o n
- - - i n i Show sample c o n f i g f i l e
- - s Stop - send SIGINT to running p ro c e s s
- -k K i l l - send SIGTERM to running p r o ce s s
- - r Reload - send SIGHUP to running p r o c e s s
Настройка
Обозначения:
master-host мастер база данных;
slave1-host, slave2-host, slave3-host, slave4-host слейв базы данных;
l3simple название реплицируемой базы данных;
Конфигурация репликаторов
Сначала создается конфигурационный файл для master базы онфиг
будет /etc/skytools/master-londiste. ini):
Листинг 5.62 Конфигурация репликаторов
Line 1 [ l o n d i s t e 3 ]
- job_name = master _l3 sim ple
- db = dbname=l 3s im pl e
98
5.6. Londiste
- queue_name = r e p l i k a
5 l o g f i l e = / var / l og / s k y t o o l s / mast er_ l3s imp le . l og
- p i d f i l e = / var / pid / s k y t o o l s / master_l3simple . pid
-
- # Задержка между проверками наличия активности
- # новых( пакетов данных) в секундах
10 loop_delay = 0 .5
Инициализируем Londiste для master базы:
Листинг 5.63 Инициализируем Londiste
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i c r ea te - r oot
master - node "dbname=l 3 s i m p l e host=master - h ost "
- INFO p lp gs ql i s i n s t a l l e d
- INFO I n s t a l l i n g pgq
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq . s q l
5 INFO pgq . get_batch_cursor i s i n s t a l l e d
- INFO I n s t a l l i n g pgq_ext
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq_ext . s q l
- INFO I n s t a l l i n g pgq_node
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq_node . s q l
10 INFO I n s t a l l i n g l o n d i s t e
- INFO Reading from / us r / share / s k y t o o l s 3 / l o n d i s t e . s q l
- INFO l o n d i s t e . global_add_table i s i n s t a l l e d
- INFO I n i t i a l i z i n g node
- INFO Loc a tion r e g i s t e r e d
15 INFO Node " master - node" i n i t i a l i z e d f o r queue " r e p l i k a " with
type " roo t "
- INFO Done
где master-server это имя провайдера (мастера базы).
Теперь запустим демон:
Листинг 5.64 Запускаем демон для master базы
Line 1 $ l o n d i s t e 3 -d / e t c / s k y t oo l s / master - l o n d i s t e . i n i worker
- $ t a i l - f / var / lo g / s k y t o o l s / mas ter _l3 sim ple . l o g
- INFO { standby : 1}
- INFO { standby : 1}
Если нужно перегрузить демон (например при изменении конфигура-
ции), то можно воспользоваться параметром -r:
Листинг 5.65 Перегрузка демона
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i - r
Для остановки демона есть параметр -s:
99
5.6. Londiste
Листинг 5.66 Остановка демона
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i - s
или если потребуется «убить» ( kill ) демон:
Листинг 5.67 Остановка демона
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i - k
Для автоматизации данного процесса skytools3 имеет встроенный де-
мон, который запускает все воркеры из директории /etc/skytools/. Сама
конфигурация демона находится в /etc/skytools. ini . Что бы запустить все
демоны londiste достаточно выполнить:
Листинг 5.68 Демон для ticker
Line 1 $ / et c / i n i t . d/ s k y t o o l s 3 s t a r t
- INFO S ta r t in g master_l3simple
Перейдем к slave базе. Для начала нужно создать базу данных:
Листинг 5.69 Копирования структуры базы
Line 1 $ p s ql -h sla v e 1 - hos t -U p o s tg re s
- # CREATE DATABASE l 3 s i m p l e ;
Подключение должно быть «trust» (без паролей) между master и slave
базами данных.
Далее создадим конфиг для slave базы (/etc/skytools/slave1- londiste . ini
):
Листинг 5.70 Создаём конфигурацию для slave
Line 1 [ l o n d i s t e 3 ]
- job_name = s l a ve1 _ l 3si m pl e
- db = dbname=l 3s im pl e
- queue_name = r e p l i k a
5 l o g f i l e = / var / l og / s k y t o o l s / s l ave 1 _l 3si m pl e . l og
- p i d f i l e = / var / pid / s k y t o o l s / sl a v e1_ l 3si m p le . pid
-
- # Задержка между проверками наличия активности
- # новых( пакетов данных) в секундах
10 loop_delay = 0 .5
Инициализируем Londiste для slave базы:
Листинг 5.71 Инициализируем Londiste для slave
100
5.6. Londiste
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e1 - l o n d i s t e . i n i cr e a te - l e a f
slav e 1 - node "dbname=l 3 s i m p l e hos t=s la v e 1 - host " - - p rov i de r
="dbname=l 3 s i m p l e host=master - host "
Теперь можем запустить демон:
Листинг 5.72 Запускаем демон для slave базы
Line 1 $ l o n d i s t e 3 -d / e t c / s k y t oo l s / sl a ve 1 - l o n d i s t e . i n i worker
Или же через главный демон:
Листинг 5.73 Запускаем демон для slave базы
Line 1 $ / et c / i n i t . d/ s k y t o o l s 3 s t a r t
- INFO S ta r t in g master_l3simple
- INFO S ta r t in g s l ave 1 _ l3s i mp le
Создаём конфигурацию для PgQ ticker
Londiste требуется PgQ ticker для работы с мастер базой данных, ко-
торый может быть запущен и на другой машине. Но, конечно, лучше его
запускать на той же, где и master база данных. Для этого мы настраиваем
специальный конфиг для ticker демона (конфиг будет /etc/skytools/pgqd.ini
):
Листинг 5.74 PgQ ticker конфиг
Line 1 [ pgqd ]
- l o g f i l e = / var / l og / s k y t o o l s /pgqd . l o g
- p i d f i l e = / var / pid / s k y t o o l s /pgqd . pid
Запускаем демон:
Листинг 5.75 Запускаем PgQ ticker
Line 1 $ pgqd -d / e t c / s k y t o o l s /pgqd . i n i
- $ t a i l - f / var / lo g / s k y t o o l s /pgqd . l og
- LOG S t a r t i n g pgqd 3 . 2
- LOG auto - d et e c t i n g dbs . . .
5 LOG l 3 s i m p l e : pgq v e r si on ok : 3 .2
Или же через глобальный демон:
Листинг 5.76 Запускаем PgQ ticker
Line 1 $ / et c / i n i t . d/ s k y t o o l s 3 r e s t a r t
- INFO S ta r t in g master_l3simple
101
5.6. Londiste
- INFO S ta r t in g s l ave 1 _ l3s i mp le
- INFO S ta r t in g pgqd
5 LOG S t a r t i n g pgqd 3 . 2
Теперь можно увидеть статус кластера:
Листинг 5.77 Статус кластера
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i s t a t u s
- Queue : r e p l i k a Loc al node : s l a ve 1 - node
-
- master - node ( ro o t )
5 | Tables : 0/0/0
- | Lag : 44 s , Tick : 5
- + - - : s l a v e1 - node ( l e a f )
- Tables : 0/0/0
- Lag : 44 s , Tick : 5
10
- $ l o n d i s t e 3 / e t c / s k y t o o l s / master - l o n d i s t e . i n i members
- Member i n f o on master - node@r epl ika :
- node_name dead no de_l ocation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
15 master - node Fal s e dbname=l 3 s i m p l e host=
master - hos t
- s l a ve 1 - node False dbname=l 3 s i m p l e host=
slav e 1 - h o st
Но репликация еще не запущенна: требуется добавить таблицы в оче-
редь, которые мы хотим реплицировать. Для этого используем команду
add-table:
Листинг 5.78 Добавляем таблицы
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i add - t a b l e - -
a l l
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 1 - l o n d i s t e . i n i add - t ab le - -
a l l - - c re at e - f u l l
В данном примере используется параметр -- all , который означает все
таблицы, но вместо него вы можете перечислить список конкретных таб-
лиц, если не хотите реплицировать все. Если имена таблиц отличаются на
master и slave, то можно использовать --dest-table параметр при добавле-
нии таблиц на slave базе. Также, если вы не перенесли структуру таблиц
заранее с master на slave базы, то это можно сделать автоматически через
-- create параметр (или -- create- full , если нужно перенести полностью всю
схему таблицы).
102
5.6. Londiste
Подобным образом добавляем последовательности (sequences) для ре-
пликации:
Листинг 5.79 Добавляем последовательности
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i add - seq - - a l l
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 1 - l o n d i s t e . i n i add - s eq - - a l l
Но последовательности должны на slave базе созданы заранее (тут не
поможет -- create- full для таблиц). Поэтому иногда проще перенести точ-
ную копию структуры master базы на slave:
Листинг 5.80 Клонирование структуры базы
Line 1 $ pg_dump - s - npu b l i c l 3s im pl e | ps q l - h slav e 1 - hos t l 3s im pl e
Далее проверяем состояние репликации:
Листинг 5.81 Статус кластера
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i s t a t u s
- Queue : r e p l i k a Loc al node : master - node
-
- master - node ( ro o t )
5 | Tables : 4/0/0
- | Lag : 18 s , Tick : 12
- + - - : s l a v e1 - node ( l e a f )
- Tables : 0/4/0
- Lag : 18 s , Tick : 12
Как можно заметить, возле «Table» содержится три цифры (x/y/z).
Каждая обозначает:
x - количество таблиц в состоянии «ok» (replicated). На master базе
указывает, что она в норме, а на slave базах - таблица синхронизи-
рована с master базой;
y - количество таблиц в состоянии half (initial copy, not finnished),
у master должно быть 0, а у slave базах это указывает количество
таблиц в процессе копирования;
z - количество таблиц в состоянии ignored (table not replicated locally),
у master должно быть 0, а у slave базах это количество таблиц, кото-
рые не добавлены для репликации с мастера . е. master отдает на
репликацию эту таблицу, но slave их просто не забирает).
Через небольшой интервал времени все таблицы должны синхронизи-
роваться:
103
5.6. Londiste
Листинг 5.82 Статус кластера
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i s t a t u s
- Queue : r e p l i k a Loc al node : master - node
-
- master - node ( ro o t )
5 | Tables : 4/0/0
- | Lag : 31 s , Tick : 20
- + - - : s l a v e1 - node ( l e a f )
- Tables : 4/0/0
- Lag : 31 s , Tick : 20
Дополнительно Londiste позволяет просмотреть состояние таблиц и по-
следовательностей на master и slave базах:
Листинг 5.83 Статус таблиц и последовательностей
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / master - l o n d i s t e . i n i t a b l e s
- Tables on node
- table_name merge_state t ab l e_ a t t rs
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5 p u bl i c . pgbench_accounts ok
- p u bl i c . pgbench_branches ok
- p u bl i c . pgbench_history ok
- p u bl i c . p g b en ch_ t e ll ers ok
-
10 $ l o n d i s t e 3 / e t c / s k y t o o l s / master - l o n d i s t e . i n i s eq s
- Sequences on node
- seq_name l o c a l la st _v al ue
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- p u bl i c . pgbench_history_hid_seq True 33345
Проверка
Для проверки будем использовать pgbench утилиту. Запустим добавле-
ние данных в таблицу и смотрим в логи одновременно:
Листинг 5.84 Проверка
Line 1 $ pgbench -T 10 - c 5 l 3 s i m p l e
- $ t a i l - f / var / lo g / s k y t o o l s / sl a v e1_ l 3 sim p le . l o g
- INFO { count : 1508 , d u r a ti o n : 0 . 3 0 7 , i d l e : 0.0026}
- INFO { count : 1572 , d u r a ti o n : 0 . 308 5 , i d l e : 0 . 0 0 2 }
5 INFO { count : 1600 , d u r a ti o n : 0 . 308 6 , i d l e : 0 . 0026}
- INFO { count : 36 , d u r at i o n : 0 .01 5 7 , i d l e : 2 .0191}
Как видно по логам slave база успешно реплицируется с master базой.
104
5.6. Londiste
Каскадная репликация
Каскадная репликация позволяет реплицировать данные с одного слей-
ва на другой. Создадим конфиг для второго slave (конфиг будет /etc/
skytools/slave2- londiste . ini ):
Листинг 5.85 Конфиг для slave2
Line 1 [ l o n d i s t e 3 ]
- job_name = s l a ve2 _ l 3si m pl e
- db = dbname=l 3s im pl e h o st=s l a v e 2 - h ost
- queue_name = r e p l i k a
5 l o g f i l e = / var / l og / s k y t o o l s / s l ave 2 _l 3si m pl e . l og
- p i d f i l e = / var / pid / s k y t o o l s / sl a v e2_ l 3si m p le . pid
-
- # Задержка между проверками наличия активности
- # новых( пакетов данных) в секундах
10 loop_delay = 0 .5
Для создания slave, от которого можно реплицировать другие базы
данных используется команда create-branch вместо create- leaf (root, корень
- master нода, предоставляет информацию для репликации; branch, ветка
- нода с копией данных, с которой можно реплицировать; leaf, лист - нода
с копией данными, но реплицировать с нее уже не возможно):
Листинг 5.86 Инициализируем slave2
Line 1 $ p s ql - hs lave2 - host -d p o s t g r e s - c "CREATE DATABASE
l 3 s i m p l e ; "
- $ pg_dump - s - n pu bl ic l 3 s i m p l e | p sq l - hsl ave2 - h ost l 3 s i m p l e
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 2 - l o n d i s t e . i n i c r e at e - branch
slav e 2 - node "dbname=l 3 s i m p l e hos t=s la v e 2 - host " - - p rov i de r
="dbname=l 3 s i m p l e host=master - host "
- INFO p lp gs ql i s i n s t a l l e d
5 INFO I n s t a l l i n g pgq
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq . s q l
- INFO pgq . get_batch_cursor i s i n s t a l l e d
- INFO I n s t a l l i n g pgq_ext
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq_ext . s q l
10 INFO I n s t a l l i n g pgq_node
- INFO Reading from / us r / share / s k y t o o l s 3 /pgq_node . s q l
- INFO I n s t a l l i n g l o n d i s t e
- INFO Reading from / us r / share / s k y t o o l s 3 / l o n d i s t e . s q l
- INFO l o n d i s t e . global_add_table i s i n s t a l l e d
15 INFO I n i t i a l i z i n g node
- INFO Loc a tion r e g i s t e r e d
- INFO Loc a tion r e g i s t e r e d
- INFO S ub s cr i be r r e g i s t e r e d : sl a v e 2 - node
105
5.6. Londiste
- INFO Loc a tion r e g i s t e r e d
20 INFO Loc a tion r e g i s t e r e d
- INFO Loc a tion r e g i s t e r e d
- INFO Node " s l a v e 2 - node" i n i t i a l i z e d f o r queue " r e p l i k a " with
type " branch"
- INFO Done
Далее добавляем все таблицы и последовательности:
Листинг 5.87 Инициализируем slave2
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e2 - l o n d i s t e . i n i add - t ab l e - -
a l l
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 2 - l o n d i s t e . i n i add - s eq - - a l l
И запускаем новый демон:
Листинг 5.88 Инициализируем slave2
Line 1 $ / et c / i n i t . d/ s k y t o o l s 3 s t a r t
- INFO S ta r t in g master_l3simple
- INFO S ta r t in g s l ave 1 _ l3s i mp le
- INFO S ta r t in g s l ave 2 _ l3s i mp le
5 INFO S t a r t in g pgqd
- LOG S t a r t i n g pgqd 3 . 2
Повторим вышеперечисленные операции для slave3 и slave4, только по-
меняем provider для них:
Листинг 5.89 Инициализируем slave3 и slave4
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e3 - l o n d i s t e . i n i cr e a te - branch
slav e 3 - node "dbname=l 3 s i m p l e hos t=s la v e 3 - host " - - p rov i de r
="dbname=l 3 s i m p l e host=s la v e 2 - host "
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 4 - l o n d i s t e . i n i c r e at e - branch
slav e 4 - node "dbname=l 3 s i m p l e hos t=s la v e 4 - host " - - p rov i de r
="dbname=l 3 s i m p l e host=s la v e 3 - host "
В результате получаем такую картину с кластером:
Листинг 5.90 Кластер с каскадной репликацией
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e4 - l o n d i s t e . i n i s t a t us
- Queue : r e p l i k a Loc al node : s l a ve 4 - node
-
- master - node ( ro o t )
5 | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e1 - node ( l e a f )
106
5.6. Londiste
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
10 + - - : s l a v e2 - node ( branch )
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e 3 - node ( branch )
- | Tables : 4/0/0
15 | Lag : 9 s , Tick : 49
- + - - : s l av e 4 - node ( branch )
- Tables : 4/0/0
- Lag : 9 s , Tick : 49
Londiste позволяет «на лету» изменять топологию кластера. Например,
изменим «provider» для slave4:
Листинг 5.91 Изменяем топологию
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e4 - l o n d i s t e . i n i change -
p r ovi d er - - p r ov i d er="dbname=l 3 s i m p l e host=slave 2 - h ost "
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 4 - l o n d i s t e . i n i s t at u s
- Queue : r e p l i k a Loc al node : s l a ve 4 - node
-
5 master - node ( ro o t )
- | Tables : 4/0/0
- | Lag : 12s , Tick : 56
- + - - : s l a v e1 - node ( l e a f )
- | Tables : 4/0/0
10 | Lag : 12s , Tick : 56
- + - - : s l a v e2 - node ( branch )
- | Tables : 4/0/0
- | Lag : 12 s , Tick : 56
- + - - : s l a v e 3 - node ( branch )
15 | Tables : 4/0/0
- | Lag : 12 s , Tick : 56
- + - - : s l a v e 4 - node ( branch )
- Tables : 4/0/0
- Lag : 12 s , Tick : 56
Также топологию можно менять с стороны репликатора через команду
takeover:
Листинг 5.92 Изменяем топологию
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e3 - l o n d i s t e . i n i takeo v e r
slav e 4 - node
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 4 - l o n d i s t e . i n i s t at u s
- Queue : r e p l i k a Loc al node : s l a ve 4 - node
-
107
5.6. Londiste
5 master - node ( ro o t )
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e1 - node ( l e a f )
- | Tables : 4/0/0
10 | Lag : 9 s , Tick : 49
- + - - : s l a v e2 - node ( branch )
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e 3 - node ( branch )
15 | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l av e 4 - node ( branch )
- Tables : 4/0/0
- Lag : 9 s , Tick : 49
Через команду drop-node можно удалить slave из кластера:
Листинг 5.93 Удаляем ноду
Line 1 $ l o n d i s t e 3 / et c / s k y t o o l s / sl a v e4 - l o n d i s t e . i n i drop - node
slav e 4 - node
- $ l o n d i s t e 3 / e t c / s k y t o o l s / sl av e 3 - l o n d i s t e . i n i s t at u s
- Queue : r e p l i k a Loc al node : s l a ve 3 - node
-
5 master - node ( ro o t )
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e1 - node ( l e a f )
- | Tables : 4/0/0
10 | Lag : 9 s , Tick : 49
- + - - : s l a v e2 - node ( branch )
- | Tables : 4/0/0
- | Lag : 9 s , Tick : 49
- + - - : s l a v e 3 - node ( branch )
15 Tables : 4/0/0
- Lag : 9 s , Tick : 49
Команда tag-dead может использоваться, что бы указать slave как не
живой (прекратить на него репликацию), а через команду tag- alive его
можно вернуть в кластер.
Общие задачи
Проверка состояния слейвов
Данный запрос на мастере дает некоторую информацию о каждой оче-
реди и слейве:
108
5.6. Londiste
Листинг 5.94 Проверка состояния слейвов
Line 1 # SELECT queue_name , consumer_name , la g , l as t _s ee n FROM pgq .
get_consumer_info ( ) ;
- queue_name | consumer_name | l ag |
la s t _ se en
- - -
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - -
- r e p l i k a | . global_watermark | 0 0 : 0 3: 37 . 1 0 8 2 59 |
0 0 : 0 2: 33 . 0 1 3 9 15
5 r e p l i k a | s lav e 1_ l3s i mp le | 0 0: 00 : 3 2 . 6 31 50 9 |
0 0 : 0 0: 32 . 5 3 3 9 11
- r e p l i k a | . slav e 1 - node . watermark | 0 0 :0 3: 3 7 . 1 0 82 59 |
0 0 : 0 3 : 0 5 . 0 1 4 3 1
где lag столбец показывает отставание от мастера в синхронизации,
last_seen время последней запроса от слейва. Значение этого столбца не
должно быть больше, чем 60 секунд для конфигурации по умолчанию.
Удаление очереди всех событий из мастера
При работе с Londiste может потребоваться удалить все ваши настрой-
ки для того, чтобы начать все заново. Для PGQ, чтобы остановить накоп-
ление данных, используйте следующие API:
Листинг 5.95 Удаление очереди всех событий из мастера
Line 1 SELECT pgq . unr egi ster_co nsumer ( queue_name , consumer_name
) ;
Добавление столбца в таблицу
Добавляем в следующей последовательности:
1. добавить поле на все слейвы;
2. BEGIN; на мастере;
3. добавить поле на мастере;
4. COMMIT;
Удаление столбца из таблицы
1. BEGIN; на мастере;
2. удалить поле на мастере;
3. COMMIT;
4. Проверить lag, когда londiste пройдет момент удаления поля;
109
5.7. Bucardo
5. удалить поле на всех слейвах;
Хитрость тут в том, чтобы удалить поле на слейвах только тогда, когда
больше нет событий в очереди на это поле.
Устранение неисправностей
Londiste пожирает процессор и lag растет
Это происходит, например, если во время сбоя забыли перезапустить
ticker. Или когда сделали большой UPDATE или DELETE в одной тран-
закции, но теперь что бы реализовать каждое событие в этом запросе
создаются транзакции на слейвах . . .
Следующий запрос позволяет подсчитать, сколько событий пришло в
pgq.subscription в колонках sub_last_tick и sub_next_tick.
Листинг 5.96 Устранение неисправностей
Line 1 SELECT count ( *)
- FROM pgq . event_1 ,
- (SELECT t ick_s naps hot
- FROM pgq . t i c k
5 WHERE tic k _ i d BETWEEN 5715138 AND 5715139
- ) as t ( snapsho t s )
- WHERE txid _ v i s i ble_in_snap s h o t ( ev_txid , s n a p s h o t s ) ;
На практике это было более чем 5 миллионов и 400 тысяч событий. Чем
больше событий с базы данных требуется обработать Londiste, тем больше
ему требуется памяти для этого. Возможно сообщить Londiste не загру-
жать все события сразу. Достаточно добавить в INI конфиг PgQ ticker
следующую настройку:
Листинг 5.97 Устранение неисправностей
Line 1 pgq_lazy_fetch = 500
Теперь Londiste будет брать максимум 500 событий в один пакет за-
просов. Остальные попадут в следующие пакеты запросов.
5.7 Bucardo
Bucardo асинхронная master-master или master-slave репликация
PostgreSQL, которая написана на Perl. Система очень гибкая, поддержи-
вает несколько видов синхронизации и обработки конфликтов.
110
5.7. Bucardo
Установка
Установка будет проводиться на Debian сервере. Сначала нужно уста-
новить DBIx::Safe Perl модуль.
Листинг 5.98 Установка
Line 1 $ apt - g e t i n s t a l l l ibd b ix - s a f e - p e r l
Для других систем можно поставить из исходников:
Листинг 5.99 Установка
Line 1 $ t ar xv f z dbix_safe . t ar . gz
- $ cd DBIx - Safe - 1 . 2 . 5
- $ p e r l M a k ef il e . PL
- $ make
5 $ make t e s t
- $ sudo make i n s t a l l
Теперь ставим сам Bucardo. Скачиваем его и инсталлируем:
Листинг 5.100 Установка
Line 1 $ wget http : / / bucardo . org / downloads/Bucardo - 5 . 4 . 1 . t a r . gz
- $ t a r x vf z Bucardo - 5 . 4 . 1 . t ar . gz
- $ cd Bucardo - 5 . 4 . 1
- $ p e r l M a k ef il e . PL
5 $ make
- $ sudo make i n s t a l l
Для работы Bucardo потребуется установить поддержку pl/perl языка
в PostgreSQL.
Листинг 5.101 Установка
Line 1 $ sudo a p tit u d e i n s t a l l p o st gr e s q l - p l p e r l - 9 . 5
и дополнительные пакеты для Perl (DBI, DBD::Pg, Test::Simple,
boolean):
Листинг 5.102 Установка
Line 1 $ sudo a p tit u d e i n s t a l l libdbd - pg - p e r l l ibb o ol e an - p e r l
Теперь можем приступать к настройке репликации.
111
5.7. Bucardo
Настройка
Инициализация Bucardo
Запускаем установку Bucardo:
Листинг 5.103 Инициализация Bucardo
Line 1 $ bucardo i n s t a l l
Во время установки будут показаны настройки подключения к
PostgreSQL, которые можно будет изменить:
Листинг 5.104 Инициализация Bucardo
Line 1 This w i l l i n s t a l l the bucardo databa se i nt o an e x i s t i n g
Po s t gr es c l u s t e r .
- P os tgre s must have been compiled with Pe r l support ,
- and you must conn ect as a su p eru s er
-
5 We w i l l c r e a t e a new s u pe r u se r named bucardo ,
- and make i t the owner o f a new database named bucardo
-
- Current c o n n ec t io n s e t t i n g s :
- 1 . Host : <none>
10 2 . Port : 5432
- 3 . User : p o s t g r e s
- 4 . Database : p o s t g r e s
- 5 . PID d i r e c t o r y : / var / run / bucardo
После подтверждения настроек, Bucardo создаст пользователя bucardo
и базу данных bucardo. Данный пользователь должен иметь право логи-
ниться через Unix socket, поэтому лучше заранее дать ему такие права в
pg_hda.conf.
После успешной установки можно проверить конфигурацию через ко-
манду bucardo show all:
Листинг 5.105 Инициализация Bucardo
Line 1 $ bucardo show a l l
- autosync_ddl = newcol
- bu c ar d o _i n it i al_ v er s ion = 5 . 0 . 0
- bucardo_vac = 1
5 bucardo_version = 5 . 0 . 0
- ctl_checkonkids_time = 10
- c t l _ c r e a t e kid_time = 0. 5
- c tl _s l e e p = 0 .2
- d e f a u l t _ c o n f l i c t _ s t r a t e g y = b u c ardo_lates t
112
5.7. Bucardo
10 default_email_from = nobody@example . com
- default_e mai l_h ost = l o c a l h o s t
- de fault_email_t o = nobody@example . com
- . . .
Настройка баз данных
Теперь нужно настроить базы данных, с которыми будет работать
Bucardo. Обозначим базы как master_db и slave_db. Реплицировать будем
simple_database базу. Сначала настроим мастер базу:
Листинг 5.106 Настройка баз данных
Line 1 $ bucardo add db master_db dbname=simple_database host=
master_host
- Added d ata bas e "master_db"
Данной командой указали базу данных и дали ей имя master_db (для
того, что в реальной жизни master_db и slave_db имеют одинаковое назва-
ние базы simple_database и их нужно отличать в Bucardo).
Дальше добавляем slave_db:
Листинг 5.107 Настройка баз данных
Line 1 $ bucardo add db slave_db dbname=simple_database port =5432
hos t=s lave_ h ost
Настройка репликации
Теперь требуется настроить синхронизацию между этими базами дан-
ных. Делается это командой sync:
Листинг 5.108 Настройка репликации
Line 1 $ bucardo add sync d el ta dbs=master_db : so urce , slave_db :
t ar g e t c o n f l i c t _ s t r a t e g y=bucar d o _ l a test t a b l e s=a l l
- Added sync " d e l t a "
- Created a new r e l gr o up named " d e l ta "
- Created a new dbgroup named " d e l t a "
5 Added t a b l e " pu b l i c . pgbench_accounts "
- Added t a b l e " pu b l i c . pgbench_branches "
- Added t a b l e " pu b l i c . pgbench_history "
- Added t a b l e " pu b l i c . p gbe n c h_ tel l e rs "
Данная команда устанавливает Bucardo триггеры в PostgreSQL для
master-slave репликации. Значения параметров:
113
5.7. Bucardo
dbs список баз данных, которые следует синхронизировать. Значе-
ние source или target указывает, что это master или slave база данных
соответственно (их может быть больше одной);
conflict_strategy для работы в режиме master-master нужно указать
как Bucardo должен решать конфликты синхронизации. Существуют
следующие стратегии:
bucardo_source при конфликте мы копируем данные с source;
bucardo_target при конфликте мы копируем данные с target;
bucardo_skip конфликт мы просто не реплицируем. Не реко-
мендуется для продакшен систем;
bucardo_random каждая БД имеет одинаковый шанс, что её
изменение будет взято для решение конфликта;
bucardo_latest запись, которая была последней изменена ре-
шает конфликт;
bucardo_abort синхронизация прерывается;
tables таблицы, которые требуется синхронизировать. Через «all»
указываем все;
Для master-master репликации требуется выполнить:
Листинг 5.109 Настройка репликации
Line 1 $ bucardo add sync d el ta dbs=master_db : so urce , slave_db :
so u r ce c o n f l i c t _ s t r a t e g y=bucardo _ l a t e s t t a b l e s=a l l
Пример для создания master-master и master-slave репликации:
Листинг 5.110 Настройка репликации
Line 1 $ bucardo add sync d el ta dbs=master_db1 : sour ce , master_db2 :
so urce , slave_db1 : t ar get , slave_db2 : t a r g et
c o n f l i c t _ s t r a t e g y=b u c a r d o_latest t a b l e s=a l l
Для проверки состояния репликации:
Листинг 5.111 Проверка состояния репликации
Line 1 $ bucardo s t a t u s
- PID o f Bucardo MCP: 12122
- Name Sta t e Last good Time Last I /D Last bad
Time
- =======+========+============+========+===========+===========+=======
5 d e lt a | Good | 1 3 : 2 8 : 5 3 | 13m 6 s | 3685/7384 | none
|
114
5.7. Bucardo
Запуск/Остановка репликации
Запуск репликации:
Листинг 5.112 Запуск репликации
Line 1 $ bucardo s t a r t
Остановка репликации:
Листинг 5.113 Остановка репликации
Line 1 $ bucardo stop
Общие задачи
Просмотр значений конфигурации
Листинг 5.114 Просмотр значений конфигурации
Line 1 $ bucardo show a l l
Изменения значений конфигурации
Листинг 5.115 Изменения значений конфигурации
Line 1 $ bucardo s e t name=v a l u e
Например:
Листинг 5.116 Изменения значений конфигурации
Line 1 $ bucardo_ctl s e t s y s l o g _ f a c i l i t y=LOG_LOCAL3
Перегрузка конфигурации
Листинг 5.117 Перегрузка конфигурации
Line 1 $ bucardo re l oad _ c onf i g
Более подробную информацию можно найти на официальном сайте.
115
5.7. Bucardo
Репликация в другие типы баз данных
Начиная с версии 5.0 Bucardo поддерживает репликацию в другие ис-
точники данных: drizzle, mongo, mysql, oracle, redis и sqlite (тип базы за-
дается при использовании команды bucardo add db через ключ «type», ко-
торый по умолчанию postgres). Давайте рассмотрим пример с redis базой.
Для начала потребуется установить redis адаптер для Perl (для других баз
устанавливаются соответствующие):
Листинг 5.118 Установка redis
Line 1 $ a p t itu d e i n s t a l l l i b r e d i s - p e r l
Далее зарегистрируем redis базу в Bucardo:
Листинг 5.119 Добавление redis базы
Line 1 $ bucardo add db R dbname=simple_database type=r e d i s
- Added d ata bas e "R"
Создадим группу баз данных под названием pg_to_redis:
Листинг 5.120 Группа баз данных
Line 1 $ bucardo add dbgroup pg_to_redis master_db : so u r ce slave_db :
so u r ce R: t ar g e t
- Created dbgroup " pg_to_redis "
- Added d ata bas e "master_db" to dbgroup " pg_to_redis " as
so u r ce
- Added d ata bas e " slave_db " to dbgroup " pg_to_redis " as s ou rce
5 Added d ata bas e "R" to dbgroup " pg_to_redis " as t a r g e t
И создадим репликацию:
Листинг 5.121 Установка sync
Line 1 $ bucardo add sync pg_to_redis_sync t a b l e s=a l l dbs=
pg_to_redis s t a t u s=a c t i v e
- Added sync " pg_to_redis_sync "
- Added t a b l e " pu b l i c . pgbench_accounts "
- Added t a b l e " pu b l i c . pgbench_branches "
5 Added t a b l e " pu b l i c . pgbench_history "
- Added t a b l e " pu b l i c . p gbe n c h_ tel l e rs "
После перезапуска Bucardo данные с PostgreSQL таблиц начнут репли-
цироваться в Redis:
116
5.7. Bucardo
Листинг 5.122 Репликация в redis
Line 1 $ pgbench -T 10 - c 5 simple_database
- $ r e d i s - c l i monitor
- "HMSET" " pgbench_history : 6 " " bid " "2" " a i d " " 36291 " " d e l t a "
" 3716" "mtime" " 2014 -07 -11 1 4 : 5 9: 38 . 4 5 4 8 24 " " hid " " 4331 "
- "HMSET" " pgbench_history : 2 " " bid " "1" " a i d " " 65179 " " d e l t a "
" 2436" "mtime" " 2014 -07 -11 1 4 : 5 9: 38 . 5 0 0 8 96 " " hid " " 4332 "
5 "HMSET" " pgbench_history : 1 4 " " bid " "2" " a i d " "153001 " " d el ta
" " -264 " "mtime" " 2014 -07 -11 1 4 : 59 :3 8 . 4 7 2 70 6 " " hid " "4333
"
- "HMSET" " pgbench_history : 1 5 " " bid " "1" " a i d " "195747 " " d el ta
" " -1671 " "mtime" " 2014 -07 -11 1 4 : 5 9: 38 . 5 0 9 8 39 " " hid " "
4334 "
- "HMSET" " pgbench_history : 3 " " bid " "2" " a i d " " 147650 " " de l t a "
" 3237" "mtime" " 2014 -07 -11 1 4 : 5 9: 38 . 4 8 9 8 78 " " hid " " 4335 "
- "HMSET" " pgbench_history : 1 5 " " bid " "1" " a i d " "39521" " d e l t a "
" -2125 " "mtime" " 2014 -07 -11 1 4 : 5 9 : 38 . 5 2 6 3 17 " " hid " " 4336
"
- "HMSET" " pgbench_history : 1 4 " " bid " "2" " a i d " "60105" " d e l t a "
" 2555" "mtime" " 2014 -07 -11 1 4 : 5 9: 38 . 6 1 6 9 35 " " hid " " 4337 "
10 "HMSET" " pgbench_history : 1 5 " " bid " "2" " a i d " "186655 " " d el ta
" "930 " "mtime" " 2014 -07 -11 1 4 : 59 :3 8 . 5 4 1 29 6 " " hid " "4338 "
- "HMSET" " pgbench_history : 1 5 " " bid " "1" " a i d " "101406 " " d el ta
" "668 " "mtime" " 2014 -07 -11 1 4 : 5 9 :3 8. 5 6 0 9 7 1 " " hid " " 4339"
- "HMSET" " pgbench_history : 1 5 " " bid " "2" " a i d " "126329 " " d el ta
" " -4236 " "mtime" " 2014 -07 -11 1 4 : 5 9 : 3 8 .5 9 0 7 " " hid " " 4340 "
- "DEL" " p g ben c h _t ell e r s : 20 "
Данные в Redis хранятся в виде хешей:
Листинг 5.123 Данные в redis
Line 1 $ r ed is - c l i "HGETALL" " pgbench_history : 15 "
- 1) " bid "
- 2) "2"
- 3) " a i d "
5 4) " 126329 "
- 5) " d e l t a "
- 6) " -4236 "
- 7) "mtime"
- 8) " 2014 -07 -11 1 4 : 5 9: 3 8 . 5 9 0 7 "
10 9) " hid "
- 10) " 4340"
Также можно проверить состояние репликации:
Листинг 5.124 Установка redis
117
5.8. Заключение
Line 1 $ bucardo s t a t u s
- PID o f Bucardo MCP: 4655
- Name Sta t e Last good Time Last I /D
Last bad Time
- ==================+========+============+========+===========+===========+========
5 d e lt a | Good | 1 4 : 5 9 : 3 9 | 8m 15 s | 0/0
| none |
- pg_to_redis_sync | Good | 1 4 : 5 9 : 4 0 | 8m 14 s | 646/2546
| 1 4 : 5 9 : 3 9 | 8m 15 s
Теперь данные из redis могут использоваться для приложения в виде
быстрого кэш хранилища.
5.8 Заключение
Репликация одна из важнейших частей крупных приложений, кото-
рые работают на PostgreSQL. Она помогает распределять нагрузку на базу
данных, делать фоновый бэкап одной из копий без нагрузки на централь-
ный сервер, создавать отдельный сервер для логирования или аналитики,
прочее.
В главе было рассмотрено несколько видов репликации PostgreSQL.
Нельзя четко сказать какая лучше всех. Потоковая репликация один
из самых лучших вариантов для поддержки идентичных кластеров баз
данных. Slony-I громоздкая и сложная в настройке система, но имею-
щая в своем арсенале множество функций, таких как отказоустойчиво-
сти (failover) и переключение между серверами (switchover). В тоже время
Londiste имея в своем арсенале подобный функционал, может похвастать-
ся еще компактностью и простой в установке. Bucardo система которая
может быть или master-master, или master-slave репликацией.
118
6
Шардинг
Если ешь слона, не пытайся
запихать его в рот целиком
Народная мудрость
6.1 Введение
Шардинг разделение данных на уровне ресурсов. Концепция шар-
динга заключается в логическом разделении данных по различным ресур-
сам, исходя из требований к нагрузке.
Рассмотрим пример. Пусть у нас есть приложение с регистрацией поль-
зователей, которое позволяет писать друг другу личные сообщения. До-
пустим оно очень популярно, и много людей им пользуются ежедневно.
Естественно, что таблица с личными сообщениями будет намного больше
всех остальных таблиц в базе (скажем, будет занимать 90% всех ресур-
сов). Зная это, мы можем подготовить для этой (только одной!) таблицы
выделенный сервер помощнее, а остальные оставить на другом (посла-
бее). Теперь мы можем идеально подстроить сервер для работы с одной
специфической таблицей, постараться уместить ее в память, возможно, до-
полнительно партиционировать ее и т. д. Такое распределение называется
вертикальным шардингом.
Что делать, если наша таблица с сообщениями стала настолько боль-
шой, что даже выделенный сервер под нее одну уже не спасает? Необхо-
димо делать горизонтальный шардинг т. е. разделение одной таблицы
по разным ресурсам. Как это выглядит на практике? На разных серверах
у нас будет таблица с одинаковой структурой, но разными данными. Для
нашего случая с сообщениями, мы можем хранить первые 10 миллионов
сообщений на одном сервере, вторые 10 - на втором и т. д. Т. е. необхо-
димо иметь критерий шардинга какой-то параметр, который позволит
определить, на каком именно сервере лежат те или иные данные.
119
6.2. PL/Proxy
Обычно, в качестве параметра шардинга выбирают ID пользователя
(user_id) это позволяет делить данные по серверам равномерно и просто.
Т.о. при получении личных сообщений пользователей алгоритм работы
будет такой:
Определить, на каком сервере БД лежат сообщения пользователя,
исходя из user_id;
Инициализировать соединение с этим сервером;
Выбрать сообщения;
Задачу определения конкретного сервера можно решать двумя путями:
Хранить в одном месте хеш-таблицу с соответствиями «пользова-
тель=сервер». Тогда, при определении сервера, нужно будет выбрать
сервер из этой таблицы. В этом случае узкое место это большая
таблица соответствия, которую нужно хранить в одном месте. Для
таких целей очень хорошо подходят базы данных «ключ=значение»;
Определять имя сервера с помощью числового (буквенного) преобра-
зования. Например, можно вычислять номер сервера, как остаток от
деления на определенное число оличество серверов, между которы-
ми Вы делите таблицу). В этом случае узкое место это проблема
добавления новых серверов придется делать перераспределение
данных между новым количеством серверов;
Естественно, делая горизонтальный шардинг, Вы ограничиваете себя
в возможности выборок, которые требуют пересмотра всей таблицы (на-
пример, последние посты в блогах людей будет достать невозможно, если
таблица постов шардится). Такие задачи придется решать другими подхо-
дами. Например, для описанного примера, можно при появлении нового
поста, заносить его ID в общий стек, размером в 100 элементом.
Горизонтальный шардинг имеет одно явное преимущество он бес-
конечно масштабируем. Для создания шардинга PostgreSQL существует
несколько решений:
Postgres-XC
Greenplum Database
Citus
PL/Proxy
Stado (sequel to GridSQL)
6.2 PL/Proxy
PL/Proxy представляет собой прокси-язык для удаленного вызова про-
цедур и партицирования данных между разными базами. Основная идея
120
6.2. PL/Proxy
его использования заключается в том, что появляется возможность вызы-
вать функции, расположенные в удаленных базах, а также свободно рабо-
тать с кластером баз данных (например, вызвать функцию на всех узлах
кластера, или на случайном узле, или на каком-то одном определенном).
Чем PL/Proxy может быть полезен? Он существенно упрощает го-
ризонтальное масштабирование системы. Становится удобным разделять
таблицу с пользователями, например, по первой латинской букве имени
на 26 узлов. При этом приложение, которое работает непосредственно с
прокси-базой, ничего не будет замечать: запрос на авторизацию, напри-
мер, сам будет направлен прокси-сервером на нужный узел. То есть адми-
нистратор баз данных может проводить масштабирование системы прак-
тически независимо от разработчиков приложения.
PL/Proxy позволяет полностью решить проблемы масштабирования
OLTP систем. В систему легко вводится резервирование с failover-ом не
только по узлам, но и по самим прокси-серверам, каждый из которых ра-
ботает со всеми узлами.
Недостатки и ограничения:
все запросы и вызовы функций вызываются в autocommit-режиме
на удаленных серверах;
в теле функции разрешен только один SELECT. При необходимости
нужно писать отдельную процедуру;
при каждом вызове прокси-сервер стартует новое соединение к
бэкенд-серверу. В высоконагруженных системах целесообразно ис-
пользовать менеджер для кеширования соединений к бэкенд-
серверам (для этой цели идеально подходит PgBouncer);
изменение конфигурации кластера (например количества партиций)
требует перезапуска прокси-сервера;
Установка
1. Скачать PL/Proxy и распаковать;
2. Собрать PL/Proxy командами make и make install;
Так же можно установить PL/Proxy из репозитория пакетов. Например
в Ubuntu Server достаточно выполнить команду для PostgreSQL 9.6:
Листинг 6.1 Установка
Line 1 $ sudo a p tit u d e i n s t a l l p o st gr e s q l - 9 . 6 - plprox y
Настройка
Для примера настройки используется 3 сервера PostgreSQL. 2 сервера
пусть будут node1 и node2, а главный, что будет проксировать запросы
121
6.2. PL/Proxy
на два других proxy. Для корректной работы pl/proxy рекомендуется
использовать количество нод равное степеням двойки. База данных будет
называться plproxytest, а таблица в ней users.
Для начала настроим node1 и node2. Команды, написанные ниже, нуж-
но выполнять на каждой ноде. Сначала создадим базу данных plproxytest
(если её ещё нет):
Листинг 6.2 Настройка
Line 1 CREATE DATABASE p l p r o x y t e s t
- WITH OWNER = po st gr es
- ENCODING = UTF8 ;
Добавляем табличку users:
Листинг 6.3 Настройка
Line 1 CREATE TABLE pu b li c . u s e r s
- (
- username c h a ra ct e r varying ( 2 5 5 ) ,
- ema il c ha ra c t e r v a r y i n g ( 25 5 )
5 )
- WITH (OIDS=FALSE) ;
- ALTER TABLE pu b l i c . u se rs OWNER TO p o s t g r es ;
Теперь создадим функцию для добавления данных в таблицу users:
Листинг 6.4 Настройка
Line 1 CREATE OR REPLACE FUNCTION pu b l i c . i n se r t _ us e r ( i_username
te xt ,
- i _ e m a i la d d r e s s t e xt )
- RETURNS i n t e g e r AS
- $BODY$
5 INSERT INTO p ub l i c . u se rs ( username , e mail ) VALUES ( $1 , $2 ) ;
- SELECT 1 ;
- $BODY$
- LANGUAGE s q l VOLATILE;
- ALTER FUNCTION p u b li c . i ns e rt _ us er ( te xt , t e x t ) OWNER TO
p o s t g r e s ;
С настройкой нод закончено. Приступим к серверу proxy. Как и на всех
нодах, на главном сервере (proxy) должна присутствовать база данных:
Листинг 6.5 Настройка
Line 1 CREATE DATABASE p l p r o x y t e s t
- WITH OWNER = po st gr es
- ENCODING = UTF8 ;
122
6.2. PL/Proxy
Теперь надо указать серверу что эта база данных управляется с помо-
щью pl/proxy:
Листинг 6.6 Настройка
Line 1 CREATE OR REPLACE FUNCTION pu b l i c . plp r oxy_ c all_ h andl er ( )
- RETURNS language_handler AS
- $ l i b d i r / pl proxy , plp roxy_ call _ hand l er
- LANGUAGE c VOLATILE
5 COST 1 ;
- ALTER FUNCTION p u b li c . plpro xy_ca ll_ha ndler ( )
- OWNER TO p o s t g r e s ;
- - - languag e
- CREATE LANGUAGE p lpr oxy HANDLER plpro xy_ca ll_ha ndle r ;
10 CREATE LANGUAGE p l p g s q l ;
Также, для того что бы сервер знал где и какие ноды у него есть,
надо создать 3 сервисные функции, которые pl/proxy будет использовать
в своей работе. Первая функция конфиг для кластера баз данных. Тут
указываются параметры через key-value:
Листинг 6.7 Настройка
Line 1 CREATE OR REPLACE FUNCTION pu b l i c . g et _ cl us t er_ c onf i g
- ( IN cluster_name te xt , OUT " key" text , OUT va l t e xt )
- RETURNS SETOF rec o r d AS
- $BODY$
5 BEGIN
- - - l e t s use same c o n f i g f o r a l l c l u s t e r s
- key := c on n e c ti on _ l i f e ti m e ;
- va l := 3 0 * 6 0 ; - - 30m
- RETURN NEXT;
10 RETURN;
- END;
- $BODY$
- LANGUAGE p lp g s q l VOLATILE
- COST 100
15 ROWS 1000 ;
- ALTER FUNCTION p u b li c . g e t_c l us t e r_ c o nf i g ( t e xt )
- OWNER TO p o s t g r e s ;
Вторая важная функция, код которой надо будет подправить. В ней
надо будет указать DSN нод:
Листинг 6.8 Настройка
Line 1 CREATE OR REPLACE FUNCTION
- p u bl i c . g e t _ c l u s t e r_ pa rt i t i o n s ( cluster_name t e xt )
123
6.2. PL/Proxy
- RETURNS SETOF te x t AS
- $BODY$
5 BEGIN
- IF cluster_name = u s e r c l u s t e r THEN
- RETURN NEXT dbname=p l p r o x y t e s t h o st=node1 us e r=p o s t g r e s
;
- RETURN NEXT dbname=p l p r o x y t e s t h o st=node2 us e r=p o s t g r e s
;
- RETURN;
10 END IF ;
- RAISE EXCEPTION Unknown c l u s t e r ;
- END;
- $BODY$
- LANGUAGE p lp g s q l VOLATILE
15 COST 100
- ROWS 1000 ;
- ALTER FUNCTION p u b li c . g e t _ c l u s t e r_ pa rt i t i o n s ( t ex t )
- OWNER TO p o s t g r e s ;
И последняя:
Листинг 6.9 Настройка
Line 1 CREATE OR REPLACE FUNCTION
- p u bl i c . g e t _cl u ste r _ ver s ion ( cluster_name t ex t )
- RETURNS i n t e g e r AS
- $BODY$
5 BEGIN
- IF cluster_name = u s e r c l u s t e r THEN
- RETURN 1 ;
- END IF ;
- RAISE EXCEPTION Unknown c l u s t e r ;
10 END;
- $BODY$
- LANGUAGE p lp g s q l VOLATILE
- COST 10 0 ;
- ALTER FUNCTION p u b li c . g e t _cl u ste r _ ver s ion ( t ex t )
15 OWNER TO p o s t g r e s ;
Ну и собственно самая главная функция, которая будет вызываться
уже непосредственно в приложении:
Листинг 6.10 Настройка
Line 1 CREATE OR REPLACE FUNCTION
- p u bl i c . i ns e rt _ us e r ( i_username te xt , i _ e m a i la d d r e s s t e xt )
- RETURNS i n t e g e r AS
- $BODY$
124
6.2. PL/Proxy
5 CLUSTER u s e r c l u s t e r ;
- RUN ON h a s h t e x t ( i_username ) ;
- $BODY$
- LANGUAGE pl proxy VOLATILE
- COST 10 0 ;
10 ALTER FUNCTION p u b li c . i ns e rt _ us er ( te xt , t e x t )
- OWNER TO p o s t g r e s ;
Все готово. Подключаемся к серверу proxy и заносим данные в базу:
Листинг 6.11 Настройка
Line 1 SELECT i n se r t _ us e r ( Sven , sven@somewhere . com ) ;
- SELECT in s e r t_ u se r ( Marko , marko@somewhere . com ) ;
- SELECT in s e r t_ u se r ( Ste ve , steve@somewhere . com ) ;
Пробуем извлечь данные. Для этого напишем новую серверную функ-
цию:
Листинг 6.12 Настройка
Line 1 CREATE OR REPLACE FUNCTION
- p u bl i c . get_user_email ( i_username t ex t )
- RETURNS SETOF te x t AS
- $BODY$
5 CLUSTER u s e r c l u s t e r ;
- RUN ON h a s h t e x t ( i_username ) ;
- SELECT email FROM p u bl i c . u s e r s
- WHERE username = i_username ;
- $BODY$
10 LANGUAGE pl proxy VOLATILE
- COST 100
- ROWS 1000 ;
- ALTER FUNCTION p u b li c . get_user_email ( t ex t )
- OWNER TO p o s t g r e s ;
И попробуем её вызвать:
Листинг 6.13 Настройка
Line 1 SELECT p lpr oxy . get_user_email ( Ste ve ) ;
Если потом подключиться к каждой ноде отдельно, то можно четко
увидеть, что данные users разбросаны по таблицам каждой ноды.
Все ли так просто?
Как видно на тестовом примере ничего сложного в работе с pl/proxy
нет. Но в реальной жизни все не так просто. Представьте что у вас 16 нод.
125
6.3. Postgres-X2
Это же надо как-то синхронизировать код функций. А что если ошибка
закрадётся как её оперативно исправлять?
Этот вопрос был задан и на конференции Highload++ 2008, на что
Аско Ойя ответил что соответствующие средства уже реализованы внутри
самого Skype, но ещё не достаточно готовы для того что бы отдавать их
на суд сообществу opensource.
Вторая проблема, которая не дай бог коснётся вас при разработке та-
кого рода системы, это проблема перераспределения данных в тот мо-
мент, когда нам захочется добавить ещё нод в кластер. Планировать эту
масштабную операцию придётся очень тщательно, подготовив все серве-
ра заранее, занеся данные и потом в один момент подменив код функции
get_cluster_partitions.
6.3 Postgres-X2
Postgres-X2 система для создания мульти-мастер кластеров, работа-
ющих в синхронном режиме все узлы всегда содержат актуальные дан-
ные. Postgres-X2 поддерживает опции для увеличения масштабирования
кластера как при преобладании операций записи, так и при основной на-
грузке на чтение данных: поддерживается выполнение транзакций с рас-
параллеливанием на несколько узлов, за целостностью транзакций в пре-
делах всего кластера отвечает специальный узел GTM (Global Transaction
Manager).
Измерение производительности показало, что КПД кластера Postgres-
X2 составляет примерно 64%, т. е. кластер из 10 серверов позволяет до-
биться увеличения производительности системы в целом в 6.4 раза, отно-
сительно производительности одного сервера (цифры приблизительные).
Система не использует в своей работе триггеры и представляет собой
набор дополнений и патчей к PostgreSQL, дающих возможность в про-
зрачном режиме обеспечить работу в кластере стандартных приложений,
без их дополнительной модификации и адаптации (полная совместимость
с PostgreSQL API). Кластер состоит из одного управляющего узла (GTM),
предоставляющего информацию о состоянии транзакций, и произвольно-
го набора рабочих узлов, каждый из которых в свою очередь состоит из
координатора и обработчика данных (обычно эти элементы реализуются
на одном сервере, но могут быть и разделены).
Хоть Postgres-X2 и выглядит похожим на MultiMaster, но он им не
является. Все сервера кластера должны быть соединены сетью с мини-
мальными задержками, никакое географически-распределенное решение
с разумной производительностью построить на нем невозможно (это важ-
ный момент).
126
6.3. Postgres-X2
Рис. 6.1: Архитектура Postgres-X2
Архитектура
Рис. 6.1 показывает архитектуру Postgres-X2 с тремя её основными
компонентами:
1. Глобальный менеджер транзакций (GTM) собирает и обрабатыва-
ет информацию о транзакциях в Postgres-X2, решает вопросы гло-
бального идентификатора транзакции по операциям (для поддержа-
ния согласованного представления базы данных на всех узлах). Он
обеспечивает поддержку других глобальных данных, таких как по-
следовательности и временные метки. Он хранит данные пользова-
теля, за исключением управляющей информации;
2. Координаторы (coordinators) обеспечивают точку подключения
для клиента (приложения). Они несут ответственность за разбор и
выполнение запросов от клиентов и возвращение результатов (при
необходимости). Они не хранят пользовательские данные, а соби-
рают их из обработчиков данных (datanodes) с помощью запросов
SQL через PostgreSQL интерфейс. Координаторы также обрабаты-
вают данные, если требуется, и даже управляют двухфазной фик-
127
6.3. Postgres-X2
сацией. Координаторы используются также для разбора запросов,
составления планов запросов, поиска данных и т.д;
3. Обработчики данных (datanodes) обеспечивают сохранение поль-
зовательских данных. Datanodes выполняют запросы от координа-
торов и возвращают им полученный результат;
Установка
Установить Postgres-X2 можно из исходников.
Распределение данных и масштабируемость
Postgres-X2 предусматривает два способа хранения данных в таблицах:
Рис. 6.2: Распределенные таблицы
Рис. 6.3: Реплицированные таблицы
128
6.3. Postgres-X2
1. Распределенные таблицы (distributed tables, рис. 6.2): данные по таб-
лице распределяются на указанный набор обработчиков данных с ис-
пользованием указанной стратегии (hash, round-robin, modulo). Каж-
дая запись в таблице находится только на одном обработчике дан-
ных. Параллельно могут быть записаны или прочитаны данные с
различных обработчиков данных. За счет этого значительно улуч-
шена производительность на запись и чтение;
2. Реплицированные таблицы (replicated tables, рис. 6.3): данные по таб-
лице реплицируется (клонируются) на указанный набор обработчи-
ков данных. Каждая запись в таблице находится на всех обработчи-
ках данных (которые были указаны) и любые изменения дублиру-
ются на все обработчики данных. Так как все данные доступны на
любом обработчике данных, координатор может собрать все данные
из одного узла, что позволяет направить различные запросы на раз-
личные обработчики данных. Таким образом создается балансировка
нагрузки и увеличения пропускной способности на чтение;
Таблицы и запросы к ним
После установки работа с Postgres-X2 ведется как с обыкновенным
PostgreSQL. Подключаться для работы с данными нужно только к ко-
ординаторам (по умолчанию координатор работает на порту 5432). Для
начала создадим распределенные таблицы:
Листинг 6.14 Создание распределенных таблиц
Line 1 CREATE TABLE
- users_with_hash ( id SERIAL, type INT , . . . )
- DISTRIBUTE by HASH( i d ) ;
-
5 CREATE TABLE
- users_with_modulo ( i d SERIAL, type INT , . . . )
- DISTRIBUTE by MODULO( id ) ;
-
- CREATE TABLE
10 users_wi th_rrobin ( i d SERIAL , type INT , . . . )
- DISTRIBUTE by ROUNDROBIN;
На листинге 6.14 создано 3 распределенные таблицы:
1. Таблица users_with_hash распределяется по хешу значения из указан-
ного поля в таблице (тут указано поле id) по обработчикам данных.
Вот как распределились первые 15 значений:
Листинг 6.15 Данные с координатора и обработчиков данных
129
6.3. Postgres-X2
Line 1 # координатор
- $ p s q l
- # SELECT id , type from users_with_hash ORDER BY i d ;
- i d | type
5 - - - - - - -+ - - - - - - -
- 1 | 946
- 2 | 153
- 3 | 484
- 4 | 422
10 5 | 785
- 6 | 906
- 7 | 973
- 8 | 699
- 9 | 434
15 10 | 986
- 11 | 135
- 12 | 1012
- 13 | 395
- 14 | 667
20 15 | 324
-
- # первый обработчик данных
- $ p s q l - p15432
- # SELECT id , type from users_with_hash ORDER BY i d ;
25 id | type
- - - - - - - + - - - - - - -
- 1 | 946
- 2 | 153
- 5 | 785
30 6 | 906
- 8 | 699
- 9 | 434
- 12 | 1012
- 13 | 395
35 15 | 324
-
- # второй обработчик данных
- $ p s q l - p15433
- # SELECT id , type from users_with_hash ORDER BY i d ;
40 i d | type
- - - - - - - -+ - - - - - - -
- 3 | 484
- 4 | 422
- 7 | 973
45 10 | 986
- 11 | 135
- 14 | 667
130
6.3. Postgres-X2
2. Таблица users_with_modulo распределяется по модулю значения из
указанного поля в таблице (тут указано поле id) по обработчикам
данных. Вот как распределились первые 15 значений:
Листинг 6.16 Данные с координатора и обработчиков данных
Line 1 # координатор
- $ p s q l
- # SELECT id , type from users_with_modulo ORDER BY i d ;
- i d | type
5 - - - - - - -+ - - - - - - -
- 1 | 883
- 2 | 719
- 3 | 29
- 4 | 638
10 5 | 363
- 6 | 946
- 7 | 440
- 8 | 331
- 9 | 884
15 10 | 199
- 11 | 78
- 12 | 791
- 13 | 345
- 14 | 476
20 15 | 860
-
- # первый обработчик данных
- $ p s q l - p15432
- # SELECT id , type from users_with_modulo ORDER BY i d ;
25 id | type
- - - - - - - -+ - - - - - - -
- 2 | 719
- 4 | 638
- 6 | 946
30 8 | 331
- 10 | 199
- 12 | 791
- 14 | 476
-
35 # второй обработчик данных
- $ p s q l - p15433
- # SELECT id , type from users_with_modulo ORDER BY i d ;
- id | type
- - - - - - - + - - - - - - -
40 1 | 883
- 3 | 29
131
6.3. Postgres-X2
- 5 | 363
- 7 | 440
- 9 | 884
45 11 | 78
- 13 | 345
- 15 | 860
3. Таблица users_with_rrobin распределяется циклическим
способом(round-robin) по обработчикам данных. Вот как рас-
пределились первые 15 значений:
Листинг 6.17 Данные с координатора и обработчиков данных
Line 1 # координатор
- $ p s q l
- # SELECT id , type from users_with_rrobin ORDER BY id ;
- i d | type
5 - - - - - - -+ - - - - - - -
- 1 | 890
- 2 | 198
- 3 | 815
- 4 | 446
10 5 | 61
- 6 | 337
- 7 | 948
- 8 | 446
- 9 | 796
15 10 | 422
- 11 | 242
- 12 | 795
- 13 | 314
- 14 | 240
20 15 | 733
-
- # первый обработчик данных
- $ p s q l - p15432
- # SELECT id , type from users_with_rrobin ORDER BY id ;
25 id | type
- - - - - - - -+ - - - - - - -
- 2 | 198
- 4 | 446
- 6 | 337
30 8 | 446
- 10 | 422
- 12 | 795
- 14 | 240
-
132
6.3. Postgres-X2
35 # второй обработчик данных
- $ p s q l - p15433
- # SELECT id , type from users_with_rrobin ORDER BY id ;
- id | type
- - - - - - - + - - - - - - -
40 1 | 890
- 3 | 815
- 5 | 61
- 7 | 948
- 9 | 796
45 11 | 242
- 13 | 314
- 15 | 733
Теперь создадим реплицированную таблицу:
Листинг 6.18 Создание реплицированной таблицы
Line 1 CREATE TABLE
- u se rs _ r e p l i c a t e d ( i d SERIAL , type INT , . . . )
- DISTRIBUTE by REPLICATION;
Естественно данные идентичны на всех обработчиках данных:
Листинг 6.19 Данные с координатора и обработчиков данных
Line 1 # SELECT id , type from u s e r s _r ep l i c a t e d ORDER BY i d ;
- id | type
- - - - - - - -+ - - - - - - -
- 1 | 75
5 2 | 262
- 3 | 458
- 4 | 779
- 5 | 357
- 6 | 51
10 7 | 249
- 8 | 444
- 9 | 890
- 10 | 810
- 11 | 809
15 12 | 166
- 13 | 605
- 14 | 401
- 15 | 58
Рассмотрим как выполняются запросы для таблиц. Выберем все записи
из распределенной таблицы:
133
6.3. Postgres-X2
Листинг 6.20 Выборка записей из распределенной таблицы
Line 1 # EXPLAIN VERBOSE SELECT * from users_with_modulo ORDER BY
id ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- So r t ( c os t = 4 9 . 8 3. .5 2. 3 3 rows =1000 width=8)
5 Output : id , type
- Sort Key : users_with_modulo . i d
- -> Re s u l t ( c os t = 0 . 0 0 . . 0 . 0 0 rows=1000 width=8)
- Output : id , type
- -> Data Node Scan on users_with_modulo ( c o s t
= 0 . 0 0 . . 0 . 0 0 rows=1000 width=8)
10 Output : id , type
- Node/ s : dn1 , dn2
- Remote query : SELECT id , type FROM ONLY
users_with_modulo WHERE tr u e
- (9 rows )
Как видно на листинге 6.20 координатор собирает данные из обработ-
чиков данных, а потом собирает их вместе.
Подсчет суммы с группировкой по полю из распределенной таблицы:
Листинг 6.21 Выборка записей из распределенной таблицы
Line 1 # EXPLAIN VERBOSE SELECT sum( i d ) from users_with_modulo
GROUP BY type ;
-
QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- HashAggregate ( c os t = 5 . 0 0 .. 5 . 0 1 rows=1 width =8)
5 Output : pg_catalog . sum ( ( sum ( users_with_modulo . i d ) ) ) ,
users_with_modulo . type
- -> M a t e r i a l i z e ( c o s t = 0 . 0 0 . . 0 . 0 0 rows=0 width=0)
- Output : (sum( users_with_modulo . i d ) ) ,
users_with_modulo . type
- -> Data Node Scan on "__REMOTE_GROUP_QUERY__" (
c o s t = 0 .0 0 . . 0 . 0 0 rows =1000 width=8)
- Output : sum( users_with_modulo . id ) ,
users_with_modulo . type
10 Node/ s : dn1 , dn2
- Remote query : SELECT sum( group_1 . i d ) , group_1
. type FROM (SELECT id , type FROM ONLY users_with_modulo
WHERE t r ue ) group_1 GROUP BY 2
134
6.3. Postgres-X2
- (8 rows )
JOIN между и с участием реплицированных таблиц, а также JOIN
между распределенными по одному и тому же полю в таблицах будет вы-
полняются на обработчиках данных. Но JOIN с участием распределенных
таблиц по другим ключам будут выполнены на координаторе и скорее все-
го это будет медленно (листинг 6.22).
Листинг 6.22 Выборка записей из распределенной таблицы
Line 1 # EXPLAIN VERBOSE SELECT * from users_with_modulo ,
users_with_hash WHERE users_with_modulo . i d =
users_with_hash . i d ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Nested Loop ( c o s t = 0 . 0 0 . . 0 . 0 1 rows=1 width =16)
5 Output : users_with_modulo . id , users_with_modulo . type ,
users_with_hash . id , users_with_hash . type
- Joi n F i l t e r : ( users_with_modulo . i d = users_with_hash . i d )
- -> Data Node Scan on users_with_modulo ( c o s t = 0 . 0 0 . . 0 . 0 0
rows =1000 width=8)
- Output : users_with_modulo . id , users_with_modulo .
type
- Node/ s : dn1 , dn2
10 Remote query : SELECT id , type FROM ONLY
users_with_modulo WHERE tr u e
- -> Data Node Scan on users_with_hash ( c o s t = 0 . 0 0 .. 0 . 0 0
rows =1000 width=8)
- Output : users_with_hash . id , users_with_hash . type
- Node/ s : dn1 , dn2
- Remote query : SELECT id , type FROM ONLY
users_with_hash WHERE t r u e
15 (11 rows )
Пример выборки данных из реплицированной таблицы:
Листинг 6.23 Выборка записей из реплицированной таблицы
Line 1 # EXPLAIN VERBOSE SELECT * from u s er s_ r e p l i c a t e d ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Data Node Scan on "__REMOTE_FQS_QUERY__" ( c os t = 0 . 0 0 . . 0 . 0 0
rows=0 width =0)
5 Output : u s e r s _ r e p l i ca t e d . id , u s e r s _ r e p li ca t e d . type
135
6.3. Postgres-X2
- Node/ s : dn1
- Remote query : SELECT id , type FROM u se r s _ r e p l i c a t ed
- (4 rows )
Как видно из запроса для выборки данных используется один обработ-
чик данных, а не все (что логично).
Высокая доступность (HA)
По архитектуре у Postgres-X2 всегда есть согласованность данных. По
теореме CAP в такой системе тяжело обеспечить высокую доступность.
Для достижения высокой доступности в распределенных системах тре-
буется избыточность данных, резервные копии и автоматическое восста-
новление. В Postgres-X2 избыточность данных может быть достигнута с
помощью PostgreSQL потоковой (streaming) репликации с hot-standby для
обработчиков данных. Каждый координатор способен записывать и чи-
тать данные независимо от другого, поэтому координаторы способны за-
менять друг друга. Поскольку GTM отдельный процесс и может стать
точкой отказа, лучше создать GTM-standby как резервную копию. Ну а
вот для автоматического восстановления придется использовать сторон-
ние утилиты.
Ограничения
1. Postgres-X2 базируется на PostgreSQL 9.3;
2. Нет системы репартиционирования при добавлении или удалении
нод;
3. Нет глобальных UNIQUE на распределенных таблицах;
4. Не поддерживаются foreign keys между нодами поскольку такой
ключ должен вести на данные расположенные на том же обработ-
чике данных;
5. Не поддерживаются курсоры;
6. Не поддерживается INSERT ... RETURNING;
7. Невозможно удаление и добавление нод в кластер без полной реини-
циализации кластера;
Заключение
Postgres-X2 очень перспективное решение для создания кластера на
основе PostgreSQL. И хоть это решение имеет ряд недостатков, нестабиль-
но (очень часты случаи падения координаторов при тяжелых запросах) и
еще очень молодое, со временем это решение может стать стандартом для
масштабирования систем на PostgreSQL.
136
6.4. Postgres-XL
6.4 Postgres-XL
Postgres-XL система для создания мульти-мастер кластеров, работаю-
щих в синхронном режиме все узлы всегда содержат актуальные данные.
Проект построен на основе кодовой базы Postgres-X2, поэтому архитек-
турный подход полностью идентичен (глобальный менеджер транзакций
(GTM), координаторы (coordinators) и обработчики данных (datanodes)).
Более подробно про архитектуру можно почитать в «6.3 Архитектура»
разделе. Поэтому рассмотрим только отличие Postgres-X2 и Postgres-XL.
Postgres-X2 и Postgres-XL
Одно из главных отличий Postgres-XL от Postgres-X2 является улуч-
шенный механизм массово-параллельной архитектуры (massive parallel
processing, MPP). Чтобы понять разницу, давайте рассмотрим как
Postgres-X2 и Postgres-XL будет обрабатывать разные SQL запросы. Оба
этих кластера будут содержать три таблицы T1, T2 и R1. T1 имеет колонки
a1 и a2, T2 b1 и b2. T1 распределена в кластере по a1 полю и T2 распре-
делена по b1 полю. R1 таблица имеет колонки c1 и c2 и реплицируется в
кластере (DISTRIBUTE by REPLICATION).
Для начала, простой запрос вида SELECT * FROM T1 будет параллель-
но выполняться на нодах как у Postgres-X2, так и у Postgres-XL. Другой
пример запроса SELECT * FROM T1 INNER JOIN R1 ON T1.a1 = R1.c1 бу-
дет также выполняться параллельно обоими кластерами, потому что будет
передан («pushed down») на обработчики данных (datanodes) для выпол-
нения и координатор (coordinators) будет только агрегировать (собирать)
результаты запросов. Это будет работать благодаря тому, что R1 таблица
дублицируется на каждом обработчике данных. Этот тип запросов будет
работать хорошо, когда T1 является таблицей фактов (основной таблицей
хранилища данных), в то время как R1 таблицей измерений (содержит
атрибуты событий, сохраненных в таблице фактов).
Теперь рассмотрим другой вариант SQL запроса:
Листинг 6.24 Запрос на распределенные таблицы
Line 1 # SELECT * FROM T1 INNER JOIN T2 ON T1 . a1 = T2 . b2
Данный запрос делает JOIN по распределенной колонке a1 в таблице
T1 и по НЕ распределенной колонке b2 в таблице T2. В кластере, который
состоит из 4 обработчиков данных, колонка в таблице T1 на первом из
них потенциально требуется объединить с колонками таблицы T2 на всех
обработчиках данных в кластере.
У Postgres-X2 в данном случае обработчики данных отправляют все
данные по заданному условию в запросе к координатору, который и за-
нимается объединением данных с таблиц. В данном примере отсутствует
137
6.5. Citus
условие WHERE, что значит, что все обработчики данных отправят все
содержимое таблиц T1 и T2 на координатор, который и будет делать JOIN
данных. В данной операции будет отсутствовать параллельное выполне-
ние JOIN запроса и будут дополнительные накладные расходы на доставку
всех данных к координатору. Поэтому в данном случае Postgres-X2 фак-
тически будет медленнее, чем выполнение подобного запроса на обычном
PostgreSQL сервере (особенно, если таблицы очень большие).
Postgres-XL будет обрабатывать подобный запрос по-другому. Условие
T1.a1 = T2.b2 говорит о том, что мы объединяем колонку b2 с колонкой
a1, которая является ключом распределения для таблицы T1. Поэтому,
выбрав значения поля b2, кластер будет точно знать для каких обработчи-
ков данных требуется полученный результат для объединения с таблицей
T1 (поскольку возможно применить хеш функцию распределения на полу-
ченные значения). Поэтому каждый обработчик данных считает с другого
обработчика данных требуемые данные по таблице T2 для объединения со
своей таблицей T1 без участия координатора. Данная возможность пря-
мой коммуникации обработчиков данных с другими обработчиками дан-
ных позволяет распараллеливать более сложные запросы в Postgres-XL.
Postgres-XL имеет также другие улучшения производительности (более
оптимально обрабатываются последовательности, прочее).
Заключение
Postgres-XL - еще одно перспективное решение для создания кластера
на основе Postgres-X2. Разработчики данного решения больше нацелены
на улучшение производительности и стабильности кластера, вместо добав-
ления нового функционала.
6.5 Citus
Citus горизонтально масштабируемый PostgreSQL кластер. Citus ис-
пользует механизм расширений PostgreSQL вместо того, что бы исполь-
зовать модифицированную версию базы (как это делает «6.3 Postgres-
X2» или «6.4 Postgres-XL»), что позволяет использовать новые версии
PostgreSQL с новыми возможностями, сохраняя при этом совместимость
с существующими PostgreSQL инструментами. Кластер предоставляет
пользователям результаты запросов в режиме «реального времени» для
большого и растущего объема данных (благодаря параллелизации запро-
сов между нодами). Примеры использования:
аналитика и вывод данных в реальном времени на графики;
хранение большого набора данных для архива и создание отчетов по
ним;
анализ и сегментация большого объема данных;
138
6.5. Citus
Нагрузки, которые требуют большой поток данных между узлами кла-
стера, как правило, не будет хорошо работать с Citus кластером. Напри-
мер:
традиционные хранилища данных с длительными и в свободном фор-
мате SQL запросами (data warehousing);
множественные распределенные транзакции между несколькими
шардами;
запросы, которые возвращают данные по тяжелым ETL запросам;
Архитектура
На верхнем уровне Citus кластер распределяет данные по PostgreSQL
экземплярам. Входящие SQL запросы затем обрабатываются параллельно
через эти сервера.
Рис. 6.4: Архитектура Citus кластера
При разворачивании кластера один из экземпляров PostgreSQL выби-
рается в качестве мастер (master) ноды. Затем остальные добавляются
как PostgreSQL воркеры (worker) в конфигурационном файле мастер но-
ды. После этого все взаимодействие с кластером ведется через мастер ноду
с помощью стандартных PostgreSQL интерфейсов. Все данные распреде-
лены по воркерам. Мастер хранит только метаданные о воркерах.
Citus использует модульную архитектуру для блоков данных, кото-
рая похожа на HDFS (Hadoop Distributed File System), но использует
PostgreSQL таблицы вместо файлов. Каждая из этих таблиц представля-
ет собой горизонтальный раздел или логический «шард» (shard). Каждый
139
6.5. Citus
шард дублируется, по крайней мере, на двух воркерах (можно настроить
на более высокое значение). В результате, потеря одной машины не вли-
яет на доступность данных. Логическая архитектура шардинга в Citus
также позволяет добавлять новые воркеры, чтобы увеличить пропускную
способность и вычислительную мощность кластера.
Citus мастер содержит таблицы метаданных для отслеживания всех
воркеров и расположение шардов базы данных на них. Эти таблицы также
ведут статистику, такую как размер и минимальное/максимальное зна-
чений в шардах, которые помогают распределению SQL запросов Citus
планировщику. Таблицы метаданных небольшие (обычно несколько мега-
байт), и могут быть дублированы и быстро восстановлены, если с масте-
ром когда-либо произойдет сбой. Подробнее о таблицах метаданных можно
глянуть в документации.
Когда кластер получает SQL запрос, Citus мастер делит его на более
мелкие фрагменты запросов, где каждый фрагмент может выполняться
независимо на воркере. Это позволяет Citus распределять каждый запрос
в кластере, используя вычислительные мощности всех задействованных
узлов, а также отдельных ядер на каждом узле. Мастер затем поруча-
ет воркерам выполнить запрос, осуществляет контроль за их исполнени-
ем, объединяет результаты по запросам и возвращает конечный результат
пользователю. Для того, чтобы гарантировать, что все запросы выполня-
ются в масштабируемой манере, мастер также применяет оптимизации,
которые сводят к минимуму объем данных, передаваемых по сети.
Citus кластер может легко переносить сбои воркеров из-за своей логи-
ческой шардинг архитектуры. Если воркер терпит неудачу во время вы-
полнения запроса, Citus завершает запрос, направляя неудачные части
запроса другим воркерам, которые имеют копию данных. Если воркер на-
ходится в нерабочем состоянии (сервер упал), пользователь может легко
произвести ребалансировку кластера, чтобы поддерживать тот же уровень
доступности.
Установка
Установка Citus кластера не требует особых усилий. Для использова-
ния в боевом окружении лучше изучить данную документацию. Для про-
верки, что кластер работает и мастер видит воркеры можно выполнить
команду master_get_active_worker_nodes, которая покажет список ворке-
ров:
Листинг 6.25 Список воркеров
Line 1 p os tg re s=# s e l e c t * from master_get_active_worker_nodes ( ) ;
- node_name | node_port
- - - - - - - - - - - -+ - - - - - - - - - - -
- l o c a l h o s t | 9702
140
6.5. Citus
5 l o c a l h o s t | 9701
- (2 rows )
Распределенные таблицы
Каждая распределенная таблица в Citus содержит стоблец, который
должен быть выбран в качестве значения для распределения по шардам
(возможно выбрать только один столбец). Это информирует базу данных
как хранить статистику и распределять запросы по кластеру. Как прави-
ло, требуется выбрать столбец, который является наиболее часто исполь-
зуемым в запросах WHERE. В таком случае запросы, которые в фильтре
используют данный столбец, будут выполняться на шардах, которые вы-
бираются по условию фильтрации. Это помогает значительно уменьшить
количество вычислений на шардах.
Следующим шагом после выбора столбца на распределения будет опре-
деление правильного метода распределения данных в таблицу. В целом,
существует два шаблона таблиц: распределенные по времени (время со-
здания заказа, запись логов, прочее) и распределение по идентификатору
(ID пользователя, ID приложения, прочее). Citus поддерживает оба метода
распределения: append и hash соответственно.
Append метод подходит для таблиц, в которые записываются данные по
времени (упорядочены по времени). Такой тип таблиц отлично справляет-
ся с запросами, которые используют фильтры с диапазонами значений по
распределенному столбцу (BETWEEN x AND y). Это объясняется тем, что
мастер хранит диапазоны значений, которые хранятся на шардах, и пла-
нировщик может эффективно выбирать шарды, которые содержат данные
для SQL запроса.
Hash метод распределения подходит для неупорядоченного столбца
(user UUID) или по данным, которые могут записываться в любом по-
рядке. В таком случае Citus кластер будет хранить минимальные и мак-
симальные значения для хеш функций на всех шардах. Эта модель лучше
подходит для SQL запросов, включающих фильтры на основе равенства по
колонке распределения (user_uuid=’a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11’).
Hash распределение
Для примера создадим и распределим таблицу по hash методу.
Листинг 6.26 Создание таблицы
Line 1 # CREATE TABLE github_events
- (
- event_id b ig i nt ,
- event_type text ,
5 event_public boolean ,
141
6.5. Citus
- repo_id b i gi n t ,
- payload jsonb ,
- repo jsonb ,
- a c to r jsonb ,
10 org jsonb ,
- created_at timestamp
- ) ;
Далее укажем Citus кластеру использовать repo_id с hash распределе-
нием для github_events таблицы.
Листинг 6.27 Создание hash распределения
Line 1 # SELECT m as t e r _ c r e a t e _ d i s t r i b u t e d _ t a b l e ( github_events ,
repo_id , hash ) ;
И создадим шарды для таблицы:
Листинг 6.28 Создание шардов
Line 1 # SELECT master_create_worker_shards ( github_events , 16 , 1)
;
Данный метод принимает два аргумента в дополнение к имени табли-
цы: количество шардов и коэффициент репликации. Этот пример позво-
лит создать в общей сложности шестнадцать шардов, где каждый будет
владеть частью символического пространства хэша, а данные будут реп-
лицироваться на один воркер.
Далее мы можем заполнить таблицу данными:
Листинг 6.29 Загрузка данных
Line 1 $ wget http : / / examples . c i t u sd a t a . com/ git hub_ a rchi ve /
github_events - 2 0 1 5 - 0 1 - 01 - { 0 . .5 } . csv . gz
- $ gzi p -d github_events - 2 015 - 0 1 - 01 - * . gz
Листинг 6.30 Загрузка данных
Line 1 # \COPY github_events FROM github_events - 2 0 15 - 0 1 - 0 1 - 0 . csv
WITH ( format CSV)
- # INSERT INTO github_events VALUES (2489373118 , PublicEvent
, t ,2 45090 48 , {} , {" i d " : 24509048 , " u r l " : " h t t p s : // api .
gi thub . com/ r e p os /SabinaS / csee6 8 6 8 " , "name " : " SabinaS /
csee6 8 6 8 "} , {" id " : 2955009 , " u r l " : " h t tp s : // api . g ithu b .
com/ u se rs / SabinaS " , " l o g i n " : " SabinaS " , " avatar _url " : "
http s : / / av a ta rs . g it h ubu s erc o nte n t . com/u/2955009?" , "
gr avatar_id " : ""} ,NULL, 2015 -01 -01 0 0 : 0 9 : 13 ) ;
142
6.5. Citus
Теперь мы можем обновлять и удалять данные с таблицы:
Листинг 6.31 Изменение данных
Line 1 # UPDATE github_events SET org = NULL WHERE repo_id =
245 0 9048;
- # DELETE FROM github_events WHERE repo_id = 245 0 9048;
Для работы UPDATE и DELETE запросов требуется, что бы он «затра-
гивал» один шард. Это означает, что условие WHERE должно содержать
условие, что ограничит выполнение запроса на один шард по распреде-
ленному столбцу. Для обновления или удаления данных на нескольких
шардах требуется использовать команду master_modify_multiple_shards:
Листинг 6.32 Изменение данных на нескольких шардах
Line 1 # SELECT master_modify_multiple_shards (
- DELETE FROM github_events WHERE repo_id IN (245 0 9048 ,
24509049) ) ;
Для удаления таблицы достаточно выполнить DROP TABLE на масте-
ре:
Листинг 6.33 Удаление таблицы
Line 1 # DROP TABLE github_events ;
Append распределение
Для примера создадим и распределим таблицу по append методу.
Листинг 6.34 Создание таблицы
Line 1 # CREATE TABLE github_events
- (
- event_id b ig i nt ,
- event_type text ,
5 event_public boolean ,
- repo_id b i gi n t ,
- payload jsonb ,
- repo jsonb ,
- a c to r jsonb ,
10 org jsonb ,
- created_at timestamp
- ) ;
Далее укажем Citus кластеру использовать created_at с append распре-
делением для github_events таблицы.
143
6.5. Citus
Листинг 6.35 Создание hash распределения
Line 1 # SELECT m as t e r _ c r e a t e _ d i s t r i b u t e d _ t a b l e ( github_events ,
created_at , append ) ;
После этого мы можем использовать таблицу и загружать в нее данные:
Листинг 6.36 Загрузка данных
Line 1 # SET c i t u s . shard_max_size TO 64MB ;
- # \copy github_events from github_events -2 0 15 -0 1 - 01 - 0 . c sv
WITH ( format CSV)
По умолчанию команда \copy требует два конфигурационных парамет-
ра для работы: citus .shard_max_size и citus .shard_replication_factor.
citus .shard_max_size параметр указывает максимальный размер
шарда при использовании команды \copy (1Гб по умолчанию). Если
файл больше данного параметра, то команда автоматически разо-
бьет файл по нескольким шардам;
citus .shard_replication_factor параметр количество воркеров, на кото-
рые шарды будут реплицироваться (2 по умолчанию);
По умолчанию команда \copy создает каждый раз новый шард для дан-
ных. Если требуется добавлять данные в один и тот же шард, существуют
команды master_create_empty_shard, которая вернет идентификатор на но-
вый шард, и команда master_append_table_to_shard для добавления дан-
ных в этот шард по идентификатору.
Для удаления старых данных можно использовать команду
master_apply_delete_command, которая удаляет старые шарды, кото-
рые попадают в переданное условие на удаление:
Листинг 6.37 Удаление старых шардов
Line 1 # SELECT * from master_apply_delete_command ( DELETE FROM
github_events WHERE created_a t >= 2015 -01 -01 0 0 : 0 0 : 0 0
) ;
- master_apply_delete_command
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 3
5 (1 row )
Для удаления таблицы достаточно выполнить DROP TABLE на масте-
ре:
Листинг 6.38 Удаление таблицы
Line 1 # DROP TABLE github_events ;
144
6.6. Greenplum Database
Ребалансировка кластера
Логическая архитектура шардинга Citus позволяет масштабировать
кластер без каких-либо простоев (no downtime!). Для добавления нового
воркера достаточно добавить его в pg_worker_list.conf и вызвать на мастере
pg_reload_conf для загрузки новой конфигурации:
Листинг 6.39 Загрузка новой конфигурации
Line 1 # SELECT pg_reload_conf ( ) ;
После этого Citus автоматически начнет использовать данный вор-
кер для новых распределенных таблиц. Если требуется ребалансиро-
вать существующие таблицы на новый воркер, то для этого есть коман-
да rebalance_table_shards, но, к сожалению, она доступна только в Citus
Enterprise (платное решение).
Ограничения
Модель расширения PostgreSQL в Citus позволяет использовать до-
ступные типы данных (JSON, JSONB, другие) и другие расширения в
кластере. Но не весь спектр SQL запросов доступен для распределенных
таблиц. На текущий момент распределенные таблицы не поддерживают:
Оконные функции (window functions);
Общие табличные выражения (CTE);
UNION операции (UNION/INTERSECT/EXCEPT);
Транзакционная семантика для запросов, которые распределены по
нескольким шардам;
Заключение
Citus кластер достаточно гибкое и мощное решение для горизонтально-
го масштабирования PostgreSQL. Зрелость данного решения показывает
его использование такими игроками на рынке, как CloudFlare, Heap и мно-
гими другими.
6.6 Greenplum Database
Greenplum Database (GP) реляционная СУБД, имеющая массово-
параллельную (massive parallel processing) архитектуру без разделения
ресурсов (shared nothing). Для подробного понимания принципов работы
Greenplum необходимо обозначить основные термины:
145
6.6. Greenplum Database
Master instance («мастер») инстанс PostgreSQL, являющийся од-
новременно координатором и входной точкой для пользователей в
кластере;
Master host («сервер-мастер») сервер, на котором работает master
instance;
Secondary master instance — инстанс PostgreSQL, являющийся ре-
зервным мастером, включается в работу в случае недоступности ос-
новного мастера (переключение происходит вручную);
Primary segment instance («сегмент») инстанс PostgreSQL, являю-
щийся одним из сегментов. Именно сегменты непосредственно хра-
нят данные, выполняют с ними операции и отдают результаты ма-
стеру общем случае). По сути сегмент самый обычный инстанс
PostgreSQL 8.3.23 с настроенной репликацией в своё зеркало на дру-
гом сервере;
Mirror segment instance («зеркало») инстанс PostgreSQL, являю-
щийся зеркалом одного из primary сегментов, автоматически прини-
мает на себя роль primary в случае падения оного. Greenplum поддер-
живает только 1-to-1 репликацию сегментов: для каждого из primary
может быть только одно зеркало;
Segment host («сервер-сегмент») сервер, на котором работает один
или несколько сегментов и/или зеркал;
В общем случае кластер GP состоит из нескольких серверов-сегментов,
одного сервера-мастера, и одного сервера-секондари-мастера, соединённых
между собой одной или несколькими быстрыми (10g, infiniband) сетями,
обычно обособленными (interconnect) (рис 6.5).
Рис. 6.5: Состав кластера и сетевое взаимодействие элементов. Зелёная и
красная линии обособленные сети interconnect, синяя линия внешняя,
клиентская сеть
146
6.6. Greenplum Database
Использование нескольких interconnect-сетей позволяет, во-первых, по-
высить пропускную способность канала взаимодействия сегментов между
собой, и во-вторых, обеспечить отказоустойчивость кластера случае от-
каза одной из сетей весь трафик перераспределяется между оставшимися).
При выборе числа серверов-сегментов важно правильно выбрать со-
отношение кластера «число процессоров/Тб данных» в зависимости от
планируемого профиля нагрузки на БД чем больше процессорных ядер
приходится на единицу данных, тем быстрее кластер будет выполнять «тя-
жёлые» операции, а также работать со сжатыми таблицами.
При выборе числа сегментов в кластере оторое в общем случае к
числу серверов никак не привязано) необходимо помнить следующее:
все ресурсы сервера делятся между всеми сегментами на сервере (на-
грузкой зеркал, в случае если они располагаются на этих же серве-
рах, можно условно пренебречь);
каждый запрос на одном сегменте не может потреблять процессор-
ных ресурсов больше, чем одно ядро CPU. Это означает, например,
что, если кластер состоит из 32-ядерных серверов с 4-я сегментами
GP на борту и используется в среднем для обработки 3-4 одновремен-
ных тяжёлых, хорошо утилизирующих CPU, запросов, «в среднем по
больнице» CPU не будет утилизироваться оптимально. В данной си-
туации лучше увеличить число сегментов на сервере до 6-8;
штатный процесс бэкапа и рестора данных «из коробки» работает
только на кластерах, имеющих одинаковое число сегментов. Восста-
новить данные, забэкапленные на кластере из 96 сегментов, в кластер
из 100 сегментов без напильника будет невозможно;
Хранение данных
В Greenplum реализуется классическая схема шардирования данных.
Каждая таблица представляет из себя N+1 таблиц на всех сегментах кла-
стера, где N число сегментов (+1 в этом случае это таблица на масте-
ре, данных в ней нет). На каждом сегменте хранится 1/N строк таблицы.
Логика разбиения таблицы на сегменты задаётся ключом (полем) дистри-
буции таким полем, на основе данных которого любую строку можно
отнести к одному из сегментов.
Ключ (поле или набор полей) дистрибуции очень важное понятие в
GP. Как было сказано выше, Greenplum работает со скоростью самого мед-
ленного сегмента, это означает, что любой перекос в количестве данных
ак в рамках одной таблицы, так и в рамках всей базы) между сегмен-
тами ведёт к деградации производительности кластера, а также к другим
проблемам. Именно поэтому следует тщательно выбирать поле для дис-
трибуции распределение количества вхождений значений в нём должно
быть как можно более равномерным. Правильно ли вы выбрали ключ дис-
трибуции вам подскажет служебное поле gp_segment_id, существующее в
147
6.6. Greenplum Database
каждой таблице оно содержит номер сегмента, на котором хранится
конкретная строка. Важный нюанс: GP не поддерживает UPDATE поля,
по которому распределена таблица.
Рассмотрим пример (здесь и далее в примерах кластер состоит из 96
сегментов):
Листинг 6.40 Создание распределенной таблицы
Line 1 db=# c r e a t e t a bl e d i s t r i b _ t e s t _ ta bl e as s e l e c t
g e n e r a t e _ se ri es ( 1 , 2 0) as num_field d i s t r i b u t e d by (
num_field ) ;
- SELECT 20
- db=# s e l e c t count ( 1) , gp_segment_id from d is tr ib _t es t_ ta bl e
group by gp_segment_id o r d e r by gp_segment_id ;
- count | gp_segment_id
5 - - - - - - - + - - - - - - - - - - - - - - -
- 1 | 4
- 1 | 6
- 1 | 15
- 1 | 21
10 1 | 23
- 1 | 25
- 1 | 31
- 1 | 40
- 1 | 42
15 1 | 48
- 1 | 50
- 1 | 52
- 1 | 65
- 1 | 67
20 1 | 73
- 1 | 75
- 1 | 77
- 1 | 90
- 1 | 92
25 1 | 94
-
- db=# t r unc a te t ab le d i st ri b_ te st _t ab le ;
- TRUNCATE TABLE
- db=# i n s e r t i n to d i s t r i b _ t e s t _ t a b l e v a lu e s ( 1) , ( 1) , ( 1) ,
( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) ,
( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) , ( 1 ) ;
30 INSERT 0 20
- db=# s e l e c t count ( 1) , gp_segment_id from d is tr ib _t es t_ ta bl e
group by gp_segment_id o r d e r by gp_segment_id ;
- count | gp_segment_id
- - - - - - - - + - - - - - - - - - - - - - - -
148
6.6. Greenplum Database
- 20 | 42
В обоих случаях распределена таблица по полю num_field. В первом
случае вставили в это поле 20 уникальных значений, и, как видно, GP
разложил все строки на разные сегменты. Во втором случае в поле было
вставлено 20 одинаковых значений, и все строки были помещены на один
сегмент.
В случае, если в таблице нет подходящих полей для использования в
качестве ключа дистрибуции, можно воспользоваться случайной дистри-
буцией (DISTRIBUTED RANDOMLY). Поле для дистрибуции можно ме-
нять в уже созданной таблице, однако после этого её необходимо пере-
распределить. Именно по полю дистрибуции Greenplum совершает самые
оптимальные JOIN: в случае, если в обоих таблицах поля, по которым
совершается JOIN, являются ключами дистрибуции, JOIN выполняется
локально на сегменте. Если же это условие не верно, GP придётся или
перераспределить обе таблицы по искомому полю, или закинуть одну из
таблиц целиком на каждый сегмент (операция BROADCAST) и уже затем
джойнить таблицы локально на сегментах.
Листинг 6.41 JOIN по ключу дистрибуции
Line 1 db=# c r e a t e t a bl e d i s t r i b _ t e s t _ ta bl e as s e l e c t
g e n e r a t e _ se ri es ( 1 , 192 ) as num_field , g e n e r a t e _ s e r i e s
( 1 , 19 2) as num_field_2 d i s t r i b u t e d by ( num_field ) ;
- SELECT 192
- db=# c r e a t e t a b l e d i st r ib _ te s t_ t ab le _ 2 as s e l e c t
g e n e r a t e _ se ri es ( 1 , 1 0 00) as num_field , g e n e r a t e _ s e r i e s
(1 , 1 0 00) as num_field_2 d i s t r i b u t e d by ( num_field ) ;
- SELECT 1000
5 db=# e x p la i n s e l e c t * from d is tr ib _t es t_ ta bl e sq
- db-# l e f t j o i n d is tr i b_ t es t _t a bl e_ 2 sq2
- db-# on sq . num_field = sq2 . num_field ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 Gather Motion 96: 1 ( s l i c e 1 ; segments : 96) ( c o st
= 2 0 . 3 7 . . 42 .9 0 rows=861 width=16)
- -> Hash L e f t Join ( c o s t = 20 .3 7 . . 4 2 . 9 0 rows=9 width =16)
- Hash Cond : sq . num_field = sq2 . num_field
- -> Seq Scan on d i s t r i b _ t e s t _ t a b l e sq ( c o st
= 0 . 0 0 . . 9 . 6 1 rows=9 width =8)
- -> Hash ( c o s t = 9 . 6 1 . . 9 . 6 1 rows=9 width =8)
15 -> Seq Scan on dist r i b _t e st _ ta b le _2 sq2 (
c o s t = 0 .0 0 . . 9 . 6 1 rows=9 width =8)
149
6.6. Greenplum Database
Листинг 6.42 JOIN не по ключу дистрибуции
Line 1 db_dev=# e x p la i n s e l e c t * from d i s tr ib _ t e s t _ t a b l e sq l e f t
j o i n d i s tr i b_ t es t _t a bl e_ 2 sq2
- on sq . num_field_2 = sq2 . num_field_2 ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5 Gather Motion 96: 1 ( s l i c e 3 ; segments : 96) ( c o st
= 3 7 . 5 9 . . 77 .3 4 rows=861 width=16)
- -> Hash L e f t Join ( c o s t = 37 .5 9 . . 7 7 . 3 4 rows=9 width =16)
- Hash Cond : sq . num_field_2 = sq2 . num_field_2
- -> R e d i s t r i b u t e Motion 9 6: 9 6 ( s l i c e 1 ; segments :
96) ( c o s t = 0 . 0 0 . . 2 6 . 83 rows=9 width=8)
- Hash Key : sq . num_field_2
10 -> Seq Scan on d i s t r i b _ t e s t _ t a b l e sq ( c o s t
= 0 . 0 0 . . 9 . 6 1 rows=9 width =8)
- -> Hash ( c o s t = 2 6 .8 3. . 2 6 . 8 3 rows=9 width=8)
- -> R e d i s t r i b u t e Motion 9 6: 9 6 ( s l i c e 2 ;
segments : 96) ( c o s t = 0 . 0 0 . . 2 6. 83 rows=9 width =8)
- Hash Key : sq2 . num_field_2
- -> Seq Scan on distr i b _ te s t_ t ab l e_ 2
sq2 ( c o s t = 0 . 0 0 . . 9 . 6 1 rows=9 width=8)
Как видно в примере «JOIN не по ключу дистрибуции» в плане запроса
появляются два дополнительных шага (по одному для каждой из участву-
ющих в запросе таблиц): Redistribute Motion. По сути, перед выполнением
запроса GP перераспределяет обе таблицы по сегментам, используя логику
поля num_field_2, а не изначального ключа дистрибуции поля num_field.
Взаимодействие с клиентами
В общем случае всё взаимодействие клиентов с кластером ведётся толь-
ко через мастер — именно он отвечает клиентам, выдаёт им результат
запроса и т. д. Обычные клиенты не имеют сетевого доступа к серверам-
сегментам.
Для ускорения загрузки данных в кластер используется bulk load
параллельная загрузка данных с/на клиент одновременно с нескольких
сегментов. Bulk load возможен только с клиентов, имеющих доступ в ин-
терконнекты. Обычно в роли таких клиентов выступают ETL-сервера и
другие системы, которым необходима загрузка большого объёма данных
(на рис 6.5 они обозначены как ETL/Pro client).
Для параллельной загрузки данных на сегменты используется утили-
та gpfdist. По сути, утилита поднимает на удалённом сервере web-сервер,
который предоставляет доступ по протоколам gpfdist и http к указанной
папке. После запуска директория и все файлы в ней становятся доступны
150
6.6. Greenplum Database
обычным wget. Создадим для примера файл в директории, обслуживаемой
gpfdist, и обратимся к нему как к обычной таблице.
Листинг 6.43 Пример с gpfdist
Line 1 # На ETLсервере - :
- bash# f o r i i n { 1 . . 1 0 0 0 } ; do echo " $i , $ ( c a t /dev/urandom |
t r - dc a - zA - Z0 - 9 | f o l d -w 8 | head - n 1) " ; done > /tmp
/work/ gpfdist_home / t e st _ ta b le . c sv
-
- # Теперь создадим внешнюю таблицу и прочитаем данные из файла
5 # В Greenplum DB:
- db=# c r e a t e e x t e r n a l t a b l e ext_te s t _table
- db-# ( i d i n te g e r , rand var char ( 8) )
- db-# l o c a t i o n ( g p f d i s t : / / etl_hostname :8081/ t e st _ ta b le . csv )
- db-# format ’TEXT ( d e l i m i t e r , NULL ) ;
10 CREATE EXTERNAL TABLE
- db_dev=# s e l e c t * from e x t _test_ t a ble l i m i t 1 0 0 ;
- NOTICE: External scan from g p f d i s t ( s ) s e r v e r w i l l u t i l i z e
64 out o f 96 segment d a t a b a s e s
- i d | rand
- - - - - - + - - - - - - - - - -
15 1 | UWlonJHO
- 2 | HTyJNA41
- 3 | CBP1QSn1
- 4 | 0K9y51a3
- . . .
Также, но с немного другим синтаксисом, создаются внешние web-
таблицы. Их особенность заключается в том, что они ссылаются на http
протокол, и могут работать с данными, предоставляемыми сторонними
web-серверами (apache, nginx и другие).
В Greenplum также существует возможность создавать внешние таб-
лицы на данные, лежащие на распределённой файловой системе Hadoop
(hdfs) за это в GP ответственна отдельная компонента gphdfs. Для обес-
печения её работы на каждый сервер, входящий в состав кластера GP,
необходимо установить библиотеки Hadoop и прописать к ним путь в од-
ной из системных переменных базы. Создание внешней таблицы, обраща-
ющейся к данным на hdfs, будет выглядеть примерно так:
Листинг 6.44 Пример с gphdfs
Line 1 db=# c r e a t e e xt e r n a l t ab le h d f s _ t e s t_table
- db=# ( i d i nt , rand t ex t )
- db=# l o c a t i o n ( gphdfs : // hadoop_name_node : 8 0 2 0 / tmp/ t e s t _ f i l e .
cs v )
- db=# format ’TEXT ( d e l i m i t e r , ) ;
151
6.6. Greenplum Database
где hadoop_name_node — адрес хоста неймноды, /tmp/test_file.csv
путь до искомого файла на hdfs.
При обращении к такой таблице Greenplum выясняет у неймноды
Hadoop расположение нужных блоков данных на датанодах, к которым
затем обращается с серверов-сегментов параллельно. Естественно, все
ноды кластера Hadoop должны быть в сетях интерконнекта кластера
Greenplum. Такая схема работы позволяет достичь значительного приро-
ста скорости даже по сравнению с gpfdist. Что интересно, логика выбора
сегментов для чтения данных с датанод hdfs является весьма нетривиаль-
ной. Например, GP может начать тянуть данные со всех датанод толь-
ко двумя сегмент-серверами, причём при повторном аналогичном запросе
схема взаимодействия может поменяться.
Также есть тип внешних таблиц, которые ссылаются на файлы на
сегмент-серверах или файл на мастере, а также на результат выполнения
команды на сегмент-серверах или на мастере. К слову сказать, старый доб-
рый COPY FROM никуда не делся и также может использоваться, однако
по сравнению с описанным выше работает он медленней.
Надёжность и резервирование
Резервирование мастера
Как было сказано ранее, в кластере GP используется полное резервиро-
вание мастера с помощью механизма репликации транзакционных логов,
контролируемого специальным агентом (gpsyncagent). При этом автомати-
ческое переключение роли мастера на резервный инстанс не поддержива-
ется. Для переключения на резервный мастер необходимо:
убедиться, что основной мастер остановлен (процесс убит и в рабочей
директории инстанса мастера отсутствует файл postmaster.pid);
на сервере резервного мастера выполнить команду gpactivatestandby
-d /master_instance_directory;
переключить виртуальный ip-адрес на сервер нового мастера (меха-
низм виртуального ip в Greenplum отсутствует, необходимо исполь-
зовать сторонние инструменты);
Как видно, переключение выполняется совсем не сложно и при приня-
тии определённых рисков может быть автоматизировано.
Резервирование сегментов
Схема резервирования сегментов похожа на таковую для мастера, от-
личия совсем небольшие. В случае падения одного из сегментов (инстанс
PostgreSQL перестаёт отвечать мастеру в течении таймаута) сегмент поме-
чается как сбойный, и вместо него автоматически запускается его зеркало
152
6.6. Greenplum Database
(по сути, абсолютно аналогичный инстанс PostgreSQL). Репликация дан-
ных сегмента в его зеркало происходит на основе кастомной синхронной
репликации на уровне файлов.
Cтоит отметить, что довольно важное место в процессе планирования
архитектуры кластера GP занимает вопрос расположения зеркал сегмен-
тов на серверах, благо GP даёт полную свободу в вопросе выбора мест
расположения сегментов и их зеркал: с помощью специальной карты рас-
положения сегментов их можно разместить на разных серверах, в разных
директориях и заставить использовать разные порты. Рассмотрим два ва-
рианта:
Рис. 6.6: Все зеркала сегментов, располагающихся на хосте N, находятся
на хосте N+1
При использовании схемы 6.6 при отказе одного из серверов на сервере-
соседе оказывается в два раза больше работающих сегментов. Как было
сказано выше, производительность кластера равняется производительно-
сти самого медленного из сегментов, а значит, в случае отказа одного сер-
вера производительность базы снижается минимум вдвое. Однако, такая
схема имеет и положительные стороны: при работе с отказавшим сервером
уязвимым местом кластера становится только один сервер тот самый,
куда переехали сегменты.
При использовании схемы 6.7 в случае отказа сервера возросшая на-
грузка равномерно распределяется между несколькими серверами, не
сильно влияя на общую производительность кластера. Однако, существен-
но повышается риск выхода из строя всего кластера достаточно выйти
из строя одному из M серверов, соседствующих с вышедшим из строя из-
начально.
Истина, как это часто бывает, где-то посередине — можно располо-
жить по несколько зеркал сегментов одного сервера на нескольких других
153
6.6. Greenplum Database
Рис. 6.7: Все зеркала сегментов, располагающихся на хосте N, равномерно
«мажутся» на сервера N+1, N+2 ... N+M, где M число сегментов на
сервере
серверах, можно объединять сервера в группы отказоустойчивости, и так
далее. Оптимальную конфигурацию зеркал следует подбирать исходя из
конкретных аппаратных данных кластера, критичности простоя и так да-
лее.
Также в механизме резервирования сегментов есть ещё один нюанс,
влияющий на производительность кластера. В случае выхода из строя зер-
кала одного из сегментов последний переходит в режим change tracking
сегмент логирует все изменения, чтобы затем при восстановлении упав-
шего зеркала применить их к нему, и получить свежую, консистентную
копию данных. Другими словами, при падении зеркала нагрузка, создава-
емая на дисковую подсистему сервера сегментом, оставшимся без зеркала,
существенно возрастает.
При устранении причины отказа сегмента (аппаратные проблемы, кон-
чившееся место на устройстве хранения и прочее) его необходимо вернуть
в работу вручную, с помощью специальной утилиты gprecoverseg (даун-
тайм СУБД не требуется). По факту эта утилита скопирует скопившиеся
на сегменте WA-логи на зеркало и поднимет упавший сегмент/зеркало. В
случае, если речь идёт о primary-сегменте, изначально он включится в ра-
боту как зеркало для своего зеркала, ставшего primary (зеркало и основной
сегмент будут работать поменявшись ролями). Для того, чтобы вернуть
всё на круги своя, потребуется процедура ребаланса смены ролей. Такая
процедура также не требует даунтайма СУБД, однако на время ребаланса
все сессии в БД подвиснут.
154
6.6. Greenplum Database
В случае, если повреждения упавшего сегмента настолько серьёзны,
что простым копированием данных из WA-логов не обойтись, есть воз-
можность использовать полное восстановление упавшего сегмента в та-
ком случае, по факту, инстанс PostgreSQL будет создан заново, однако за
счёт того, что восстановление будет не инкрементальным, процесс восста-
новления может занять продолжительное время.
Производительность
Оценка производительности кластера Greenplum понятие довольно
растяжимое. Исходные данные: кластер из 24 сегмент-серверов, каждый
сервер 192 Гб памяти, 40 ядер. Число primary-сегментов в кластере: 96.
В первом примере мы создаём таблицу с 4-я полями + первичный ключ
по одному из полей. Затем мы наполняем таблицу данными (10 000 000
строк) и пробуем выполнить простой SELECT с несколькими условиями.
Листинг 6.45 SELECT с условиями
Line 1 db=# CREATE TABLE t e s t 3
- db-# ( i d b i g i n t NOT NULL,
- db(# p r o f i l e b i g i n t NOT NULL,
- db(# s t a t u s i n t e g e r NOT NULL,
5 db(# switch_date timestamp without time zone NOT NULL,
- db(# CONSTRAINT test3_id_pkey PRIMARY KEY ( i d ) )
- db-# d i s t r i b u t e d by ( i d ) ;
- NOTICE: CREATE TABLE / PRIMARY KEY w i l l c r e a t e i m p l i c i t
in dex " test3_pkey " f o r t ab le " t e s t 3 "
- CREATE TABLE
10
- db=# i n s e r t i n to t e s t 3 ( i d , p r o f i l e , s t a t us , switch_date )
s e l e c t a , round ( random ( ) *100000) , round ( random ( ) *4) , now
( ) - 1 yea r : : i n t e r v a l * round ( random ( ) * 40) from
g e n e r a t e _ se ri es ( 1 , 1 0 0 0 0 0 0 0 ) a ;
- INSERT 0 10000000
-
- db=# e x p la i n analy z e s e l e c t p r o f i l e , count ( s t at u s ) from
t e s t 3
15 db=# where sta t u s <>2
- db=# and switch_date between
1970 -01 -01 and 2015 -01 -01 group by p r o f i l e ;
-
- Gather Motion 9 6 :1 ( s l i c e 2 ; segments : 96) ( c o s t
= 2 0 92 . 80. . 20 9 2 .9 3 rows=10 width =16)
- Rows out : 100001 rows at d e s t i n a t i o n with 141 ms to f i r s t
row , 169 ms to end , s t a r t o f f s e t by 0 . 77 8 ms .
20 -> HashAggregate ( c o s t = 2 0 92 . 80. . 20 9 2 .9 3 rows=1 width=16)
155
6.6. Greenplum Database
- Group By : t e s t 3 . p r o f i l e
- Rows out : Avg 1 0 4 1 .7 rows x 96 wo rkers . Max 1061 rows (
seg 20 ) with 141 ms to end , s t a r t o f f s e t by 2. 2 8 1 ms .
- Executor memory : 4233K byt e s avg , 4233K b y t es max ( seg 0 ) .
- -> R e d i s t r i b u t e Motion 9 6 :9 6 ( s l i c e 1 ; segments : 96) ( c o s t
= 2 0 92 . 45. . 20 9 2 .6 5 rows=1 width=16)
25 Hash Key : t e s t 3 . p r o f i l e
- Rows out : Avg 53770.2 rows x 96 workers at d e s t i n a t i o n
. Max 54896 rows ( seg2 0 ) with 71 ms to f i r s t row , 117 ms
to end , s t a r t o f f s e t by 5 . 2 0 5 ms .
- -> HashAggregate ( c os t = 2 09 2 . 45 . .20 9 2. 4 5 rows=1 width
=16)
- Group By : t e s t 3 . p r o f i l e
- Rows out : Avg 53770.2 rows x 96 workers . Max 54020
rows ( seg 69 ) with 71 ms to f i r s t row , 90 ms to end , s t a r t
o f f s e t by 7 .0 1 4 ms .
30 Executor memory : 7882K by t e s avg , 7882K b y t e s max (
seg 0 ) .
- -> Seq Scan on t e s t 3 ( c o s t = 0 . 0 0. . 2 0 87 .0 4 rows=12 width
=12)
- F i l t e r : s t a t us <> 2 AND switch_date >= 1970 -01 -01
0 0 : 0 0 : 0 0 : : timestamp without time zone AND switch_date <=
2015 -01 -01 0 0 : 0 0 : 0 0 : : timestamp without time zone
- Rows out : Avg 77155 . 1 rows x 96 workers . Max 77743
rows ( seg 26 ) with 0 . 09 2 ms to f i r s t row , 31 ms to end ,
s t a r t o f f s e t by 7 . 8 8 1 ms .
- S l i c e s t a t i s t i c s :
35 ( s l i c e 0 ) Executor memory : 364K bytes .
- ( s l i c e 1 ) Executor memory : 9675K by t e s avg x 96 workers , 9675
K bytes max ( seg0 ) .
- ( s l i c e 2 ) Executor memory : 4526K by t e s avg x 96 workers , 4526
K bytes max ( seg0 ) .
- Statement s t a t i s t i c s :
- Memory used : 128000K bytes
40 Total runtime : 1 7 5.859 ms
Как видно, время выполнения запроса составило 175 мс. Теперь по-
пробуем пример с джойном по ключу дистрибуции одной таблицы и по
обычному полю другой таблицы.
Листинг 6.46 JOIN по ключу дистрибуции одной таблицы и по обычному полю другой
Line 1 db=# c r e a t e t a bl e test3_1 ( id b i g i n t NOT NULL, name text ,
CONSTRAINT test3_1_id_pkey PRIMARY KEY ( i d ) ) d i s t r i b u t e d
by ( i d ) ;
- NOTICE: CREATE TABLE / PRIMARY KEY w i l l c r e a t e i m p l i c i t
in dex " test3_1_pkey " f o r t a b l e " test3_1 "
156
6.6. Greenplum Database
- CREATE TABLE
- db=# i n s e r t i n to test3_1 ( i d , name ) s e l e c t a , md5( random ( )
: : t e xt ) from g en er at e_ s e r i e s ( 1 , 1 0 0 00 0 ) a ;
5 INSERT 0 100000
- db=# e x p la i n analy z e s e l e c t t e s t 3 . * , test3_1 . name from t e s t 3
j o i n test3_1 on t e s t 3 . p r o f i l e=test3_1 . i d ;
-
- -> Hash J oin ( c o s t = 3 4. 5 2. . 5 0 99 . 48 rows =1128 width=60)
- Hash Cond : t e s t 3 . p r o f i l e = test3_1 . i d
10 Rows out : Avg 1 0 4166.2 rows x 96 work ers . Max 106093 rows
( s eg20 ) with 7 . 6 4 4 ms to f i r s t row , 103 ms to end , s t a r t
o f f s e t by 223 ms .
- Executor memory : 74K b y t e s avg , 75K bytes max ( seg2 0 ) .
- Work_mem used : 74K bytes avg , 75K bytes max ( seg2 0 ) .
Workfile : (0 s p i l l i n g , 0 r e u sed )
- ( seg2 0 ) Hash c hain l en gth 1 . 0 avg , 1 max, us i n g 1061 o f
262151 buc k ets .
- -> R e d i s t r i b u t e Motion 9 6 :9 6 ( s l i c e 1 ; segments : 96) ( c o s t
= 0 . 0 0. . 3 4 40 . 6 4 rows=1128 width=28)
15 Hash Key : t e s t 3 . p r o f i l e
- Rows out : Avg 1041 6 6 .7 rows x 96 work ers at
d e s t i n a t i o n . Max 106093 rows ( seg2 0 ) with 3.160 ms to
f i r s t row , 44 ms to end , s t a r t o f f s e t by 228 ms .
- -> Seq Scan on t e s t 3 ( c o s t = 0 . 0 0. . 1 2 74 .8 8 rows=1128
width =28)
- Rows out : Avg 1041 6 6 .7 rows x 96 work ers . Max 104209
rows ( seg 66 ) with 0 . 16 5 ms to f i r s t row , 16 ms to end ,
s t a r t o f f s e t by 228 ms .
- -> Hash ( c os t = 1 7 . 0 1 . .1 7. 01 rows=15 width =40)
20 Rows in : Avg 1041.7 rows x 96 work ers . Max 1061 rows (
seg 20 ) with 1 . 0 59 ms t o end , s t a r t o f f s e t by 227 ms .
- -> Seq Scan on test3_1 ( c os t = 0 . 0 0 . . 1 7 . 0 1 rows=15 width
=40)
- Rows out : Avg 1041.7 rows x 96 workers . Max 1061
rows ( seg 20 ) with 0 . 12 6 ms to f i r s t row , 0.4 9 8 ms to end ,
s t a r t o f f s e t by 227 ms .
- S l i c e s t a t i s t i c s :
- ( s l i c e 0 ) Executor memory : 364K bytes .
25 ( s l i c e 1 ) Executor memory : 1805K by t e s avg x 96 workers , 1805
K bytes max ( seg0 ) .
- ( s l i c e 2 ) Executor memory : 4710K by t e s avg x 96 workers , 4710
K bytes max ( seg0 ) . Work_mem: 75K bytes max .
- Statement s t a t i s t i c s :
- Memory used : 128000K bytes
- Total runtime : 4526.06 5 ms
Время выполнения запроса составило 4.6 секунды. Много это или мало
для такого объёма данных вопрос спорный и лежащий вне этой книги.
157
6.6. Greenplum Database
Расширение кластера
В жизненном цикле распределённой аналитической БД рано или позд-
но возникает ситуация, когда объём доступного дискового пространства
уже не может вместить всех необходимых данных, а добавление устройств
хранения в имеющиеся сервера либо невозможна, либо слишком дорога
и сложна (потребуется, как минимум, расширение существующих разде-
лов). Кроме того, добавление одних лишь дисковых мощностей негативно
скажется на соотношении «число процессоров/Тб данных», о котором мы
говорили в «6.6 Хранение данных». Говоря простым языком, в кластер
рано или поздно понадобится вводить новые сервера. Greenplum позволя-
ет добавлять как новые сервера, так и новые сегменты практически без
простоя СУБД. Последовательность этого действа примерно такая:
разработать карту сегментов, согласно которой GP будет размещать
новые сегменты и зеркала на новых серверах;
сделать бэкап необходимых критичных данных (как минимум всех
метаданных);
установить ПО СУБД на новые сервера;
остановить СУБД (следующий пункт выполняется в даунтайм);
инициализировать новые сегменты утилитой gpexpand (занимает от
5 до 10 минут);
поднять СУБД (даунтайм окончен);
перераспределить (redistribute) все таблицы;
собрать статистику (analyze) по всем таблицам;
Как видно, хотя процедура расширения и продолжительна, полная
недоступность БД при правильных действиях администратора не превы-
сит 20-30 минут.
Особенности эксплуатации
Как обычно, практика вносит в красивую теорию свои коррективы. По-
делюсь некоторыми нюансами эксплуатации, выявленными нами за дол-
гое время использования GP. Сразу оговорюсь, что стандартные нюансы
PostgreSQL (необходимость VACUUM, особенности репликации) в этот пе-
речень не попали:
Автоматический failover не даёт 100% гарантии переключения на
зеркало. Увы, но это так, особенно под нагрузкой, есть риск зави-
сания процессов базы при попытке переключения на зеркало. Ча-
стично проблему решает уменьшение таймаута ответа от сегментов
до нескольких минут, однако даже в таком случае риск остаётся.
Как частное решение проблемы зависания при переключении можно
использовать ручное убийство зависшего сегмента или перезагрузку
базы;
158
6.7. Заключение
Greenplum и OLTP несовместимы. GP аналитическая БД, предна-
значенная для небольшого числа одновременных запросов, выпол-
няющих тяжёлые операции над большим объёмом данных. Большое
число (более 600 queries per second) лёгких запросов/транзакций,
выполняющих одну операцию, негативно сказывается на произво-
дительности базы из-за её распределённой архитектуры каждая
транзакция на мастере порождает N транзакций на сегментах. Хоро-
шей практикой является агрегация большого числа UPDATE/INSERT
в батчи;
Отсутствие механизма инкрементального бэкапа;
Свой синтаксис. Несмотря на то, что для клиента Greenplum по су-
ти является PostgreSQL DB, небольшие различия в синтаксисе SQL
заставляют использовать стандартный клиентский PostgreSQL-софт
с большой осторожностью;
Отсутствие возможности пометить сегменты как «архивные». Ча-
стично этот недостаток можно решить путём использования архив-
ных партиций, находящихся на медленном дешевом tablespace, а так-
же c помощью появившейся в последней на момент написания главы
версии GP 4.3.6.0 возможности располагать партиции таблицы во
внешних источниках (например, внешних таблицах gphdfs, лежащих
в кластере Hadoop);
Greenplum использует PostgreSQL версии 8.3.23, а значит о многих
современных плюшках этой замечательной БД придётся забыть;
Заключение
Greenplum мощный и гибкий инструмент для аналитической обра-
ботки больших объёмов данных. Он требует к себе немного другого подхо-
да, чем остальные enterprise-level решения для Data Warehouse («напиль-
ник» любимый инструмент администратора GP). Однако при достаточ-
но низком пороге вхождения и большой унифицированности с PostgreSQL
Greenplum является сильным игроком на поле Data Warehouse DB.
6.7 Заключение
В данной главе рассмотрены лишь базовые настройки кластеров БД.
Про кластеры PostgreSQL потребуется написать отдельную книгу, чтобы
рассмотреть все шаги с установкой, настройкой и работой кластеров. Наде-
юсь, что несмотря на это, информация будет полезна многим читателям.
159
7
PgPool-II
Имеется способ сделать
лучше найди его
Томас Алва Эдисон
7.1 Введение
Pgpool-II это прослойка, работающая между серверами PostgreSQL
и клиентами СУБД PostgreSQL. Она предоставляет следующие функции:
Объединение соединений
Pgpool-II сохраняет соединения с серверами PostgreSQL и исполь-
зует их повторно в случае если новое соединение устанавливается
с теми же параметрами . е. имя пользователя, база данных, вер-
сия протокола). Это уменьшает накладные расходы на соединения и
увеличивает производительность системы в целом;
Репликация
Pgpool-II может управлять множеством серверов PostgreSQL. Ис-
пользование функции репликации данных позволяет создание ре-
зервной копии данных в реальном времени на 2 или более физи-
ческих дисков, так что сервис может продолжать работать без оста-
новки серверов в случае выхода из строя диска;
Балансировка нагрузки
Если база данных реплицируется, то выполнение запроса SELECT
на любом из серверов вернет одинаковый результат. pgpool-II исполь-
зует преимущество функции репликации для уменьшения нагрузки
на каждый из серверов PostgreSQL распределяя запросы SELECT
на несколько серверов, тем самым увеличивая производительность
системы в целом. В лучшем случае производительность возрастает
160
7.2. Установка и настройка
пропорционально числу серверов PostgreSQL. Балансировка нагруз-
ки лучше всего работает в случае когда много пользователей выпол-
няют много запросов в одно и то же время.
Ограничение лишних соединений
Существует ограничение максимального числа одновременных со-
единений с PostgreSQL, при превышении которого новые соединения
отклоняются. Установка максимального числа соединений, в то же
время, увеличивает потребление ресурсов и снижает производитель-
ность системы. pgpool-II также имеет ограничение на максимальное
число соединений, но «лишние» соединения будут поставлены в оче-
редь вместо немедленного возврата ошибки.
Параллельные запросы
Используя функцию параллельных запросов можно разнести данные
на множество серверов, благодаря чему запрос может быть выполнен
на всех серверах одновременно для уменьшения общего времени вы-
полнения. Параллельные запросы работают лучше всего при поиске
в больших объемах данных.
Pgpool-II общается по протоколу бэкенда и фронтенда PostgreSQL и
располагается между ними. Таким образом, приложение базы данных счи-
тает что pgpool-II настоящий сервер PostgreSQL, а сервер видит pgpool-
II как одного из своих клиентов. Поскольку pgpool-II прозрачен как для
сервера, так и для клиента, существующие приложения, работающие с ба-
зой данных, могут использоваться с pgpool-II практически без изменений
в исходном коде.
7.2 Установка и настройка
Во многих Linux системах pgpool-II может находиться в репозитории
пакетов. Для Ubuntu Linux, например, достаточно будет выполнить:
Листинг 7.1 Установка pgpool-II
Line 1 $ sudo a p tit u d e i n s t a l l pgpool2
Настройка
Параметры конфигурации pgpool-II хранятся в файле pgpool.conf. Фор-
мат файла: одна пара параметр = значение в строке. При установке pgpool-
II автоматически создается файл pgpool.conf.sample:
Листинг 7.2 Файлы конфигурации
161
7.2. Установка и настройка
Line 1 $ cp / u s r / l o c a l / e t c / pgpool . c o n f . sample / us r / l o c a l / e tc / pgpool
. co n f
Pgpool-II принимает соединения только с localhost на порт 9999. Если
требуется принимать соединения с других хостов, установите для пара-
метра listen_addresses значение «*».
Листинг 7.3 Файлы конфигурации
Line 1 l i s t e n _ a d d r e s s e s = l o c a l h o s t
- port = 9999
Настройка команд PCP
У pgpool-II есть PCP интерфейс для административных целей (полу-
чить информацию об узлах базы данных, остановить pgpool-II, прочее).
Чтобы использовать команды PCP, необходима идентификация пользова-
теля. Эта идентификация отличается от идентификации пользователей в
PostgreSQL. Имя пользователя и пароль нужно указывать в файле pcp
.conf. В этом файле имя пользователя и пароль указываются как пара
значений, разделенных двоеточием (:). Одна пара в строке, пароли за-
шифрованы в формате хэша md5:
Листинг 7.4 Настройка команд PCP
Line 1 p os tg re s : e8a48 65385 1 e28c6 9 d0506 5 08fb2 7 fc5
Для того чтобы зашифровать пароль в формате md5 хэша использу-
ется команда pg_md5, которая устанавливается как один из исполняемых
файлов pgpool-II. pg_md5 принимает текст в параметре командной строки
и отображает md5 хэш как результат.
Листинг 7.5 Настройка команд PCP
Line 1 $ / u s r / bin /pg_md5 p o s t g r e s
- e8a48 6 53851 e 28c69 d05065 08fb27 fc5
Команды PCP выполняются по сети, так что в файле pgpool.conf дол-
жен быть указан номер порта в параметре pcp_port:
Листинг 7.6 Настройка команд PCP
Line 1 pcp_port = 9898
162
7.3. Настройка репликации
Подготовка узлов баз данных
Далее требуется настроить серверы бэкендов PostgreSQL для pgpool-
II. Эти серверы могут быть размещены на одном хосте с pgpool-II или на
отдельных машинах. Если вы решите разместить серверы на том же хосте,
для всех серверов должны быть установлены разные номера портов. Если
серверы размещены на отдельных машинах, они должны быть настроены
так чтобы могли принимать сетевые соединения от pgpool-II. В данном
примере три сервера PostgreSQL размещено в рамках одного хоста вместе
с pgpool-II (5432, 5433, 5434 порты соответственно):
Листинг 7.7 Подготовка узлов баз данных
Line 1 backend_hostname0 = l o c a l h o s t
- backend_port0 = 5432
- backend_weight0 = 1
- backend_hostname1 = l o c a l h o s t
5 backend_port1 = 5433
- backend_weight1 = 1
- backend_hostname2 = l o c a l h o s t
- backend_port2 = 5434
- backend_weight2 = 1
В параметрах backend_hostname, backend_port, backend_weight указыва-
ется имя хоста узла базы данных, номер порта и коэффициент для балан-
сировки нагрузки. В конце имени каждого параметра должен быть ука-
зан идентификатор узла путем добавления положительного целого числа
начиная с 0. Параметры backend_weight все равны 1, что означает что за-
просы SELECT равномерно распределены по трем серверам.
7.3 Настройка репликации
Pgpool-II репликация включает копирование одних и тех же данных
на множество узлов базы данных (синхронная репликация). Но данная ре-
пликация имеет тот недостаток, что она создаёт дополнительную нагрузку
при выполнении всех транзакций, в которых обновляются какие-либо ре-
плики (кроме того, могут возникать проблемы, связанные с доступностью
данных).
Настройка репликации
Чтобы включить функцию репликации базы данных установите зна-
чение true для параметра replication_mode в файле pgpool.conf.
Листинг 7.8 Настройка репликации
163
7.3. Настройка репликации
Line 1 r epli catio n_mo de = tr ue
Если параметр replication_mode равен true, pgpool-II будет отправлять
копию принятого запроса на все узлы базы данных.
Если параметр load_balance_mode равен true, pgpool-II будет распреде-
лять запросы SELECT между узлами базы данных.
Листинг 7.9 Настройка репликации
Line 1 load_balance_mode = t ru e
Проверка репликации
После настройки pgpool.conf и перезапуска pgpool-II, можно проверить
репликацию в действии. Для этого создадим базу данных, которую будем
реплицировать (базу данных нужно создать на всех узлах):
Листинг 7.10 Проверка репликации
Line 1 $ creat e d b -p 9999 b en ch_ r e pli c a tio n
Затем запустим pgbench с параметром - i. Параметр - i инициализирует
базу данных предопределенными таблицами и данными в них.
Листинг 7.11 Проверка репликации
Line 1 $ pgbench - i - p 9999 b e n ch_ r e pli c a tio n
Указанная ниже таблица содержит сводную информацию о таблицах и
данных, которые будут созданы при помощи pgbench -i. Если на всех узлах
базы данных перечисленные таблицы и данные были созданы, репликация
работает корректно.
Имя таблицы Число строк
branches 1
tellers 10
accounts 100000
history 0
Для проверки указанной выше информации на всех узлах используем
простой скрипт на shell:
Листинг 7.12 Проверка репликации
Line 1 f o r p ort in 5432 5433 543 4 ; do
- > echo $po rt
- > f o r table_name i n bra nches t e l l e r s ac counts h i s t o r y ;
do
- > echo $table_name
164
7.4. Параллельное выполнение запросов
5 > ps q l - c "SELECT count ( *) FROM $table_name " -p \
- > $po rt b e n ch_ r e pli c a tio n
- > done
- > done
7.4 Параллельное выполнение запросов
Pgpool-II позволяет использовать распределение для таблиц. Данные
из разных диапазонов сохраняются на двух или более узлах базы дан-
ных параллельным запросом. Более того, одни и те же данные на двух
и более узлах базы данных могут быть воспроизведены с использованием
распределения.
Чтобы включить параллельные запросы в pgpool-II вы должны уста-
новить еще одну базу данных, называемую «системной базой данных»
(«System Database») (далее будет называться SystemDB). SystemDB хра-
нит определяемые пользователем правила, определяющие какие данные
будут сохраняться на каких узлах базы данных. Также SystemDB исполь-
зуется чтобы объединить результаты возвращенные узлами базы данных
посредством dblink.
Настройка
Чтобы включить функцию выполнения параллельных запросов требу-
ется установить для параметра parallel_mode значение true в файле pgpool
.conf:
Листинг 7.13 Настройка параллельного запроса
Line 1 parallel_mode = tr ue
Установка параметра parallel_mode равным true не запустит параллель-
ные запросы автоматически. Для этого pgpool-II нужна SystemDB и пра-
вила определяющие как распределять данные по узлам базы данных. Так-
же SystemDB использует dblink для создания соединений с pgpool-II. Та-
ким образом, нужно установить значение параметра listen_addresses таким
образом чтобы pgpool-II принимал эти соединения:
Листинг 7.14 Настройка параллельного запроса
Line 1 l i s t e n _ a d d r e s s e s = *
Нужно обратить внимание, что репликация не реализована для таблиц,
которые распределяются посредством параллельных запросов. Поэтому:
165
7.4. Параллельное выполнение запросов
Листинг 7.15 Настройка параллельного запроса
Line 1 r epli catio n_mo de = tr ue
- load_balance_mode = f a l s e
или
Листинг 7.16 Настройка параллельного запроса
Line 1 r epli catio n_mo de = f a l s e
- load_balance_mode = true
Настройка SystemDB
В основном, нет отличий между простой и системной базами данных.
Однако, в системной базе данных определяется функция dblink и при-
сутствует таблица, в которой хранятся правила распределения данных.
Таблицу dist_def необходимо определять. Более того, один из узлов базы
данных может хранить системную базу данных, а pgpool-II может исполь-
зоваться для распределения нагрузки каскадным подключением.
Создадим SystemDB на узле с портом 5432. Далее приведен список
параметров конфигурации для SystemDB:
Листинг 7.17 Настройка SystemDB
Line 1 system_db_hostname = l o c a l h o s t
- system_db_port = 5432
- system_db_dbname = pgpool
- system_db_schema = pgpool_ca talog
5 system_db_user = pgpool
- system_db_password =
На самом деле, указанные выше параметры являются параметрами по
умолчанию в файле pgpool.conf. Теперь требуется создать пользователя с
именем «pgpool» и базу данных с именем «pgpool» и владельцем «pgpool»:
Листинг 7.18 Настройка SystemDB
Line 1 $ c r e a t e u s e r - p 5432 pgpool
- $ c re a t ed b -p 5432 -O pgpool pgpool
Установка dblink
Далее требуется установить dblink в базу данных «pgpool». Dblink
один из инструментов включенных в каталог contrib исходного кода
PostgreSQL. После того как dblink был установлен в вашей системе мы
добавим функции dblink в базу данных «pgpool».
166
7.4. Параллельное выполнение запросов
Листинг 7.19 Установка dblink
Line 1 $ p s ql - c "CREATE EXTENSION d bl i nk ; " -p 5432 pgpool
Создание таблицы dist_def
Следующим шагом мы создадим таблицу с именем dist_def, в которой
будут храниться правила распределения данных. Поскольку pgpool-II уже
был установлен, файл с именем system_db.sql должен быть установлен в /
usr/local/share/system_db.sql (имейте в виду, что на вашей системе каталог
установки может быть другой). Файл system_db.sql содержит директивы
для создания специальных таблиц, включая и таблицу dist_def. Выполним
следующую команду для создания таблицы dist_def:
Листинг 7.20 Создание таблицы dist_def
Line 1 $ p s ql - f / u s r / l o c a l / share /system_db . s q l -p 5432 -U pgpool
pgpool
Все таблицы в файле system_db.sql, включая dist_def, создаются в схеме
pgpool_catalog. Если вы установили параметр system_db_schema на исполь-
зование другой схемы, вам нужно, соответственно, отредактировать файл
system_db.sql. Описание таблицы dist_def выглядит так как показано ниже:
Листинг 7.21 Создание таблицы dist_def
Line 1 CREATE TABLE pg poo l_catal og . d i s t _d ef (
- dbname tex t , - - имя базы данных
- schema_name text , - - имя схемы
- table_name t ext , - - имя таблицы
5 col_name t ex t NOT NULL CHECK ( col_name = ANY ( c o l _ l i s t ) )
,
- - - столбец ключ для распределения данных
- c o l _ l i s t t ex t [ ] NOT NULL, - - список имен столбцов
- t y p e _ li st t e xt [ ] NOT NULL, - - список типов столбцов
- dist_ def_fun c t e xt NOT NULL,
10 - - имя функции распределения данных
- PRIMARY KEY ( dbname , schema_name , table_name )
- ) ;
Записи, хранимые в таблице dist_def, могут быть двух типов:
Правило распределения данных (col_name, dist_def_func);
Мета-информация о таблицах (dbname, schema_name, table_name,
col_list , type_list);
Правило распределения данных определяет как будут распределены
данные на конкретный узел базы данных. Данные будут распределены в
167
7.4. Параллельное выполнение запросов
зависимости от значения столбца col_name. dist_def_func это функция,
которая принимает значение col_name в качестве аргумента и возвращает
целое число, которое соответствует идентификатору узла базы данных,
на котором должны быть сохранены данные. Мета-информация исполь-
зуется для того чтобы переписывать запросы. Параллельный запрос дол-
жен переписывать исходные запросы так чтобы результаты, возвращаемые
узлами-бэкендами, могли быть объединены в единый результат.
Создание таблицы replicate_def
В случае если указана таблица, для которой производится репликация
в выражение SQL, использующее зарегистрированную в dist_def таблицу
путем объединения таблиц, информация о таблице, для которой необхо-
димо производить репликацию, предварительно регистрируется в таблице
с именем replicate_def. Таблица replicate_def будет создана при обработке
файла system_db.sql. Таблица replicate_def описана так как показано ниже:
Листинг 7.22 Создание таблицы replicate_def
Line 1 CREATE TABLE pg poo l_catal og . r e p l i c a t e _ d e f (
- dbname tex t , - - имя базы данных
- schema_name text , - - имя схемы
- table_name t ext , - - имя таблицы
5 c o l _ l i s t t ex t [ ] NOT NULL, - - список имен столбцов
- t y p e _ li st t e xt [ ] NOT NULL, - - список типов столбцов
- PRIMARY KEY ( dbname , schema_name , table_name )
- ) ;
Установка правил распределения данных
В данном примере будут определены правила распределения данных,
созданных программой pgbench, на три узла базы данных. Тестовые дан-
ные будут созданы командой pgbench -i -s 3 . е. масштабный коэффици-
ент равен 3). Для этого раздела мы создадим новую базу данных с именем
bench_parallel. В каталоге sample исходного кода pgpool-II вы можете най-
ти файл dist_def_pgbench.sql. Будем использоваться этот файл с примером
для создания правил распределения для pgbench. Выполним следующую
команду в каталоге с распакованным исходным кодом pgpool-II:
Листинг 7.23 Установка правил распределения данных
Line 1 $ p s ql - f sample / dist_def_pgbench . s q l -p 5432 pgpool
В файле dist_def_pgbench.sql мы добавляем одну строку в таблицу
dist_def. Это функция распределения данных для таблицы accounts. В ка-
честве столбца-ключа указан столбец aid.
168
7.4. Параллельное выполнение запросов
Листинг 7.24 Установка правил распределения данных
Line 1 INSERT INTO pgpool_cat alog . d is t_ de f VALUES (
- b e n ch _p ar al le l ,
- p u b li c ,
- acc o unts ,
5 aid ,
- ARRAY[ aid , bid , a b a lance , f i l l e r ] ,
- ARRAY[ i n t e g e r , i n t e g e r , i n t e g e r ,
- c h a ra c t e r (8 4 ) ] ,
- p gpool_cata log . dis t_d ef_ accounts
10 ) ;
Теперь мы должны создать функцию распределения данных для таб-
лицы accounts. Возможно использовать одну и ту же функцию для разных
таблиц. Таблица accounts в момент инициализации данных хранит значе-
ние масштабного коэффициента равное 3, значения столбца aid от 1 до
300000. Функция создана таким образом что данные равномерно распре-
деляются по трем узлам базы данных:
Листинг 7.25 Установка правил распределения данных
Line 1 CREATE OR REPLACE FUNCTION
- pgpool _catalog . dist_def _branches ( anyelement )
- RETURNS i n t e g e r AS $$
- SELECT CASE WHEN $1 > 0 AND $1 <= 1 THEN 0
5 WHEN $1 > 1 AND $1 <= 2 THEN 1
- ELSE 2
- END;
- $$ LANGUAGE s q l ;
Установка правил репликации
Правило репликации это то что определяет какие таблицы долж-
ны быть использованы для выполнения репликации. Здесь это сделано
при помощи pgbench с зарегистрированными таблицами branches и tellers .
Как результат, стало возможно создание таблицы accounts и выполнение
запросов, использующих таблицы branches и tellers :
Листинг 7.26 Установка правил репликации
Line 1 INSERT INTO pgpool_cat alog . r e pl i c a t e _ d e f VALUES (
- b e n ch _p ar al le l ,
- p u b li c ,
- branches ,
169
7.4. Параллельное выполнение запросов
5 ARRAY[ bid , bbal anc e , f i l l e r ] ,
- ARRAY[ i n t e g e r , i n t e g e r , c h a ra c t e r (8 8 ) ]
- ) ;
-
- INSERT INTO pgpool_cata log . r e p l i c a t e _ d e f VALUES (
10 b e n ch _p ar al le l ,
- p u b li c ,
- t e l l e r s ,
- ARRAY[ t i d , bid , tb a lan c e , f i l l e r ] ,
- ARRAY[ i n t e g e r , i n t e g e r , i n t e g e r , c h a ra c t e r (8 4 ) ]
15 ) ;
Подготовленный файл replicate_def_pgbench.sql находится в каталоге
sample:
Листинг 7.27 Установка правил репликации
Line 1 $ p s ql - f sample / repli cate _def_pg benc h . s q l - p 5432 pgpool
Проверка параллельного запроса
После настройки pgpool.conf и перезапуска pgpool-II возможно провести
проверку работоспособности параллельных запросов. Сначала требуется
создать базу данных, которая будет распределена. Эту базу данных нужно
создать на всех узлах:
Листинг 7.28 Проверка параллельного запроса
Line 1 $ creat e d b -p 9999 b e nc h_ pa ra llel
Затем запустим pgbench с параметрами - i -s 3:
Листинг 7.29 Проверка параллельного запроса
Line 1 $ pgbench - i - s 3 -p 9999 be n c h _p ar al lel
Один из способов проверить корректно ли были распределены дан-
ные выполнить запрос SELECT посредством pgpool-II и напрямую на
бэкендах и сравнить результаты. Если все настроено правильно база дан-
ных bench_parallel должна быть распределена как показано ниже:
Имя таблицы Число строк
branches 3
tellers 30
accounts 300000
history 0
Для проверки указанной выше информации на всех узлах и посред-
ством pgpool-II используем простой скрипт на shell. Приведенный ниже
170
7.5. Master-slave режим
скрипт покажет минимальное и максимальное значение в таблице accounts
используя для соединения порты 5432, 5433, 5434 и 9999.
Листинг 7.30 Проверка параллельного запроса
Line 1 f o r p ort in 5432 5433 5434 i 9 9 9 9 ; do
- > echo $po rt
- > p s ql - c "SELECT min ( a i d ) , max( a i d ) FROM a ccount s " \
- > -p $po rt b e n c h_ pa ra ll el
5 > done
7.5 Master-slave режим
Этот режим предназначен для использования pgpool-II с другой репли-
кацией (например streaming, londiste). Информация про БД указывается
как для репликации. master_slave_mode и load_balance_mode устанавлива-
ется в true. pgpool-II будет посылать запросы INSERT/UPDATE/DELETE
на master базу данных (1 в списке), а SELECT использовать балансиров-
ку нагрузки, если это возможно. При этом, DDL и DML для временной
таблицы может быть выполнен только на мастере. Если нужен SELECT
только на мастере, то для этого нужно использовать комментарий /*NO
LOAD BALANCE*/ перед SELECT.
В Master/Slave режиме replication_mode должен быть установлен false ,
а master_slave_mode true.
Streaming Replication (Потоковая репликация)
В master-slave режиме с потоковой репликацией, если мастер или
слейв упал, возможно использовать отказоустойчивый функционал внут-
ри pgpool-II. Автоматически отключив упавший инстанс PostgreSQL,
pgpool-II переключится на следующий слейв как на новый мастер (при па-
дении мастера), или останется работать на мастере (при падении слейва).
В потоковой репликации, когда слейв становится мастером, требуется со-
здать триггер файл (который указан в recovery.conf, параметр trigger_file ),
чтобы PostgreSQL перешел из режима восстановления в нормальный. Для
этого можно создать небольшой скрипт:
Листинг 7.31 Скрипт выполняется при падении нода PostgreSQL
Line 1 #! / bin /sh
- # F a i l o v e r command f o r strem i n g r e p l i c a t i o n .
- # This s c r i p t assumes that DB node 0 i s primary , and 1 i s
standby .
- #
171
7.6. Онлайн восстановление
5 # I f standby goes down , does noth ing . I f primary g oes down ,
c re a t e a
- # t r i g g e r f i l e so t h at standby take ove r primary node .
- #
- # Arguments : $1 : f a i l e d node id . $2 : new master hostname . $3
: path to
- # t r i g g e r f i l e .
10
- f a i l e d _ n o d e=$1
- new_master=$2
- t r i g g e r _ f i l e=$3
-
15 # Do nothing i f standby g o es down .
- i f [ $ f a i l e d _ n ode = 1 ] ; then
- e x i t 0 ;
- f i
-
20 # Create t r i g g e r f i l e .
- / u sr / bin / ssh -T $new_master / bin / touch $ t r i g g e r _ f i l e
-
- e x i t 0 ;
Работает скрипт просто: если падает слейв — скрипт ничего не вы-
полняет, при падении мастера создает триггер файл на новом мастере.
Сохраним этот файл под именем failover \_stream.sh и добавим в pgpool.
conf:
Листинг 7.32 Что выполнять при падении нода
Line 1 failover_command = / path_to_script / fa i lo ve r _st r eam . sh %d %H
/tmp/ t r i g g e r _ f i l e
где /tmp/trigger_file триггер файл, указанный в конфиге recovery.conf
. Теперь, если мастер СУБД упадет, слейв будет переключен из режима
восстановления в обычный и сможет принимать запросы на запись.
7.6 Онлайн восстановление
Pgpool-II в режиме репликации может синхронизировать базы данных
и добавлять их как новые ноды. Называется это «онлайн восстановле-
ние». Этот метод также может быть использован когда нужно вернуть в
репликацию упавший нод базы данных. Вся процедура выполняется в два
задания. Несколько секунд или минут клиент может ждать подключения
к pgpool, в то время как восстанавливается узел базы данных. Онлайн
восстановление состоит из следующих шагов:
CHECKPOINT;
172
7.6. Онлайн восстановление
Первый этап восстановления;
Ждем, пока все клиенты не отключатся;
CHECKPOINT;
Второй этап восстановления;
Запуск postmaster (выполнить pgpool_remote_start);
Восстанавливаем инстанс СУБД;
Для работы онлайн восстановления потребуется указать следующие
параметры:
backend_data_directory - каталог данных определенного PostgreSQL
кластера;
recovery_user - имя пользователя PostgreSQL;
recovery_password - пароль пользователя PostgreSQL;
recovery_1st_stage_command - параметр указывает команду для пер-
вого этапа онлайн восстановления. Файл с командами должен быть
помещен в каталог данных СУБД кластера из соображений безопас-
ности. Например, если recovery_1st_stage_command = ’some_script’,
то pgpool-II выполнит $PGDATA/some_script. Обратите внимание,
что pgpool-II принимает подключения и запросы в то время как вы-
полняется recovery_1st_stage;
recovery_2nd_stage_command - параметр указывает команду для вто-
рого этапа онлайн восстановления. Файл с командами должен быть
помещен в каталог данных СУБД кластера из-за проблем безопасно-
сти. Например, если recovery_2st_stage_command = ’some_script’, то
pgpool-II выполнит $PGDATA/some_script. Обратите внимание, что
pgpool-II НЕ принимает подключения и запросы в то время как вы-
полняется recovery_2st_stage. Таким образом, pgpool-II будет ждать
пока все клиенты не закроют подключения;
Streaming Replication (Потоковая репликация)
В master-slave режиме с потоковой репликацией, онлайн восстановле-
ние отличное средство вернуть назад упавший инстанс PostgreSQL.
Вернуть возможно только слейв ноды, таким методом не восстановить
упавший мастер. Для восстановления мастера потребуется остановить все
PostgreSQL инстансы и pgpool-II (для восстановления из резервной копии
мастера).
Для настройки онлайн восстановления потребуется:
Установить recovery_user. Обычно это «postgres»;
Установить recovery_password для recovery_user для подключения к
СУБД;
173
7.6. Онлайн восстановление
Настроить recovery_1st_stage_command. Для этого можно создать
скрипт basebackup.sh и положим его в папку с данными мастера
($PGDATA), установив ему права на выполнение:
Листинг 7.33 basebackup.sh
Line 1 #! / bin /sh
- # Recovery s c r i p t f o r s tream ing r e p l i c a t i o n .
- # This s c r i p t assumes that DB node 0 i s primary , and 1
i s standby .
- #
5 da t ad i r=$1
- de s th o st=$2
- d e s t d i r=$3
-
- ps q l - c "SELECT pg_start_backup ( Streaming R epl i c ati o n ,
tr u e ) " p o s t g r e s
10
- r sync -C - a - - d e l e t e - e s s h - - e x cl u d e p o s t g r e s q l . co n f - -
exclud e pos tmas ter . pid \
- - - e x c l ud e pos tmast er . opt s - - e x c lu d e pg_log - - exclude
pg_xlog \
- - - e x c l ud e r eco v er y . c o n f $ dat a di r / $ d e st h o st : $ d e s t d i r /
-
15 ssh -T l o c a l h o s t mv $ d es t d i r / r e co ver y . done $ d e s t d i r /
re c ove r y . c o n f
-
- ps q l - c "SELECT pg_stop_backup ( ) " p os t g r e s
При восстановления слейва, скрипт запускает бэкап мастера и через
rsync утилиту передает данные с мастера на слейв. Для этого необхо-
димо настроить ssh так, чтобы recovery_user мог заходить с мастера
на слейв без пароля. Далее добавим скрипт на выполнение для пер-
вого этапа онлайн восстановления:
Листинг 7.34 recovery_1st_stage_command
Line 1 recovery_1st_stage_command = basebackup . sh
Оставляем recovery_2nd_stage_command пустым. После успешного
выполнения первого этапа онлайн восстановления, разницу в дан-
ных, что успели записаться во время работы скрипта basebackup.sh,
слейв инстанс заберет через WAL файлы с мастера;
Устанавливаем C и SQL функции для работы онлайн восстановления
на каждый инстанс СУБД:
174
7.7. Заключение
Листинг 7.35 Устанавливаем C и SQL функции
Line 1 $ cd pgpool - I I - x . x . x/ s q l / pgpool - r eco v er y
- $ make
- $ make i n s t a l l
- $ p s q l - f pgpool - r e co ver y . s q l template1
После этого возможно использовать pcp_recovery_node для онлайн вос-
становления упавших слейвов.
7.7 Заключение
PgPool-II прекрасное средство, функционал которого может помочь
администраторам баз данных при масштабировании PostgreSQL.
175
8
Мультиплексоры соединений
Если сразу успеха не
добились, пытайтесь снова и
снова. Затем оставьте эти
попытки. Какой смысл глупо
упорствовать?
Уильям Клод Филдс
8.1 Введение
Мультиплексоры соединений (программы для создания пула соедине-
ний) позволяют уменьшить накладные расходы на базу данных, в слу-
чае, когда огромное количество физических соединений ведет к падению
производительности PostgreSQL. Это особенно важно на Windows, когда
система ограничивает большое количество соединений. Это также важно
для веб-приложений, где количество соединений может быть очень боль-
шим.
Для PostgreSQL существует PgBouncer и Pgpool-II, которые работают
как мультиплексоры соединений.
8.2 PgBouncer
Это мультиплексор соединений для PostgreSQL от компании Skype.
Существуют три режима управления:
Session Pooling наиболее «вежливый» режим. При начале сессии
клиенту выделяется соединение с сервером; оно приписано ему в те-
чение всей сессии и возвращается в пул только после отсоединения
клиента;
176
8.2. PgBouncer
Transaction Pooling клиент владеет соединением с бэкендом только
в течение транзакции. Когда PgBouncer замечает, что транзакция
завершилась, он возвращает соединение назад в пул;
Statement Pooling наиболее агрессивный режим. Соединение с бэ-
кендом возвращается назад в пул сразу после завершения запроса.
Транзакции с несколькими запросами в этом режиме не разреше-
ны, так как они гарантировано будут отменены. Также не работают
подготовленные выражения (prepared statements) в этом режиме;
К достоинствам PgBouncer относится:
малое потребление памяти (менее 2 КБ на соединение);
отсутствие привязки к одному серверу баз данных;
реконфигурация настроек без рестарта.
Базовая утилита запускается просто:
Листинг 8.1 PgBouncer
Line 1 $ pgbouncer [ - d ] [ -R] [ - v ] [ - u u s e r ] <pgbouncer . i n i >
Пример конфига:
Листинг 8.2 PgBouncer
Line 1 [ d a t a b a s e s ]
- templ ate 1 = host = 1 27. 0 .0 .1 por t =5432 dbname=te mpl ate1
- [ pgbouncer ]
- l i s t e n _ p o r t = 6543
5 l iste n _ ad dr = 1 2 7 . 0 . 0 . 1
- auth_type = md5
- a u th _ fi l e = u s e r l i s t . t x t
- l o g f i l e = pgbouncer . l o g
- p i d f i l e = pgbouncer . pid
10 admin_users = someuser
Нужно создать файл пользователей userlist .txt примерно такого содер-
жания: "someuser" "same_password_as_in_server". Административный до-
ступ из консоли к базе данных pgbouncer можно получить через команду
ниже:
Листинг 8.3 PgBouncer
Line 1 $ p s ql -h 1 2 7 . 0 . 0 . 1 -p 6543 pgbouncer
Здесь можно получить различную статистическую информацию с по-
мощью команды SHOW.
177
8.3. PgPool-II vs PgBouncer
8.3 PgPool-II vs PgBouncer
Если сравнивать PgPool-II и PgBouncer, то PgBouncer намного лучше
работает с пулами соединений, чем PgPool-II. Если вам не нужны осталь-
ные возможности, которыми владеет PgPool-II (ведь пулы коннектов это
мелочи к его функционалу), то конечно лучше использовать PgBouncer.
PgBouncer потребляет меньше памяти, чем PgPool-II;
у PgBouncer возможно настроить очередь соединений;
в PgBouncer можно настраивать псевдо базы данных (на сервере они
могут называться по-другому);
Также возможен вариант использования PgBouncer и PgPool-II сов-
местно.
178
9
Кэширование в PostgreSQL
Чтобы что-то узнать, нужно
уже что-то знать
Станислав Лем
9.1 Введение
Кэш или кеш промежуточный буфер с быстрым доступом, содержа-
щий информацию, которая может быть запрошена с наибольшей вероят-
ностью. Кэширование SELECT запросов позволяет повысить производи-
тельность приложений и снизить нагрузку на PostgreSQL. Преимущества
кэширования особенно заметны в случае с относительно маленькими таб-
лицами, имеющими статические данные, например, справочными табли-
цами.
Многие СУБД могут кэшировать SQL запросы, и данная возможность
идет у них, в основном, «из коробки». PostgreSQL не обладает подобным
функционалом. Почему? Во-первых, мы теряем транзакционную чистоту
происходящего в базе. Что это значит? Управление конкурентным досту-
пом с помощью многоверсионности (MVCC MultiVersion Concurrency
Control) один из механизмов обеспечения одновременного конкурентно-
го доступа к БД, заключающийся в предоставлении каждому пользова-
телю «снимка» БД, обладающего тем свойством, что вносимые данным
пользователем изменения в БД невидимы другим пользователям до мо-
мента фиксации транзакции. Этот способ управления позволяет добить-
ся того, что пишущие транзакции не блокируют читающих, и читающие
транзакции не блокируют пишущих. При использовании кэширования, ко-
торому нет дела к транзакциям СУБД, «снимки» БД могут быть с невер-
ными данными. Во-вторых, кеширование результатов запросов, в основ-
ном, должно происходить на стороне приложения, а не СУБД. В таком
случае управление кэшированием может работать более гибко (включать-
179
9.2. Pgmemcache
и отключаться где потребуется для приложения), а СУБД будет зани-
маться своей непосредственной целью хранением и обеспечение целост-
ности данных.
Для организации кэширования существует два инструмента для
PostgreSQL:
Pgmemcache memcached);
Pgpool-II (query cache);
9.2 Pgmemcache
Memcached программное обеспечение, реализующее сервис кэширо-
вания данных в оперативной памяти на основе хеш-таблицы. С помощью
клиентской библиотеки позволяет кэшировать данные в оперативной па-
мяти множества доступных серверов. Распределение реализуется путём
сегментирования данных по значению хэша ключа по аналогии с сокетами
хэш-таблицы. Клиентская библиотека, используя ключ данных, вычисля-
ет хэш и использует его для выбора соответствующего сервера. Ситуация
сбоя сервера трактуется как промах кэша, что позволяет повышать от-
казоустойчивость комплекса за счет наращивания количества memcached
серверов и возможности производить их горячую замену.
Pgmemcache это PostgreSQL API библиотека на основе libmemcached
для взаимодействия с memcached. С помощью данной библиотеки
PostgreSQL может записывать, считывать, искать и удалять данные из
memcached.
Установка
Поскольку Pgmemcache идет как модуль, то потребуется PostgreSQL с
PGXS (если уже не установлен, поскольку в сборках для Linux присут-
ствует PGXS). Также потребуется memcached и libmemcached библиотека
версии не ниже 0.38. После скачивания и распаковки исходников доста-
точно выполнить в консоли:
Листинг 9.1 Установка из исходников
Line 1 $ make
- $ sudo make i n s t a l l
Настройка
После успешной установки Pgmemcache потребуется добавить во все
базы данных (на которых вы хотите использовать Pgmemcache) функции
для работы с этой библиотекой:
180
9.2. Pgmemcache
Листинг 9.2 Настройка
Line 1 % p sq l [ mydbname ] [ p guser ]
- [ mydbname]=# CREATE EXTENSION pgmemcache ;
Теперь можно добавлять сервера memcached через
memcache_server_add и работать с кэшем. Но есть одно но. Все сер-
вера memcached придется задавать при каждом новом подключении к
PostgreSQL. Это ограничение можно обойти, если настроить параметры
в postgresql .conf файле:
Добавить pgmemcache в shared_preload_libraries (автозагрузка библио-
теки pgmemcache во время старта PostgreSQL);
Добавить pgmemcache в custom_variable_classes станавливаем пере-
менную для pgmemcache);
Создаем pgmemcache.default_servers, указав в формате «host:port»
(port - опционально) через запятую. Например:
Листинг 9.3 Настройка default_servers
Line 1 pgmemcache . d ef au lt _s er v e r s = 1 2 7 . 0 . 0 . 1 ,
1 92 . 1 6 8 . 0 . 2 0 : 1 1 2 1 1 # подключили два сервера memcached
Также можем настроить работу самой библиотеки pgmemcache че-
рез pgmemcache.default_behavior. Настройки соответствуют настрой-
кам libmemcached. Например:
Листинг 9.4 Настройка pgmemcache
Line 1 pgmemcache . d e f a ul t _ be h av i o r= ’BINARY_PROTOCOL: 1
Теперь не требуется при подключении к PostgreSQL указывать сервера
memcached.
Проверка
После успешной установки и настройки pgmemcache становится досту-
пен список команд для работы с memcached серверами.
Посмотрим работу в СУБД данных функций. Для начала получим
информацию о memcached серверах:
Листинг 9.5 Проверка memcache_stats
Line 1 pgmemcache=# SELECT memcache_stats ( ) ;
- memcache_stats
181
9.2. Pgmemcache
Таблица 9.1: Список команд pgmemcache
Команда Описание
memcache_server_add(’hostname:port’::TEXT)
memcache_server_add(’hostname’::TEXT)
Добавляет memcached сервер в список доступ-
ных серверов. Если порт не указан, по умолча-
нию используется 11211.
memcache_add(key::TEXT, value::TEXT,
expire::TIMESTAMPTZ)
memcache_add(key::TEXT, value::TEXT,
expire::INTERVAL)
memcache_add(key::TEXT, value::TEXT)
Добавляет ключ в кэш, если ключ не существу-
ет.
newval = memcache_decr(key::TEXT,
decrement::INT4)
newval = memcache_decr(key::TEXT)
Если ключ существует и является целым чис-
лом, происходит уменьшение его значения на
указанное число (по умолчанию на единицу).
Возвращает целое число после уменьшения.
memcache_delete(key::TEXT,
hold_timer::INTERVAL)
memcache_delete(key::TEXT)
Удаляет указанный ключ. Если указать тай-
мер, то ключ с таким же названием может быть
добавлен только после окончания таймера.
memcache_flush_all() Очищает все данные на всех memcached серве-
рах.
value = memcache_get(key::TEXT) Выбирает ключ из кэша. Возвращает NULL,
если ключ не существует, иначе текстовую
строку.
memcache_get_multi(keys::TEXT[])
memcache_get_multi(keys::BYTEA[])
Получает массив ключей из кэша. Воз-
вращает список найденных записей в виде
«ключ=значение».
newval = memcache_incr(key::TEXT,
increment::INT4)
newval = memcache_incr(key::TEXT)
Если ключ существует и является целым чис-
лом, происходит увеличение его значения на
указанное число (по умолчанию на единицу).
Возвращает целое число после увеличения.
memcache_replace(key::TEXT, value::TEXT,
expire::TIMESTAMPTZ)
memcache_replace(key::TEXT, value::TEXT,
expire::INTERVAL)
memcache_replace(key::TEXT, value::TEXT)
Заменяет значение для существующего ключа.
memcache_set(key::TEXT, value::TEXT,
expire::TIMESTAMPTZ)
memcache_set(key::TEXT, value::TEXT,
expire::INTERVAL)
memcache_set(key::TEXT, value::TEXT)
Создает ключ со значением. Если такой ключ
существует заменяет в нем значение на ука-
занное.
stats = memcache_stats() Возвращает статистику по всем серверам
memcached.
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
5 Se r v e r : 1 2 7 . 0 . 0 . 1 ( 1 1211)
- pid : 1116
- uptime : 70
- time : 1289598098
- v e rs io n : 1 . 4 . 5
10 p o i nt e r _ si ze : 32
182
9.2. Pgmemcache
- rusage_user : 0 .0
- rusage_system : 0.2400 1
- curr_items : 0
- total _ i t e m s : 0
15 byt e s : 0
- curr_connecti o n s : 5
- t o ta l _co n nec t io n s : 7
- c o nn e c ti o n _s t ruc t ure s : 6
- cmd_get : 0
20 cmd_set : 0
- get_ h i t s : 0
- get_miss es : 0
- e v i c t i o n s : 0
- bytes_read : 20
25 by tes_ writ ten : 782
- limit_maxbytes : 67108864
- th r e a ds : 4
-
- (1 row )
Теперь сохраним данные в memcached и попробуем их забрать:
Листинг 9.6 Проверка
Line 1 pgmemcache=# SELECT memcache_add ( some_key , test_ v a l u e ) ;
- memcache_add
- - - - - - - - - - - - - - -
- t
5 (1 row )
-
- pgmemcache=# SELECT memcache_get ( some_key ) ;
- memcache_get
- - - - - - - - - - - - - - -
10 test_valu e
- (1 row )
Можно также проверить работу счетчиков в memcached (данный функ-
ционал может пригодиться для создания последовательностей):
Листинг 9.7 Проверка
Line 1 pgmemcache=# SELECT memcache_add ( some_seq , 10 ) ;
- memcache_add
- - - - - - - - - - - - - - -
- t
5 (1 row )
-
- pgmemcache=# SELECT memcache_incr ( some_seq ) ;
183
9.2. Pgmemcache
- memcache_incr
- - - - - - - - - - - - - - - -
10 11
- (1 row )
-
- pgmemcache=# SELECT memcache_incr ( some_seq ) ;
- memcache_incr
15 - - - - - - - - - - - - - - -
- 12
- (1 row )
-
- pgmemcache=# SELECT memcache_incr ( some_seq , 10) ;
20 memcache_incr
- - - - - - - - - - - - - - - -
- 22
- (1 row )
-
25 pgmemcache=# SELECT memcache_decr ( some_seq ) ;
- memcache_decr
- - - - - - - - - - - - - - - -
- 21
- (1 row )
30
- pgmemcache=# SELECT memcache_decr ( some_seq ) ;
- memcache_decr
- - - - - - - - - - - - - - - -
- 20
35 (1 row )
-
- pgmemcache=# SELECT memcache_decr ( some_seq , 6) ;
- memcache_decr
- - - - - - - - - - - - - - - -
40 14
- (1 row )
Для работы с pgmemcache лучше создать функции и, если требуется,
активировать эти функции через триггеры.
Например, приложение кэширует зашифрованные пароли пользовате-
лей в memcached (для более быстрого доступа), и нам требуется обновлять
информацию в кэше, если она изменяется в СУБД. Создаем функцию:
Листинг 9.8 Функция для обновления данных в кэше
Line 1 CREATE OR REPLACE FUNCTION auth_passwd_upd () RETURNS TRIGGER
AS $$
- BEGIN
- IF OLD. passwd != NEW. passwd THEN
184
9.2. Pgmemcache
- PERFORM memcache_set ( user_id_ | | NEW.
user_id | | _password , NEW. passwd ) ;
5 END IF ;
- RETURN NEW;
- END;
- $$ LANGUAGE p l p g s q l ;
Активируем триггер для обновления таблицы пользователей:
Листинг 9.9 Триггер
Line 1 CREATE TRIGGER auth_passwd_upd_trg AFTER UPDATE ON passwd
FOR EACH ROW EXECUTE PROCEDURE auth_passwd_upd ( ) ;
Но данный пример транзакционно небезопасен при отмене транзак-
ции кэш не вернется на старое значение. Поэтому лучше очищать старые
данные:
Листинг 9.10 Очистка ключа в кэше
Line 1 CREATE OR REPLACE FUNCTION auth_passwd_upd () RETURNS TRIGGER
AS $$
- BEGIN
- IF OLD. passwd != NEW. passwd THEN
- PERFORM memcache_delete ( user_id_ | | NEW.
user_id | | _password ) ;
5 END IF ;
- RETURN NEW;
- END; $$ LANGUAGE p l p g s q l ;
Также нужен триггер на чистку кэша при удалении записи из СУБД:
Листинг 9.11 Триггер
Line 1 CREATE TRIGGER auth_passwd_del_trg AFTER DELETE ON passwd
FOR EACH ROW EXECUTE PROCEDURE auth_passwd_upd ( ) ;
Данный пример сделан для наглядности, а создавать кэш в memcached
на кешированый пароль нового пользователя (или обновленного) лучше
через приложение.
Заключение
PostgreSQL с помощью Pgmemcache библиотеки позволяет работать с
memcached серверами, что может потребоваться в определенных случаях
для кэширования данных напрямую с СУБД. Удобство данной библиоте-
ки заключается в полном доступе к функциям memcached, но вот готовой
реализации кэширование SQL запросов тут нет, и её придется дорабаты-
вать вручную через функции и триггеры PostgreSQL.
185
9.3. Заключение
9.3 Заключение
TODO
186
10
Расширения
Гибкость ума может заменить
красоту
Стендаль
10.1 Введение
Один из главных плюсов PostgreSQL это возможность расширения его
функционала с помощью расширений. В данной статье я затрону только
самые интересные и популярные из существующих расширений.
10.2 PostGIS
PostGIS добавляет поддержку для географических объектов в
PostgreSQL. По сути PostGIS позволяет использовать PostgreSQL в ка-
честве бэкенда пространственной базы данных для геоинформационных
систем (ГИС), так же, как ESRI SDE или пространственного расширения
Oracle. PostGIS соответствует OpenGIS «Простые особенности. Специфи-
кация для SQL» и был сертифицирован.
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.1 Инициализация postgis
Line 1 # CREATE EXTENSION p o s t g i s ;
При создании пространственной базы данных автоматически создают-
ся таблица метаданных spatial_ref_sys и представления geometry_columns,
187
10.2. PostGIS
geography_columns, raster_columns и raster_overviews. Они создаются в соот-
ветствии со спецификацией «Open Geospatial Consortium Simple Features
for SQL specification», выпущенной OGC и описывающей стандартные ти-
пы объектов ГИС, функции для манипуляции ими и набор таблиц ме-
таданных. Таблица spatial_ref_sys содержит числовые идентификаторы и
текстовые описания систем координат, используемых в пространственной
базе данных. Одним из полей этой таблицы является поле SRID уни-
кальный идентификатор, однозначно определяющий систему координат.
SRID представляет из себя числовой код, которому соответствует неко-
торая система координат. Например, распространенный код EPSG 4326
соответствует географической системе координат WGS84. Более подроб-
ную информацию по таблицами метаданных можно найти в руководстве
по PostGIS.
Теперь, имея пространственную базу данных, можно создать несколько
пространственных таблиц. Для начала создадим обычную таблицу базы
данных, чтобы хранить данные о городе. Эта таблица будет содержать
три поля: числовой идентификатор, название города и колонка геометрии,
содержащую данные о местоположении городов:
Листинг 10.2 Создание таблицы cities
Line 1 # CREATE TABLE c i t i e s ( i d i nt 4 primary key , name varc har
(5 0 ) , the_geom geometry (POINT, 4 3 2 6) ) ;
the_geom поле указывает PostGIS, какой тип геометрии имеет каж-
дый из объектов (точки, линии, полигоны и т. п.), какая размерность .к.
возможны и 3-4 измерения POINTZ, POINTM, POINTZM) и какая си-
стема координат. Для данных по городам мы будем использовать систему
координат EPSG:4326. Чтобы добавить данные геометрии в соответству-
ющую колонку, используется функция PostGIS ST_GeomFromText, чтобы
сконвертировать координаты и идентификатор референсной системы из
текстового формата:
Листинг 10.3 Заполнение таблицы cities
Line 1 # INSERT INTO c i t i e s ( id , the_geom , name ) VALUES (1 ,
ST_GeomFromText( POINT( - 0 .1 2 5 7 5 1. 5 0 8) ,4326) , London ,
England ) ;
- # INSERT INTO c i t i e s ( id , the_geom , name ) VALUES (2 ,
ST_GeomFromText( POINT( - 8 1. 2 3 3 4 2. 9 8 3) ,4326) , London ,
Ontario ) ;
- # INSERT INTO c i t i e s ( id , the_geom , name ) VALUES (3 ,
ST_GeomFromText( POINT( 2 7 . 9 1 1 6 2 4 91 - 33 .01 5 2 9) , 4 3 2 6 ) ,
East London , SA ) ;
Все самые обычные операторы SQL могут быть использованы для вы-
бора данных из таблицы PostGIS:
188
10.2. PostGIS
Листинг 10.4 SELECT cities
Line 1 # SELECT * FROM c i t i e s ;
- i d | name | the_geom
- - -
- - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 1 | London , England | 0101000020
E6100000BBB88D06F016C0BF1B2FDD2406C14940
5 2 | London , Ontario | 0101000020
E6100000F4FDD478E94E54C0E7FBA9F1D27D4540
- 3 | East London , SA | 0101000020
E610000040AB064060E93B4059FAD005F58140C0
- (3 rows )
Это возвращает нам бессмысленные значения координат в шестна-
дцатеричной системе. Если вы хотите увидеть вашу геометрию в тек-
стовом формате WKT, используйте функцию ST_AsText(the_geom) или
ST_AsEwkt(the_geom). Вы также можете использовать функции ST_X(
the_geom), ST_Y(the_geom), чтобы получить числовые значения коорди-
нат:
Листинг 10.5 SELECT cities
Line 1 # SELECT id , ST_AsText( the_geom ) , ST_AsEwkt( the_geom ) , ST_X(
the_geom ) , ST_Y( the_geom ) FROM c i t i e s ;
- i d | s t _ a s t e xt | st_asewkt
| st_x | st_y
- - -
- - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - -
- 1 | POINT( - 0. 1 25 7 5 1 .5 0 8) | SRID=4326;POINT( - 0 .1 2 57
5 1 .50 8 ) | - 0 . 12 5 7 | 51.508
5 2 | POINT( - 81 . 23 3 4 2 .9 8 3) | SRID=4326;POINT( - 8 1. 2 33
4 2 .98 3 ) | - 8 1 .2 3 3 | 42.983
- 3 | POINT(27.91162491 - 3 3 .01 5 2 9) | SRID=4326;POINT
(27.91162491 - 3 3.0 1 5 29 ) | 27. 9116 2491 | - 3 3 . 0 1 5 2 9
- (3 rows )
Большинство таких функций начинаются с ST (пространственный тип)
и описаны в документации PostGIS. Теперь ответим на практический во-
прос: на каком расстоянии в метрах друг от друга находятся три города с
названием Лондон, учитывая сферичность земли?
Листинг 10.6 Расстояние до Лондона
189
10.3. pgSphere
Line 1 # SELECT p1 . name , p2 . name , ST_Distance_Sphere ( p1 . the_geom , p2 .
the_geom ) FROM c i t i e s AS p1 , c i t i e s AS p2 WHERE p1 . id >
p2 . i d ;
- name | name | st_ d istan ce_sp here
- - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - -
- London , Ontario | London , England | 5875 787.03 777356
5 East London , SA | London , England | 9789680.59961472
- East London , SA | London , Ontario | 1 389 220 8.6 782 928
- (3 rows )
Этот запрос возвращает расстояние в метрах между каждой парой го-
родов. Обратите внимание как часть WHERE предотвращает нас от полу-
чения расстояния от города до самого себя (расстояние всегда будет равно
нулю) и расстояния в обратном порядке (расстояние от Лондона, Англия
до Лондона, Онтарио будет таким же как от Лондона, Онтарио до Лон-
дона, Англия). Также можем рассчитать расстояния на сфере, используя
различные функции и указывая называния сфероида, параметры главных
полуосей и коэффициента обратного сжатия:
Листинг 10.7 Расстояние до Лондона
Line 1 # SELECT p1 . name , p2 . name , ST_Distance_Spheroid (
- # p1 . the_geom , p2 . the_geom , SPHEROID[ " GRS_1980
" ,6 3 7 8137 , 2 9 8.2 5 7 22 2]
- # )
- # FROM c i t i e s AS p1 , c i t i e s AS p2 WHERE p1 . i d > p2 . i d
;
5 name | name | st_distance _ s p h e r o id
- - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - -
- London , Ontario | London , England | 58 924 13. 639 991 53
- East London , SA | London , England | 9 756 842 .65 715 046
- East London , SA | London , Ontario | 1 388 414 9.4 143 795
10 (3 rows )
Заключение
В данной главе мы рассмотрели как начать работать с PostGIS. Более
подробно об использовании расширения можно ознакомиться через офи-
циальную документацию.
10.3 pgSphere
pgSphere обеспечивает PostgreSQL сферическими типами данных, а
также функциями и операторами для работы с ними. Используется для
работы с географическими (может использоваться вместо PostGIS) или
астрономическими типами данных.
190
10.3. pgSphere
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.8 Инициализация pgSphere
Line 1 # CREATE EXTENSION pg_sphere ;
После этого можем проверить, что расширение функционирует:
Листинг 10.9 Проверка pgSphere
Line 1 # SELECT spol y { (270d , - 10 d) , (270d , 3 0 d) , (290d , 1 0 d ) } ;
-
sp o l y
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {( 4 .7123 8 89803 8 469 , -0.174 5 3 2 9251994 3 3 ) ,(4.71238 8 9 8 0 3 8 4 6 9
, 0.52 3 59877 5 598299 ) ,(5.061454830 7 8 3 5 6 ,
0.1 7 45329 2 51994 3 3) }
5 (1 row )
И работу индексов:
Листинг 10.10 Проверка pgSphere
Line 1 # CREATE TABLE t e s t (
- # pos s po i nt NOT NULL
- # ) ;
- CREATE TABLE
5 # CREATE INDEX test_pos_idx ON t e s t USING GIST ( pos ) ;
- CREATE INDEX
- # INSERT INTO t e s t ( pos ) VALUES ( ( 1 0 .1 d , -90d ) ) , ( ( 10d
12m 1 1 . 3 s , -13 d 14m) ) ;
- INSERT 0 2
- # VACUUM ANALYZE t e s t ;
10 VACUUM
- # SELECT set_sphere_output ( ’DEG ) ;
- set_sphere_output
- - - - - - - - - - - - - - - - - - - -
- SET DEG
15 (1 row )
-
- # SELECT * FROM t e s t ;
- pos
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
20 ( 1 0. 1 d , -90d )
191
10.4. HStore
- (1 0 . 20313 8 8 88888 9 d , -13 .23333333 3333 3 d)
- (2 rows )
- # SET ena ble_seqscan = OFF;
- SET
25 # EXPLAIN SELECT * FROM t e s t WHERE pos = s p o in t ( 1 0. 1 d , - 9 0 d
) ;
- QUERY PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Bitmap Heap Scan on t e s t ( c o s t = 4 . 1 6 . . 9 . 5 0 rows=2 width=16)
- Recheck Cond : ( pos = ( 10 .1 d , -90d ) : : s p oi n t )
30 -> Bitmap Index Scan on test_pos_idx ( c o s t = 0 . 0 0 . . 4 . 1 6
rows=2 width =0)
- Index Cond : ( pos = ( 1 0 . 1 d , -90 d ) : : s p o i nt )
- (4 rows )
Заключение
Более подробно об использовании расширения можно ознакомиться че-
рез официальную документацию.
10.4 HStore
HStore расширение, которое реализует тип данных для хранения клю-
ч/значение в пределах одного значения в PostgreSQL (например, в одном
текстовом поле). Это может быть полезно в различных ситуациях, та-
ких как строки со многими атрибутами, которые редко выбираются, или
полу-структурированные данные. Ключи и значения являются простыми
текстовыми строками.
Начиная с версии 9.4 PostgreSQL был добавлен JSONB тип (бинарный
JSON). Данный тип является объединением JSON структуры с возмож-
ностью использовать индексы, как у Hstore. JSONB лучше Hstore тем,
что есть возможность сохранять вложенную структуру данных (nested) и
хранить не только текстовые строки в значениях. Поэтому лучше исполь-
зовать JSONB, если есть такая возможность.
Установка и использование
Для начала активируем расширение:
Листинг 10.11 Активация hstore
Line 1 # CREATE EXTENSION hs to r e ;
192
10.4. HStore
Проверим его работу:
Листинг 10.12 Проверка hstore
Line 1 # SELECT a=>1,a=>2 : : h s to r e ;
- hs t o r e
- - - - - - - - - - -
- "a"=>"1"
5 (1 row )
Как видно в примере 10.12 ключи в hstore уникальны. Создадим таб-
лицу и заполним её данными:
Листинг 10.13 Проверка hstore
Line 1 CREATE TABLE prod u cts (
- i d s e r i a l PRIMARY KEY,
- name varchar ,
- a t t r i b u t e s hs t o r e
5 ) ;
- INSERT INTO pr o ducts (name , a t t r i b u t e s )
- VALUES (
- Geek Love : A Novel ,
- author => " Kat herine Dunn" ,
10 pages => 368 ,
- ca t eg ory => f i c t i o n
- ) ,
- (
- Leica M9 ,
15 manufacturer => Leica ,
- type => camera ,
- m e gapixe l s => 1 8 ,
- se n so r => " f u l l - frame 35mm"
- ) ,
20 ( MacBook Air 11 ,
- manufacturer => Apple ,
- type => computer ,
- ram => 4GB,
- s t or ag e => 256GB,
25 p r o c e s s or => "1 . 8 ghz I n t e l i 7 duel c ore " ,
- weight => 2.3 8 l b s
- ) ;
Теперь можно производить поиск по ключу:
Листинг 10.14 Поиск по ключу
193
10.5. PLV8
Line 1 # SELECT name , a t t r i b u t e s -> pages as page FROM produc t s
WHERE a t t r i b u t e s ? pages ;
- name | page
- - - - - - - - - - - - - - - - - - - - - + - - - - - -
- Geek Love : A Novel | 368
5 (1 row )
Или по значению ключа:
Листинг 10.15 Поиск по значению ключа
Line 1 # SELECT name , a t t r i b u t e s -> manufacturer as ma nufacturer
FROM prod u cts WHERE a t tr i b u t e s -> type = computer ;
- name | manuf acturer
- - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - -
- MacBook Air 11 | Apple
5 (1 row )
Создание индексов:
Листинг 10.16 Индексы
Line 1 # CREATE INDEX products_hstore_index ON p roduct s USING GIST
( a t t r i b u t e s ) ;
- # CREATE INDEX products_hstore_index ON prod u cts USING GIN (
a t t r i b u t e s ) ;
Можно также cоздавать индекс на ключ:
Листинг 10.17 Индекс на ключ
Line 1 # CREATE INDEX product_manufacturer ON pr o d ucts ( ( pro d ucts .
a t t r i b u t e s -> manufacturer ) ) ;
Заключение
HStore расширение для удобного и индексируемого хранения слабо-
структурированных данных в PostgreSQL, если нет возможности исполь-
зовать версию базы 9.4 или выше, где для данной задачи есть встроенный
JSONB тип данных.
10.5 PLV8
PLV8 является расширением, которое предоставляет PostgreSQL про-
цедурный язык с движком V8 JavaScript. С помощью этого расширения
можно писать в PostgreSQL JavaScript функции, которые можно вызывать
из SQL.
194
10.5. PLV8
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.18 Инициализация plv8
Line 1 # CREATE ex t en s io n plv8 ;
V8 компилирует JavaScript код непосредственно в машинный код и с
помощью этого достигается высокая скорость работы. Для примера рас-
смотрим расчет числа Фибоначчи. Вот функция написана на plpgsql:
Листинг 10.19 Фибоначчи на plpgsql
Line 1 CREATE OR REPLACE FUNCTION
- p s q l f i b ( n i n t ) RETURNS i n t AS $$
- BEGIN
- IF n < 2 THEN
5 RETURN n ;
- END IF ;
- RETURN p s q l f i b ( n - 1 ) + p s q l f i b ( n - 2 ) ;
- END;
- $$ LANGUAGE p l p g sq l IMMUTABLE STRICT ;
Замерим скорость её работы:
Листинг 10.20 Скорость расчета числа Фибоначчи на plpgsql
Line 1 # SELECT n , p s q l f i b ( n ) FROM g e n e r a t e _ s er ie s ( 0 , 25 , 5 ) as n ;
- n | p s q l f i b
- - - - -+ - - - - - - - - -
- 0 | 0
5 5 | 5
- 10 | 55
- 15 | 610
- 20 | 6765
- 25 | 75025
10 (6 rows )
-
- Time : 2520. 3 8 6 ms
Теперь сделаем то же самое, но с использованием PLV8:
Листинг 10.21 Фибоначчи на plv8
Line 1 CREATE OR REPLACE FUNCTION
- f i b ( n i n t ) RETURNS i n t as $$
-
- f u nc ti o n f i b ( n ) {
195
10.5. PLV8
5 r e t urn n<2 ? n : f i b ( n - 1 ) + f i b ( n - 2 )
- }
- re tur n f i b ( n)
-
- $$ LANGUAGE p lv8 IMMUTABLE STRICT ;
Замерим скорость работы:
Листинг 10.22 Скорость расчета числа Фибоначчи на plv8
Line 1 # SELECT n , f i b ( n) FROM g e n e r at e_ se ri es ( 0 , 25 , 5 ) as n ;
- n | f i b
- - - - -+ - - - - - - -
- 0 | 0
5 5 | 5
- 10 | 55
- 15 | 610
- 20 | 6765
- 25 | 75025
10 (6 rows )
-
- Time : 5 . 2 0 0 ms
Как видим, PLV8 приблизительно в 484 (2520.386/5.200) раз быстрее
plpgsql. Можно ускорить работу расчета чисел Фибоначи на PLV8 за счет
кеширования:
Листинг 10.23 Фибоначчи на plv8
Line 1 CREATE OR REPLACE FUNCTION
- f i b c a c he ( n i n t ) RETURNS i n t as $$
- var memo = { 0: 0 , 1 : 1 };
- f u nc ti o n f i b ( n ) {
5 i f ( ! ( n i n memo) )
- memo [ n ] = f i b ( n - 1 ) + f i b ( n - 2 )
- r e t urn memo[ n ]
- }
- re tur n f i b ( n) ;
10 $$ LANGUAGE p lv8 IMMUTABLE STRICT ;
Замерим скорость работы:
Листинг 10.24 Скорость расчета числа Фибоначчи на plv8
Line 1 # SELECT n , f i b c a c h e ( n ) FROM g e n e r a t e _ s e r i e s ( 0 , 25 , 5) as n ;
- n | f i b c a c h e
- - - - -+ - - - - - - - - - -
- 0 | 0
196
10.5. PLV8
5 5 | 5
- 10 | 55
- 15 | 610
- 20 | 6765
- 25 | 75025
10 (6 rows )
-
- Time : 1 . 2 0 2 ms
Естественно эти измерения не имеют ничего общего с реальным ми-
ром (не нужно каждый день считать числа Фибоначчи в базе данных),
но позволяет понять, как V8 может помочь ускорить функции, которые
занимаются вычислением чего-либо в базе.
NoSQL
Одним из полезных применений PLV8 может быть создание на ба-
зе PostgreSQL документоориентированного хранилища. Для хранения
неструктурированных данных можно использовать hstore 10.4 HStore»),
но у него есть свои недостатки:
нет вложенности;
все данные (ключ и значение по ключу) это строка;
Для хранения данных многие документно-ориентированные базы дан-
ных используют JSON (MongoDB, CouchDB, Couchbase и т. д.). Для этого,
начиная с PostgreSQL 9.2, добавлен тип данных JSON, а с версии 9.4
JSONB. JSON тип можно добавить для PostgreSQL 9.1 и ниже используя
PLV8 и DOMAIN:
Листинг 10.25 Создание типа JSON
Line 1 CREATE OR REPLACE FUNCTION
- v a li d _j s on ( j s on t ex t )
- RETURNS BOOLEAN AS $$
- tr y {
5 JSON. p a r s e ( j s on ) ; r e t urn t r ue ;
- } catc h ( e ) {
- r e t urn f a l s e ;
- }
- $$ LANGUAGE p lv8 IMMUTABLE STRICT ;
10
- CREATE DOMAIN j son AS TEXT
- CHECK( v a l i d_ j s on (VALUE) ) ;
Функция valid_json используется для проверки JSON данных. Пример
использования:
197
10.5. PLV8
Листинг 10.26 Проверка JSON
Line 1 $ CREATE TABLE members ( id SERIAL , p r o f i l e js o n ) ;
- $ INSERT INTO members
- VALUES( not good j s on ) ;
- ERROR: value f o r domain j so n
5 v i o l a t e s check c o n s t r a i n t " json_check "
- $ INSERT INTO members
- VALUES( {" good " : " j so n " , " i s " : t rue } ) ;
- INSERT 0 1
- $ SELECT * FROM members ;
10 p r o f i l e
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {" good" : " js o n " , " i s " : t r u e }
- (1 row )
Рассмотрим пример использования JSON для хранения данных и PLV8
для их поиска. Для начала создадим таблицу и заполним её данными:
Листинг 10.27 Таблица с JSON полем
Line 1 $ CREATE TABLE members ( id SERIAL , p r o f i l e js o n ) ;
- $ SELECT count ( *) FROM members ;
- count
- - - - - - - - - -
5 1000000
- (1 row )
-
- Time : 201.109 ms
В profile поле мы записали приблизительно такую структуру JSON:
Листинг 10.28 JSON структура
Line 1 { +
- "name" : " L it zy S a t t e r f i e l d " , +
- " age " : 24 , +
- " s i b l i n g s " : 2 , +
5 " f a c u l t y " : f a l s e , +
- "numbers" : [ +
- { +
- " type " : "work" , +
- "number" : " 684 . 57 3.3 7 83 x368 "+
10 } , +
- { +
- " type " : "home" , +
- "number" : " 625 . 11 2.6 0 81 " +
- } +
15 ] +
198
10.5. PLV8
- }
Теперь создадим функцию для вывода значения по ключу из JSON
данном случае ожидаем цифру):
Листинг 10.29 Функция для JSON
Line 1 CREATE OR REPLACE FUNCTION get_numeric ( json_raw json , key
t e xt )
- RETURNS numeric AS $$
- var o = JSON . parse ( json_raw ) ;
- re tur n o [ key ] ;
5 $$ LANGUAGE p lv8 IMMUTABLE STRICT ;
Теперь мы можем произвести поиск по таблице, фильтруя по значени-
ям ключей age, siblings или другим числовым полям:
Листинг 10.30 Поиск по данным JSON
Line 1 $ SELECT * FROM members WHERE get_numeric ( p r o f i l e , age ) =
3 6 ;
- Time : 9340. 1 4 2 ms
- $ SELECT * FROM members WHERE get_numeric ( p r o f i l e , s i b l i n g s
) = 1 ;
- Time : 1432 0 .032 ms
Поиск работает, но скорость очень маленькая. Чтобы увеличить ско-
рость, нужно создать функциональные индексы:
Листинг 10.31 Создание индексов
Line 1 $ CREATE INDEX member_age ON members ( get_numeric ( p r o f i l e ,
age ) ) ;
- $ CREATE INDEX member_siblings ON members ( get_numeric (
p r o f i l e , s i b l i n g s ) ) ;
С индексами скорость поиска по JSON станет достаточно высокая:
Листинг 10.32 Поиск по данным JSON с индексами
Line 1 $ SELECT * FROM members WHERE get_numeric ( p r o f i l e , age ) =
3 6 ;
- Time : 5 7 . 4 2 9 ms
- $ SELECT * FROM members WHERE get_numeric ( p r o f i l e , s i b l i n g s
) = 1 ;
- Time : 6 5 . 1 3 6 ms
5 $ SELECT count ( *) from members where get_numeric ( p r o f i l e ,
age ) = 26 and get_numeric ( p r o f i l e , s i b l i n g s ) = 1 ;
- Time : 106.492 ms
199
10.6. Pg_repack
Получилось отличное документоориентированное хранилище из
PostgreSQL. Если используется PostgreSQL 9.4 или новее, то можно ис-
пользовать JSONB тип, у которого уже есть возможность создавать ин-
дексы на требуемые ключи в JSON структуре.
PLV8 позволяет использовать некоторые JavaScript библиотеки внутри
PostgreSQL. Вот пример рендера Mustache темплейтов:
Листинг 10.33 Функция для рендера Mustache темплейтов
Line 1 CREATE OR REPLACE FUNCTION mustache ( tem pla te tex t , view j s on
)
- RETURNS te x t as $$
- // . . . 400 l i n e s of mustache . . . . j s
- re tur n Mustache . render ( template , JSON. parse ( view ) )
5 $$ LANGUAGE p lv8 IMMUTABLE STRICT ;
Листинг 10.34 Рендер темплейтов
Line 1 $ SELECT mustache (
- h e l l o {{#t h in g s } } { {.}} {{/ t h i n gs } } : ) {{#data }}{{ key }}{{/
data }} ,
- {" t h in g s " : [ " world " , " from " , " p o s t g r e s q l " ] , " data " : {" key
" : "and me"}}
- ) ;
5 mustache
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- h e l l o world from p o s t g r e s q l : ) and me
- (1 row )
-
10 Time : 0 . 8 3 7 ms
Этот пример показывает какие возможности предоставляет PLV8
в PostgreSQL. В действительности рендерить Mustache темплейты в
PostgreSQL не лучшая идея.
Заключение
PLV8 расширение предоставляет PostgreSQL процедурный язык с
движком V8 JavaScript, с помощью которого можно работать с JavaScript
библиотеками, индексировать JSON данные и использовать его как более
быстрый язык для вычислений внутри базы.
10.6 Pg_repack
Таблицы в PostgreSQL представлены в виде страниц размером 8 КБ, в
которых размещены записи. Когда одна страница полностью заполняется
200
10.6. Pg_repack
записями, к таблице добавляется новая страница. При удалении записей
с помощью DELETE или изменении с помощью UPDATE, место где бы-
ли старые записи не может быть повторно использовано сразу же. Для
этого процесс очистки autovacuum, или команда VACUUM, пробегает по
изменённым страницам и помечает такое место как свободное, после чего
новые записи могут спокойно записываться в это место. Если autovacuum
не справляется, например в результате активного изменения большего ко-
личества данных или просто из-за плохих настроек, то к таблице будут из-
лишне добавляться новые страницы по мере поступления новых записей.
И даже после того как очистка дойдёт до наших удалённых записей, новые
страницы останутся. Получается, что таблица становится более разряжен-
ной в плане плотности записей. Это и называется эффектом раздувания
таблиц (table bloat).
Процедура очистки, autovacuum или VACUUM, может уменьшить раз-
мер таблицы, убрав полностью пустые страницы, но только при условии,
что они находятся в самом конце таблицы. Чтобы максимально уменьшить
таблицу в PostgreSQL есть VACUUM FULL или CLUSTER, но оба эти спо-
соба требуют «exclusively locks» на таблицу (то есть в это время с таблицы
нельзя ни читать, ни писать), что далеко не всегда является подходящим
решением.
Для решения подобных проблем существует утилита pg_repack. Эта
утилита позволяет сделать VACUUM FULL или CLUSTER команды без бло-
кировки таблицы. Для чистки таблицы pg_repack создает точную её ко-
пию в repack схеме базы данных (ваша база по умолчанию работает в public
схеме) и сортирует строки в этой таблице. После переноса данных и чист-
ки мусора утилита меняет схему у таблиц. Для чистки индексов утилита
создает новые индексы с другими именами, а по выполнению работы ме-
няет их на первоначальные. Для выполнения всех этих работ потребуется
дополнительное место на диске (например, если у вас 100 ГБ данных, и
из них 40 ГБ - распухание таблиц или индексов, то вам потребуется 100
ГБ + (100 ГБ - 40 ГБ) = 160 ГБ на диске минимум). Для проверки «рас-
пухания» таблиц и индексов в вашей базе можно воспользоваться SQL
запросом из раздела «14.2 Размер распухания (bloat) таблиц и индексов в
базе данных».
Существует ряд ограничений в работе pg_repack:
Не может очистить временные таблицы;
Не может очистить таблицы с использованием GIST индексов;
Нельзя выполнять DDL (Data Definition Language) на таблице во
время работы;
Пример использования
Выполнить команду CLUSTER всех кластеризированных таблиц и
VACUUM FULL для всех не кластеризированных таблиц в test базе дан-
201
10.6. Pg_repack
ных:
Скачать Листинг
Line 1 $ pg_repack t e s t
Выполните команду VACUUM FULL на foo и bar таблицах в test базе
данных (кластеризация таблиц игнорируется):
Скачать Листинг
Line 1 $ pg_repack - - no - or d e r - - t a b l e f o o - - t ab le bar t e s t
Переместить все индексы таблицы foo в неймспейс tbs:
Скачать Листинг
Line 1 $ pg_repack - d t e s t - - t a b l e f o o - - only - i n d exe s - - t a b le sp ac e
tbs
Pgcompact
Существует еще одно решение для борьбы с раздуванием таблиц. При
обновлении записи с помощью UPDATE, если в таблице есть свободное
место, то новая версия пойдет именно в свободное место, без выделения
новых страниц. Предпочтение отдается свободному месту ближе к началу
таблицы. Если обновлять таблицу с помощью «fake updates»(some_column
= some_column) с последней страницы, в какой-то момент все записи с
последней страницы перейдут в свободное место в предшествующих стра-
ницах таблицы. Таким образом, после нескольких таких операций, послед-
ние страницы окажутся пустыми и обычный не блокирующий VACUUM
сможет отрезать их от таблицы, тем самым уменьшив размер. В итоге, с
помощью такой техники можно максимально сжать таблицу, при этом не
вызывая критичных блокировок, а значит без помех для других сессий и
нормальной работы базы. Для автоматизации этой процедуры существует
утилита pgcompactor.
Основные характеристики утилиты:
не требует никаких зависимостей кроме Perl >=5.8.8, можно просто
скопировать pgcompactor на сервер и работать с ним;
работает через адаптеры DBD::Pg, DBD::PgPP или даже через стан-
дартную утилиту psql, если первых двух на сервере нет;
обработка как отдельных таблиц, так и всех таблиц внутри схемы,
базы или всего кластера;
возможность исключения баз, схем или таблиц из обработки;
202
10.6. Pg_repack
анализ эффекта раздувания и обработка только тех таблиц, у ко-
торых он присутствует, для более точных расчетов рекомендуется
установить расширение pgstattuple;
анализ и перестроение индексов с эффектом раздувания;
анализ и перестроение уникальных ограничений (unique constraints)
и первичных ключей (primary keys) с эффектом раздувания;
инкрементальное использование, т. е. можно остановить процесс сжа-
тия без ущерба чему-либо;
динамическая подстройка под текущую нагрузку базы данных, что-
бы не влиять на производительность пользовательских запросов
возможностью регулировки при запуске);
рекомендации администраторам, сопровождаемые готовым DDL,
для перестроения объектов базы, которые не могут быть перестрое-
ны в автоматическом режиме;
Рассмотрим пример использования данной утилиты. Для начала со-
здадим таблицу:
Листинг 10.35 Создаем test таблицу
Line 1 # c r e a t e t a b l e t e s t (
- id int4 ,
- int_1 i nt4 ,
- int_2 i nt4 ,
5 int_3 i nt4 ,
- ts_1 timestamptz ,
- ts_2 timestamptz ,
- ts_3 timestamptz ,
- text_1 te xt ,
10 text_2 te xt ,
- text_3 t ex t
- ) ;
После этого заполним её данными:
Листинг 10.36 Заполняем данными test таблицу
Line 1 # i n s e r t i n t o t e s t
- s e l e c t
- i ,
- c as t ( random ( ) * 10000000 as i nt 4 ) ,
5 c as t ( random ( ) *10000000 as i n t 4 ) ,
- c as t ( random ( ) *10000000 as i n t 4 ) ,
- now ( ) - 2 y e a rs : : i n t e r v a l * random ( ) ,
- now ( ) - 2 y e a rs : : i n t e r v a l * random ( ) ,
- now ( ) - 2 y e a rs : : i n t e r v a l * random ( ) ,
10 r e p eat ( text_1 , c as t (10 + random ( ) * 100 as i n t 4 ) ) ,
203
10.6. Pg_repack
- r e p eat ( text_2 , c as t (10 + random ( ) * 100 as i n t 4 ) ) ,
- r e p eat ( text_2 , c as t (10 + random ( ) * 100 as i n t 4 ) )
- from g e n e r at e_ se ri es ( 1 , 1000000) i ;
И добавим индексы:
Листинг 10.37 Индексы для test
Line 1 # a l t e r t a b l e t e s t add primary key ( i d ) ;
- ALTER TABLE
-
- # c r e a t e unique in dex i 1 on t e s t ( int_1 , int_2 ) ;
5 CREATE INDEX
-
- # c r e a t e index i 2 on t e s t ( int_2 ) ;
- CREATE INDEX
-
10 # c r e a t e index i 3 on t e s t ( int_3 , ts_1 ) ;
- CREATE INDEX
-
- # c r e a t e index i 4 on t e s t ( ts_2 ) ;
- CREATE INDEX
15
- # c r e a t e index i 5 on t e s t ( ts_3 ) ;
- CREATE INDEX
-
- # c r e a t e index i 6 on t e s t ( text_1 ) ;
20 CREATE INDEX
В результате получим test таблицу, как показано на 10.38:
Листинг 10.38 Таблица test
Line 1 # \d t e s t
- Table " pu b l i c . t e s t "
- Column | Type | M o di f i e rs
- - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - -
5 i d | i n t e g e r | not n u l l
- int_1 | i n t e g e r |
- int_2 | i n t e g e r |
- int_3 | i n t e g e r |
- ts_1 | timestamp with time zone |
10 ts_2 | timestamp with time zone |
- ts_3 | timestamp with time zone |
- text_1 | t e xt |
- text_2 | t e xt |
- text_3 | t e xt |
15 I ndexes :
204
10.6. Pg_repack
- " test_pkey " PRIMARY KEY, b t re e ( i d )
- " i 1 " UNIQUE, b tr e e ( int_1 , int_2 )
- " i 2 " b tr e e ( int_2 )
- " i 3 " b tr e e ( int_3 , ts_1 )
20 " i 4 " b tr e e ( ts_2 )
- " i 5 " b tr e e ( ts_3 )
- " i 6 " b tr e e ( text_1 )
Размер таблицы и индексов:
Листинг 10.39 Размер таблицы и индексов
Line 1 # SELECT nspname | | . | | relname AS " r e l a t i o n " ,
- pg_size_pretty ( p g _tot a l _re l a ti on_ s i z e (C. oid ) ) AS "
t o t a l _ s i z e "
- FROM pg_clas s C
- LEFT JOIN pg_namespace N ON (N. oid = C. r elnames pace )
5 WHERE nspname NOT IN ( pg_catalog , information_schema )
- AND nspname !~ ^pg_toast
- ORDER BY p g _ to tal _ r el at ion _ s iz e (C. oid ) DESC
- LIMIT 2 0;
- r e l a t i o n | t o t a l _ s i z e
10 - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- p ub li c . t e s t | 1705 MB
- p ub li c . i 6 | 242 MB
- p ub li c . i 3 | 30 MB
- p ub li c . i 1 | 21 MB
15 p ub li c . i 2 | 21 MB
- p ub li c . i 4 | 21 MB
- p ub li c . i 5 | 21 MB
- p ub li c . test_pkey | 21 MB
- (8 rows )
Теперь давайте создадим распухание таблицы. Для этого удалим 95%
записей в ней:
Листинг 10.40 Удаляем 95% записей
Line 1 # DELETE FROM t e s t WHERE random ( ) < 0 . 9 5 ;
- DELETE 950150
Далее сделаем VACUUM и проверим размер заново:
Листинг 10.41 Размер таблицы и индексов
Line 1 # VACUUM;
- VACUUM
- # SELECT nspname | | . | | relname AS " r e l a t i o n " ,
205
10.6. Pg_repack
- pg_size_pretty ( p g _tot a l _re l a ti on_ s i z e (C. oid ) ) AS "
t o t a l _ s i z e "
5 FROM pg_clas s C
- LEFT JOIN pg_namespace N ON (N. oid = C. r elnames pace )
- WHERE nspname NOT IN ( pg_catalog , information_schema )
- AND nspname !~ ^pg_toast
- ORDER BY p g _ to tal _ r el at ion _ s iz e (C. oid ) DESC
10 LIMIT 2 0;
- r e l a t i o n | t o t a l _ s i z e
- - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- p ub li c . t e s t | 1705 MB
- p ub li c . i 6 | 242 MB
15 p ub li c . i 3 | 30 MB
- p ub li c . i 1 | 21 MB
- p ub li c . i 2 | 21 MB
- p ub li c . i 4 | 21 MB
- p ub li c . i 5 | 21 MB
20 p ub li c . test_pkey | 21 MB
- (8 rows )
Как видно из результата, размер не изменился. Теперь попробуем
убрать через pgcompact распухание таблицы и индексов (для этого до-
полнительно добавим в базу данных расширение pgstattuple):
Листинг 10.42 Запуск pgcompact
Line 1 # CREATE EXTENSION p g s t a t t u p l e ;
- # \q
-
- $ g i t c l on e http s : / / gith ub . com/grayhemp/ p g t o o l k i t . g i t
5 $ cd p g t o o l k i t
- $ time . / bin / pgcompact - v i n f o - d a n a l y t i c s _ p r o d - - r e i nde x
2>&1 | t e e pgcompact . output
- Sun Oct 30 1 1 : 0 1 : 1 8 2016 INFO Database c o n n ec t io n method :
p s ql .
- Sun Oct 30 1 1 : 0 1 : 1 8 2016 dev INFO Created environment .
- Sun Oct 30 1 1 : 0 1 : 1 8 2016 dev INFO S t a t i c t i c s c a l c u l a t i o n
method : p g s t a t t u p l e .
10 Sun Oct 30 1 1 : 0 2 : 0 3 2016 dev , pu b l i c . t e s t INFO Vacuum
i n i t i a l : 169689 pages l e f t , d u ra t i o n 4 5 . 1 2 9 secon d s .
- Sun Oct 30 1 1 : 0 2 : 1 3 2016 dev , pu b l i c . t e s t INFO Bloat
s t a t i s t i c s with p g s t a t t up le : du r a t i o n 9.7 6 4 seconds .
- Sun Oct 30 1 1 : 0 2 : 1 3 2016 dev , pu b l i c . t e s t NOTICE S t a t i s t i c s :
169689 pages (218233 pages i n cl u d i n g t o a s t s and in d ex es )
, approx imat ely 94.62% (160557 pages ) can be compacted
redu c i n g the s i z e by 1254 MB.
- Sun Oct 30 1 1 : 0 2 : 1 3 2016 dev , pu b l i c . t e s t INFO Update by
column : i d .
206
10.6. Pg_repack
- Sun Oct 30 1 1 : 0 2 : 1 3 2016 dev , pu b l i c . t e s t INFO Set pages /
round : 1 0 .
15 Sun Oct 30 1 1 : 0 2 : 1 3 2016 dev , pu b l i c . t e s t INFO Set pages /
vacuum : 3 39 4 .
- Sun Oct 30 1 1 : 0 4 : 5 6 2016 dev , pu b l i c . t e s t INFO P r og re ss : 0%,
260 pages completed .
- Sun Oct 30 1 1 : 0 5 : 4 5 2016 dev , pu b l i c . t e s t INFO Cl ean ing i n
ave rage : 8 5.8 pages / second ( 0 .1 1 7 second s per 10 pages ) .
- Sun Oct 30 1 1 : 0 5 : 4 8 2016 dev , pu b l i c . t e s t INFO Vacuum
r o u t i n e : 166285 pages l e f t , dur a t i o n 2 .7 0 5 secon d s .
- Sun Oct 30 1 1 : 0 5 : 4 8 2016 dev , pu b l i c . t e s t INFO Set pages /
vacuum : 3 32 6 .
20 Sun Oct 30 1 1 : 0 5 : 5 7 2016 dev , pu b l i c . t e s t INFO P r og re ss : 2%,
4304 pages completed .
- Sun Oct 30 1 1 : 0 6 : 1 9 2016 dev , pu b l i c . t e s t INFO Cl ean ing i n
ave rage : 8 4 1. 6 pages / second ( 0 . 01 2 seco n d s per 10 pages ) .
- Sun Oct 30 1 1 : 0 6 : 2 3 2016 dev , pu b l i c . t e s t INFO Vacuum
r o u t i n e : 162942 pages l e f t , dur a t i o n 4 .2 6 4 secon d s .
- Sun Oct 30 1 1 : 0 6 : 2 3 2016 dev , pu b l i c . t e s t INFO Set pages /
vacuum : 3 26 0 .
- Sun Oct 30 1 1 : 0 6 : 4 9 2016 dev , pu b l i c . t e s t INFO Cl ean ing i n
ave rage : 8 1 8. 1 pages / second ( 0 . 01 2 seco n d s per 10 pages ) .
25 Sun Oct 30 1 1 : 0 6 : 4 9 2016 dev , pu b l i c . t e s t INFO Vacuum
r o u t i n e : 159681 pages l e f t , dur a t i o n 0 .3 2 5 secon d s .
- Sun Oct 30 1 1 : 0 6 : 4 9 2016 dev , pu b l i c . t e s t INFO Set pages /
vacuum : 3 19 4 .
- Sun Oct 30 1 1 : 0 6 : 5 7 2016 dev , pu b l i c . t e s t INFO P r og re ss : 6%,
10958 pages completed .
- Sun Oct 30 1 1 : 0 7 : 2 3 2016 dev , pu b l i c . t e s t INFO Cl ean ing i n
ave rage : 6 9 4. 8 pages / second ( 0 . 01 4 seco n d s per 10 pages ) .
- Sun Oct 30 1 1 : 0 7 : 2 4 2016 dev , pu b l i c . t e s t INFO Vacuum
r o u t i n e : 156478 pages l e f t , dur a t i o n 1 .3 6 2 secon d s .
30 Sun Oct 30 1 1 : 0 7 : 2 4 2016 dev , pu b l i c . t e s t INFO Set pages /
vacuum : 3 13 0 .
- . . .
- Sun Oct 30 1 1 : 4 9 : 0 2 2016 dev NOTICE P r oce s si n g complete .
- Sun Oct 30 1 1 : 4 9 : 0 2 2016 dev NOTICE P r oce s si n g r e s u l t s : s i z e
reduced by 1256 MB (1256 MB i n c lu d i n g t o a s t s and in d e xes
) i n t o t a l .
- Sun Oct 30 1 1 : 4 9 : 0 2 2016 NOTICE P ro c ess i ng complete : 0
r e t r i e s from 1 0.
35 Sun Oct 30 1 1 : 4 9 : 0 2 2016 NOTICE P ro c ess i ng r e s u l t s : s i z e
reduced by 1256 MB (1256 MB i n c lu d i n g t o a s t s and in d e xes )
in t o t a l , 1256 MB (1256 MB) dev .
- Sun Oct 30 1 1 : 4 9 : 0 2 2016 dev INFO Dropped environment .
-
- r e a l 47m44 . 831 s
207
10.6. Pg_repack
- u se r 0m37.6 9 2 s
40 s y s 0m16.3 3 6 s
После данной процедуры проверим размер таблицы и индексов в базе:
Листинг 10.43 Размер таблицы и индексов
Line 1 # VACUUM;
- VACUUM
- # SELECT nspname | | . | | relname AS " r e l a t i o n " ,
- pg_size_pretty ( p g _tot a l _re l a ti on_ s i z e (C. oid ) ) AS "
t o t a l _ s i z e "
5 FROM pg_clas s C
- LEFT JOIN pg_namespace N ON (N. oid = C. r elnames pace )
- WHERE nspname NOT IN ( pg_catalog , information_schema )
- AND nspname !~ ^pg_toast
- ORDER BY p g _ to tal _ r el at ion _ s iz e (C. oid ) DESC
10 LIMIT 2 0;
- r e l a t i o n | t o t a l _ s i z e
- - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- p ub li c . t e s t | 449 MB
- p ub li c . i 6 | 12 MB
15 p ub li c . i 3 | 1544 kB
- p ub li c . i 1 | 1112 kB
- p ub li c . i 2 | 1112 kB
- p ub li c . test_pkey | 1112 kB
- p ub li c . i 4 | 1112 kB
20 p ub li c . i 5 | 1112 kB
- (8 rows )
Как видно, в результате размер таблицы сократился до 449 МБ (было
1705 МБ). Индексы тоже стали меньше (например, i6 был 242 МБ, а стал
12 МБ). Операция заняла 47 минут и обрабатывала в среднем 600 страниц
в секунду (4.69 МБ в секунду). Можно ускорить выполнение этой опера-
ции через -- delay- ratio параметр (задержка между раундами выполнения,
по умолчанию 2 секунды) и --max-pages-per-round параметр (количество
страниц, что будет обработано за один раунд, по умолчанию 10). Более
подробно по параметрам pgcompact можно ознакомиться через команду
pgcompact --man.
Заключение
Pg_repack и pgcompact утилиты, которые могут помочь в борьбе с
распуханием таблиц в PostgreSQL «на лету».
208
10.7. Pg_prewarm
10.7 Pg_prewarm
Модуль pg_prewarm обеспечивает удобный способ загрузки данных
обьектов (таблиц, индексов, прочего) в буферный кэш PostgreSQL или
операционной системы. Данный модуль добавлен в contrib начиная с
PostgreSQL 9.4.
Установка и использование
Для начала нужно установить модуль:
Скачать Листинг
Line 1 $ CREATE EXTENSION pg_prewarm ;
После установки доступна функция pg_prewarm:
Скачать Листинг
Line 1 $ SELECT pg_prewarm ( pgbench_accounts ) ;
- pg_prewarm
- - - - - - - - - - - - -
- 4082
5 (1 row )
Первый аргумент объект, который требуется предварительно загру-
жать в память. Второй аргумент «режим» загрузки в память, который
может содержать такие варианты:
prefetch чтение файла ядром системы в асинхронном режиме
фоновом режиме). Это позволяет положить содержимое файла в кэ-
ше ядра системы. Но этот режим работает не на всех платформах;
read результат похож на prefetch, но делается синхронно значит
медленнее). Работает на всех платформах;
buffer в этом режиме данные будут грузиться в PostgreSQL
shared_buffers. Этот режим используется по умолчанию;
Третий аргумент называется «fork». О нем не нужно беспокоиться. Воз-
можные значения: «main» (используется по умолчанию), «fsm», «vm».
Четвертый и пятый аргументы указывают диапазон страниц для за-
грузки данных. По умолчанию загружается весь обьект в память, но мож-
но решить, например, загрузить только последние 1000 страниц:
Скачать Листинг
Line 1 $ SELECT pg_prewarm (
- pgbench_accounts ,
209
10.8. Smlar
- f i r s t _ b l o c k := (
- SELECT pg_r e l at io n_ size ( pgbench_accounts ) /
c u rr e nt _ se t ti n g ( b l o c k_si z e ) : : i nt 4 - 1000
5 )
- ) ;
Заключение
Pg_prewarm расширение, которое позволяет предварительно загру-
зить («подогреть») данные в буферной кэш PostgreSQL или операционной
системы.
10.8 Smlar
Поиск похожести в больших базах данных является важным вопросом
в настоящее время для таких систем как блоги (похожие статьи), интернет-
магазины (похожие продукты), хостинг изображений (похожие изображе-
ния, поиск дубликатов изображений) и т. д. PostgreSQL позволяет сделать
такой поиск более легким. Прежде всего необходимо понять, как мы будем
вычислять сходство двух объектов.
Похожесть
Любой объект может быть описан как список характеристик. Напри-
мер, статья в блоге может быть описана тегами, продукт в интернет-
магазине может быть описан размером, весом, цветом и т. д. Это означает,
что для каждого объекта можно создать цифровую подпись массив чи-
сел, описывающих объект (отпечатки пальцев, n-grams). То есть нужно
создать массив из цифр для описания каждого объекта.
Расчет похожести
Есть несколько методов вычисления похожести сигнатур объектов.
Прежде всего, легенда для расчетов:
𝑁
𝑎
, 𝑁
𝑏
количество уникальных элементов в массивах;
𝑁
𝑢
количество уникальных элементов при объединении массивов;
𝑁
𝑖
количество уникальных элементов при пересечении массивов.
Один из простейших расчетов похожести двух объектов - количество
уникальных элементов при пересечении массивов делить на количество
уникальных элементов в двух массивах:
𝑆(𝐴, 𝐵) =
𝑁
𝑖
(𝑁
𝑎
+ 𝑁
𝑏
)
(10.1)
210
10.8. Smlar
или проще
𝑆(𝐴, 𝐵) =
𝑁
𝑖
𝑁
𝑢
(10.2)
Преимущества:
Легко понять;
Скорость расчета: 𝑁 * log 𝑁;
Хорошо работает на похожих и больших 𝑁
𝑎
и 𝑁
𝑏
;
Также похожесть можно рассчитать по формуле косинусов:
𝑆(𝐴, 𝐵) =
𝑁
𝑖
𝑁
𝑎
* 𝑁
𝑏
(10.3)
Преимущества:
Скорость расчета: 𝑁 * log 𝑁;
Отлично работает на больших 𝑁;
Но у обоих этих методов есть общие проблемы:
Если элементов мало, то разброс похожести невелик;
Глобальная статистика: частые элементы ведут к тому, что вес ниже;
Спамеры и недобросовестные пользователи могут разрушить работу
алгоритма и он перестанет работать на Вас;
Для избежания этих проблем можно воспользоваться TF/IDF метри-
кой:
𝑆(𝐴, 𝐵) =
𝑖<𝑁
𝑎
,𝑗<𝑁
𝑏
,𝐴
𝑖
=𝐵
𝑗
𝑇 𝐹
𝑖
* 𝑇 𝐹
𝑗
𝑖<𝑁
𝑎
𝑇 𝐹
2
𝑖
*
𝑗<𝑁
𝑏
𝑇 𝐹
2
𝑗
(10.4)
где инвертированный вес элемента в коллекции:
𝐼𝐷𝐹
𝑒𝑙𝑒𝑚𝑒𝑛𝑡
= log (
𝑁
𝑜𝑏𝑗𝑒𝑐𝑡𝑠
𝑁
𝑜𝑏𝑗𝑒𝑐𝑡𝑠 𝑤𝑖𝑡ℎ 𝑒𝑙𝑒𝑚𝑒𝑛𝑡
+ 1) (10.5)
и вес элемента в массиве:
𝑇 𝐹
𝑒𝑙𝑒𝑚𝑒𝑛𝑡
= 𝐼𝐷𝐹
𝑒𝑙𝑒𝑚𝑒𝑛𝑡
* 𝑁
𝑜𝑐𝑐𝑢𝑟𝑟𝑒𝑛𝑐𝑒𝑠
(10.6)
Все эти алгоритмы встроены в smlar расширение. Главное понимать,
что для TF/IDF метрики требуется вспомогательная таблица для хране-
ния данных, по сравнению с другими простыми метриками.
211
10.8. Smlar
Smlar
Олег Бартунов и Теодор Сигаев разработали PostgreSQL расширение
smlar, которое предоставляет несколько методов для расчета похожести
массивов (все встроенные типы данных поддерживаются) и оператор для
расчета похожести с поддержкой индекса на базе GIST и GIN. Для начала
установим это расширение:
Листинг 10.44 Установка smlar
Line 1 $ g i t c lo n e g i t : / / s ig ae v . ru / smlar
- $ cd smlar
- $ USE_PGXS=1 make && make i n s t a l l
Теперь проверим расширение:
Листинг 10.45 Проверка smlar
Line 1 $ p s ql
- ps q l ( 9 . 5 . 1 )
- Type " help " f o r help .
-
5 t e s t=# CREATE EXTENSION smlar ;
- CREATE EXTENSION
-
- t e s t=# SELECT smlar ( {1 ,4 , 6} : : i n t [ ] , {5 ,4 ,6} : : i n t [ ] ) ;
- smlar
10 - - - - - - - - - -
- 0. 6 6 6667
- (1 row )
-
- t e s t=# SELECT smlar ( {1 ,4 , 6} : : i n t [ ] , {5 ,4 ,6} : : i n t [ ] , N. i
/ s qr t (N. a * N. b ) ) ;
15 smlar
- - - - - - - - - - -
- 0. 6 6 6667
- (1 row )
Методы, которые предоставляет это расширение:
float4 smlar(anyarray, anyarray) вычисляет похожесть двух масси-
вов. Массивы должны быть одного типа;
float4 smlar(anyarray, anyarray, bool useIntersect ) вычисляет похо-
жесть двух массивов составных типов. Составной тип выглядит сле-
дующим образом:
Листинг 10.46 Составной тип
212
10.8. Smlar
Line 1 CREATE TYPE type_name AS ( element_name anytype ,
weight_name f l o a t 4 ) ;
useIntersect параметр для использования пересекающихся элементов
в знаменателе;
float4 smlar( anyarray a, anyarray b, text formula ) вычисляет похо-
жесть двух массивов по данной формуле, массивы должны быть того
же типа. Доступные переменные в формуле:
– N.i количество общих элементов обоих массивов (пересече-
ние);
– N.a количество уникальных элементов первого массива;
– N.b количество уникальных элементов второго массива;
anyarray % anyarray возвращает истину, если похожесть масси-
вов больше, чем указанный предел. Предел указывается в конфиге
PostgreSQL:
Листинг 10.47 Smlar предел
Line 1 cu s t o m _ v a r i a b l e _ c l a s s e s = smlar
- smlar . t h r e sh o l d = 0 .8 # предел от 0 до 1
Также в конфиге можно указать дополнительные настройки для
smlar:
Листинг 10.48 Smlar настройки
Line 1 cu s t o m _ v a r i a b l e _ c l a s s e s = smlar
- smlar . t h r e sh o l d = 0 .8 # предел от 0 до 1
- smlar . type = c o si n e # по какой формуле производить расчет
похожести : c o si ne , t f i d f , o v erl a p
- smlar . s t a t t a b l e = s t a t # Имя таблицы для хранения
статистики при работе по формуле t f i d f
Более подробно можно прочитать в README этого расширения.
GiST и GIN индексы поддерживаются для оператора %.
Пример: поиск дубликатов картинок
Рассмотрим простой пример поиска дубликатов картинок. Алгоритм
помогает найти похожие изображения, которые, например, незначитель-
но отличаются (изображение обесцветили, добавили водяные знаки, про-
пустили через фильтры). Но, поскольку точность мала, то у алгоритма
есть и позитивная сторона скорость работы. Как можно определить,
что картинки похожи? Самый простой метод сравнивать попиксельно
213
10.8. Smlar
два изображения. Но скорость такой работы будет невелика на больших
разрешениях. Тем более, такой метод не учитывает, что могли изменять
уровень света, насыщенность и прочие характеристики изображения. Нам
нужно создать сигнатуру для картинок в виде массива цифр:
Рис. 10.1: Пиксельная матрица
Создаем пиксельную матрицу к изображению (изменения размера
изображения к требуемому размеру пиксельной матрице), например
15X15 пикселей(Рис. 10.1);
Рассчитаем интенсивность каждого пикселя (интенсивность вычис-
ляется по формуле 0.299 *красный + 0.587 *зеленый + 0.114 *синий).
Интенсивность поможет нам находить похожие изображения, не об-
ращая внимание на используемые цвета в них;
Узнаем отношение интенсивности каждого пикселя к среднему зна-
чению интенсивности по всей матрице(Рис. 10.2);
Генерируем уникальное число для каждой ячейки (отношение интен-
сивности + координаты ячейки);
Сигнатура для картинки готова;
Создаем таблицу, где будем хранить имя картинки, путь к ней и её
сигнатуру:
214
10.8. Smlar
Рис. 10.2: Пиксельная матрица
Листинг 10.49 Таблица для изображений
Line 1 CREATE TABLE images (
- i d s e r i a l PRIMARY KEY,
- name var char ( 5 0 ) ,
- img_path v a rchar (25 0 ) ,
5 image_array i n t e g e r [ ]
- ) ;
Создадим GIN или GIST индекс:
Листинг 10.50 Создание GIN или GIST индекса
Line 1 CREATE INDEX image_array_gin ON images USING GIN( image_array
_int4_sml_ops ) ;
- CREATE INDEX image_array_gist ON images USING GIST(
image_array _int4_sml_ops ) ;
Теперь можно произвести поиск дубликатов:
Листинг 10.51 Поиск дубликатов
Line 1 t e s t=# SELECT count ( *) from images ;
- count
- - - - - - - - - -
- 1000000
5 (1 row )
-
- t e s t=# EXPLAIN ANALYZE SELECT count ( * ) FROM images WHERE
images . image_array %
{1 0 10 259 , 1 0 112 5 3 , .. . , 2 423 2 5 3 , 2 424 2 5 2} : : i n t [ ] ;
-
215
10.8. Smlar
- Bitmap Heap Scan on images ( c o s t = 2 86 . 64 . .3 9 69. 4 5 rows=986
width =4) ( a ct u a l time = 5 04. 3 12 .. 2 0 47. 5 33 rows=200000 l oo p s
=1)
10 Recheck Cond : ( image_array %
{1 0 10 259 , 1 0 112 5 3 , .. . , 2 423 2 5 3 , 2 424 2 5 2} : : i n t e g e r [ ] )
- -> Bitmap Index Scan on image_array_gist ( c o s t
= 0 . 0 0 . . 2 86 .3 9 rows=986 width=0) ( a ct u a l time
= 4 4 6. 1 09. . 44 6 . 10 9 rows =200000 l o op s =1)
- Index Cond : ( image_array %
{1 0 10 259 , 1 0 112 5 3 , .. . , 2 423 2 5 3 , 2 424 2 5 2} : : i n t e g e r [ ] )
- Total runtime : 2152.4 1 1 ms
- (5 rows )
где {1010259,...,2424252} :: int [] сигнатура изображения, для которой
пытаемся найти похожие изображения. С помощью smlar.threshold управ-
ляем % похожести картинок (при каком проценте они будут попадать в
выборку).
Дополнительно можем добавить сортировку по самым похожим изоб-
ражениям:
Листинг 10.52 Добавляем сортировку по сходству картинок
Line 1 t e s t=# EXPLAIN ANALYZE SELECT smlar ( images . image_array ,
{ 1 01 02 5 9 , .. . , 24 2 4 2 52 } : : i n t [ ] ) as s i m i l a r i t y FROM images
WHERE images . image_array % {1010259 ,1011253 ,
. . . , 2 4 23 2 5 3 , 2 4 24 2 5 2 } : : i n t [ ] ORDER BY s i m i l a r i t y DESC;
-
-
- So r t ( c os t = 4 02 0 . 94 . .40 2 3. 4 1 rows=986 width =924) ( a ct u al
time = 2 8 88. 4 7 2.. 2 9 01. 9 7 7 rows =200000 l o op s =1)
5 Sort Key : ( smlar ( image_array , { . . . , 2 4 2 4 2 5 2 } : : i n t e g e r [ ] )
)
- Sort Method : q u i c k so r t Memory : 15520kB
- -> Bitmap Heap Scan on images ( c o s t = 2 86 . 64. . 39 7 1. 9 1
rows=986 width =924) ( a ct u a l time = 4 74 .4 3 6 ..2 7 29. 6 38 rows
=200000 l oop s =1)
- Recheck Cond : ( image_array % { . . . , 2 4 2 4 2 5 2 } : :
i n t e g e r [ ] )
- -> Bitmap Index Scan on image_array_gist ( c o s t
= 0 . 0 0 . . 2 86 .3 9 rows=986 width=0) ( a ct u a l time
= 4 2 1. 1 40. . 42 1 . 14 0 rows =200000 l o op s =1)
10 Index Cond : ( image_array % { . . . , 2 4 2 4 2 5 2 } : :
i n t e g e r [ ] )
- Total runtime : 2912.2 0 7 ms
- (8 rows )
216
10.9. Multicorn
Заключение
Smlar расширение может быть использовано в системах, где нам нужно
искать похожие объекты, такие как: тексты, темы, блоги, товары, изобра-
жения, видео, отпечатки пальцев и прочее.
10.9 Multicorn
Multicorn расширение для PostgreSQL версии 9.1 или выше, которое
позволяет создавать собственные FDW (Foreign Data Wrapper), используя
язык программирования Python. Foreign Data Wrapper позволяют под-
ключиться к другим источникам данных (другая база, файловая система,
REST API, прочее) в PostgreSQL и были представлены с версии 9.1.
Пример
Установка будет проводиться на Ubuntu Linux. Для начала нужно уста-
новить требуемые зависимости:
Листинг 10.53 Multicorn
Line 1 $ sudo a p tit u d e i n s t a l l bui l d - e s s e n t i a l p o s t gr es q l - s e rv er -
dev - 9 . 3 python - dev python - s e t u p t o o l s
Следующим шагом установим расширение:
Листинг 10.54 Multicorn
Line 1 $ g i t c lo n e git @gith ub . com : Kozea/ Multico rn . g i t
- $ cd M ulti corn
- $ make && sudo make i n s t a l l
Для завершения установки активируем расширение для базы данных:
Листинг 10.55 Multicorn
Line 1 # CREATE EXTENSION m u l t i c o rn ;
- CREATE EXTENSION
Рассмотрим какие FDW может предоставить Multicorn.
Реляционная СУБД
Для подключения к другой реляционной СУБД Multicorn использу-
ет SQLAlchemy библиотеку. Данная библиотека поддерживает SQLite,
PostgreSQL, MySQL, Oracle, MS-SQL, Firebird, Sybase, и другие базы дан-
ных. Для примера настроим подключение к MySQL. Для начала нам по-
требуется установить зависимости:
217
10.9. Multicorn
Листинг 10.56 Multicorn
Line 1 $ sudo a p tit u d e i n s t a l l python - sqla lchem y python - mysqldb
В MySQL базе данных «testing» у нас есть таблица «companies»:
Листинг 10.57 Multicorn
Line 1 $ mysql -u r o ot -p t e s t i n g
-
- mysql> SELECT * FROM companies ;
- + - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - +
5 | i d | created_at | updated_at |
- + - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - +
- | 1 | 2013 -07 -16 1 4 : 0 6 : 0 9 | 2013 -07 -16 1 4 : 0 6 :0 9 |
- | 2 | 2013 -07 -16 1 4 : 3 0 : 0 0 | 2013 -07 -16 1 4 : 3 0 :0 0 |
- | 3 | 2013 -07 -16 1 4 : 3 3 : 4 1 | 2013 -07 -16 1 4 : 3 3 :4 1 |
10 | 4 | 2013 -07 -16 1 4 : 3 8 : 4 2 | 2013 -07 -16 1 4 : 3 8 :4 2 |
- | 5 | 2013 -07 -19 1 4 : 3 8 : 2 9 | 2013 -07 -19 1 4 : 3 8 :2 9 |
- + - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - +
- 5 rows i n s e t ( 0. 0 0 s ec )
В PostgreSQL мы должны создать сервер для Multicorn:
Листинг 10.58 Multicorn
Line 1 # CREATE SERVER alchemy_srv f o r e i g n data wrapper multicor n
o p t io n s (
- wrapper multicorn . sqlalchemyfdw . SqlAlchemyFdw
- ) ;
- CREATE SERVER
Теперь мы можем создать таблицу, которая будет содержать данные
из MySQL таблицы «companies»:
Листинг 10.59 Multicorn
Line 1 # CREATE FOREIGN TABLE mysql_companies (
- id i nt e g e r ,
- created_ at timestamp without time zone ,
- updated_at timestamp without time zone
5 ) s e r v e r alchemy_srv o pt i ons (
- tablename companies ,
- db_url mysql : // r o ot : password@127 . 0 . 0 . 1 / t e s t i n g
- ) ;
- CREATE FOREIGN TABLE
Основные опции:
218
10.9. Multicorn
db_url (string) — SQLAlchemy настройки подключения к базе дан-
ных (примеры: mysql://<user>:<password>@<host>/<dbname>, mssql:
mssql://<user>:<password>@<dsname>). Подробнее можно узнать из
SQLAlchemy документации;
tablename (string) имя таблицы в подключенной базе данных.
Теперь можем проверить, что все работает:
Листинг 10.60 Multicorn
Line 1 # SELECT * FROM mysql_companies ;
- i d | cre ated_at | updated_at
- - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - -
- 1 | 2013 -07 -16 1 4 : 0 6 : 0 9 | 2013 -07 -16 1 4 : 0 6 : 0 9
5 2 | 2013 -07 -16 1 4 : 3 0 : 0 0 | 2013 -07 -16 1 4 : 3 0 : 0 0
- 3 | 2013 -07 -16 1 4 : 3 3 : 4 1 | 2013 -07 -16 1 4 : 3 3 : 4 1
- 4 | 2013 -07 -16 1 4 : 3 8 : 4 2 | 2013 -07 -16 1 4 : 3 8 : 4 2
- 5 | 2013 -07 -19 1 4 : 3 8 : 2 9 | 2013 -07 -19 1 4 : 3 8 : 2 9
- (5 rows )
IMAP сервер
Multicorn может использоваться для получения писем по IMAP прото-
колу. Для начала установим зависимости:
Листинг 10.61 Multicorn
Line 1 $ sudo a p tit u d e i n s t a l l python - pip
- $ sudo pip i n s t a l l i m ap c li e nt
Следующим шагом мы должны создать сервер и таблицу, которая бу-
дет подключена к IMAP серверу:
Листинг 10.62 Multicorn
Line 1 # CREATE SERVER multicorn_imap FOREIGN DATA WRAPPER
multicorn o pti o ns ( wrapper multicorn . imapfdw . ImapFdw )
;
- CREATE SERVER
- # CREATE FOREIGN TABLE my_inbox (
- " Message - ID" c h a r ac t e r varying ,
5 "From" c ha r a c te r varying ,
- " Subject " c ha r a c te r varying ,
- " payload " c h a r ac te r varying ,
- " f l a g s " c ha r a c te r v a r y i ng [ ] ,
- "To" c h a r ac t e r varyin g ) s e r v e r multicorn_imap o pt i o ns (
10 hos t imap . gmail . com ,
219
10.9. Multicorn
- port 993 ,
- payload_column payload ,
- flags_column f l a g s ,
- s s l True ,
15 l o g i n example@gmail . com ,
- password s u per s ecr e tpa s sw or d
- ) ;
- CREATE FOREIGN TABLE
Основные опции:
host ( string) IMAP хост;
port ( string ) IMAP порт;
login ( string ) IMAP логин;
password (string) IMAP пароль;
payload_column (string) имя поля, которое будет содержать текст
письма;
flags_column (string) имя поля, которое будет содержать IMAP фла-
ги письма (массив);
ssl (boolean) использовать SSL для подключения;
imap_server_charset (string) кодировка для IMAP команд. По умол-
чанию UTF8.
Теперь можно получить письма через таблицу «my_inbox»:
Листинг 10.63 Multicorn
Line 1 # SELECT f l a g s , " S u b j e c t " , payload FROM my_inbox LIMIT 1 0 ;
- f l a g s | Su b j e c t |
payload
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - -
- { $MailFlagBit1 , "\\ Flagged " , " \\ Seen "} | Test emai l |
Test em a il \ r +
5 | |
- {"\\ Seen"} | Test second emai l |
Test second e m ail \ r+
- | |
- (2 rows )
RSS
Multicorn может использовать RSS как источник данных. Для начала
установим зависимости:
220
10.9. Multicorn
Листинг 10.64 Multicorn
Line 1 $ sudo a p tit u d e i n s t a l l python - lxml
Как и в прошлые разы, создаем сервер и таблицу для RSS ресурса:
Листинг 10.65 Multicorn
Line 1 # CREATE SERVER rs s _ s r v f o r e i g n data wrapper multicorn
o p t io n s (
- wrapper multicorn . r s s f dw . RssFdw
- ) ;
- CREATE SERVER
5 # CREATE FOREIGN TABLE my_rss (
- " pubDate" timestamp ,
- d e s c r i p t i o n c h a r ac t e r varying ,
- t i t l e c h a ra c t e r varying ,
- l i n k c ha ra c t e r varyi n g
10 ) s e r v e r r s s_srv o pti o ns (
- u r l http : // news . yahoo . com/ r s s / entertainment
- ) ;
- CREATE FOREIGN TABLE
Основные опции:
url ( string) URL RSS ленты.
Кроме того, вы должны быть уверены, что PostgreSQL база данных
использует UTF-8 кодировку другой кодировке вы можете получить
ошибки). Результат таблицы «my_rss»:
Листинг 10.66 Multicorn
Line 1 # SELECT "pubDate" , t i t l e , l i n k from my_rss ORDER BY "
pubDate" DESC LIMIT 1 0;
- pubDate | t i t l e
|
l i n k
- - -
- - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 2013 -09 -28 1 4: 11 :5 8 | Royal Mint c o i n s to mark Prince
George c h r i s t e n i n g | http : / / news . yahoo . com/ r o yal - mint -
coins - mark - prince - george - c h r i st en i n g - 1 15 9062 4 2 . html
5 2013 -09 -28 1 1: 47 :0 3 | Miss P h i l i p p i n e s wins Miss World i n
In d on esi a | http : // news . yahoo . com/miss - p h i l i p p i n e s -
wins - miss - world - i n d on es i a - 1445 4 4 381. html
- 2013 -09 -28 1 0: 59 :1 5 | B i l l i o n a i r e s daughter i n NJ co u rt i n
w i l l d i sp u t e | http : // news . yahoo . com/ b i l l i o n a i r e s -
daughter - nj - court - dispute - 1 44 4323 3 1 . html
221
10.9. Multicorn
- 2013 -09 -28 0 8: 40 :4 2 | S ec ur i t y t i g h t at Miss World f i n a l i n
In d on esi a | http : // news . yahoo . com/ s ec ur it y - t igh t - miss
- world - f i n a l - i n do ne si a - 1 2 37 1404 1 . html
- 2013 -09 -28 0 8: 17 :5 2 | Guest l i n e u p s f o r the Sunday news
shows | http : / / news . yahoo . com/ guest - l i n eup s -
sunday - news - shows - 1 83 81 5643 . html
- 2013 -09 -28 0 7: 37 :0 2 | S ec ur i t y t i g h t at Miss World crowning
in I nd one s i a | http : // news . yahoo . com/ s e c u r i t y - t igh t - miss
- world - crowning - i n d on e si a - 1136 3 4 310. html
10 2013 -09 -27 2 0: 49 :3 2 | Simons stamps h i s n a tu r al mark on
Dior | http : / / news . yahoo . com/simons - stamps -
natu r a l - mark - dior - 2 23 8485 2 8 . html
- 2013 -09 -27 1 9: 50 :3 0 | Jackson j ur y ends d e l i b e r a t i o n s u n t i l
Tuesday | http : / / news . yahoo . com/ j ackson - jury - ends -
d e l i b e r a t i o n s - u nt il - tuesday - 2 3 50 309 6 9 . html
- 2013 -09 -27 1 9: 23 :4 0 | E ri c Clapton - owned Richte r p a in t i n g
to s e l l in NYC | http : // news . yahoo . com/ e r i c - cla pton - owned
- r i c h t e r - p a i nt i n g - s e l l - nyc -2 0 1 4472 5 2 . html
- 2013 -09 -27 1 9: 14 :1 5 | Report : Hollywood i s l e s s gay -
f r i e n d l y o f f - s cr e en | http : / / news . yahoo . com/ r e p o r t -
hollywood - l e s s - gay - f r i e n d l y - o f f - screen -2 3 1 4 1523 5 . html
- (10 rows )
CSV
Multicorn может использовать CSV файл как источник данных. Как и
в прошлые разы, создаем сервер и таблицу для CSV ресурса:
Листинг 10.67 Multicorn
Line 1 # CREATE SERVER csv_srv f o r e i g n data wrapper multicorn
o p t io n s (
- wrapper multicorn . csvfdw . CsvFdw
- ) ;
- CREATE SERVER
5 # CREATE FOREIGN TABLE c s v t e s t (
- s o r t_order numeric ,
- common_name c ha r a c te r varying ,
- formal_name c ha r a c te r varying ,
- main_type c h ar a c t er varying ,
10 sub_type c h a r ac t e r varying ,
- s ov e r e i g n t y c ha r a c te r varying ,
- c a p i t a l c h a ra ct e r varying
- ) s e r v e r csv_srv o p tio n s (
- fi l en a m e / var /data / c o u n t r y l i s t . c sv ,
15 skip_header 1 ,
- d e l i m i t e r , ) ;
222
10.9. Multicorn
- CREATE FOREIGN TABLE
Основные опции:
filename ( string) полный путь к CSV файлу;
delimiter (character) разделитель в CSV файле (по умолчанию «,»);
quotechar (character) кавычки в CSV файле;
skip_header (integer) число строк, которые необходимо пропустить
(по умолчанию 0).
Результат таблицы «csvtest»:
Листинг 10.68 Multicorn
Line 1 # SELECT * FROM c s v t e s t LIMIT 1 0;
- s o r t_order | common_name | formal_name
| main_type | sub_type |
s o v e r e i g n t y | c a p i t a l
- - -
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - -
- 1 | A fghanistan | I sl a m i c S t a t e o f
Af ghan istan | Independent S t a t e | |
| Kabul
5 2 | Albania | Republic o f Albania
| Independent Stat e | |
| Tirana
- 3 | A l g er i a | People s Democratic
Republi c o f A lg e ri a | Independent S ta t e | |
| A l g i e r s
- 4 | Andorra | P r i n c i p a l i t y o f Andorra
| Independent Stat e | |
| Andorra l a V e l la
- 5 | Angola | Republic o f Angola
| Independent Stat e | |
| Luanda
- 6 | Antigua and Barbuda |
| Independent Stat e | |
| S ai n t John s
10 7 | Argentina | Arge ntin e Republic
| Independent Stat e | |
| Buenos A i r es
- 8 | Armenia | Repub lic o f Armenia
| Independent Stat e | |
| Yerevan
- 9 | A u s t ra li a | Commonwealth o f A u s t r a l ia
| Independent Stat e | |
| Canberra
223
10.10. Pgaudit
- 10 | Aus t r i a | Republic o f Austria
| Independent Stat e | |
| Vienna
- (10 rows )
Другие FDW
Multicorn также содержит FDW для LDAP и файловой системы. LDAP
FDW может использоваться для доступа к серверам по LDAP протоколу.
FDW для файловой системы может быть использован для доступа к дан-
ным, хранящимся в различных файлах в файловой системе.
Собственный FDW
Multicorn предоставляет простой интерфейс для написания собствен-
ных FDW. Более подробную информацию вы можете найти по этой ссыл-
ке.
PostgreSQL 9.3+
В PostgreSQL 9.1 и 9.2 была представлена реализация FDW только на
чтение. Начиная с версии 9.3, FDW может писать во внешние источники
данных. Сейчас Multicorn поддерживает запись данных в другие источни-
ки, начиная с версии 1.0.0.
Заключение
Multicorn расширение для PostgreSQL, которое позволяет использо-
вать встроенные FDW или создавать собственные на Python.
10.10 Pgaudit
Pgaudit — расширение для PostgreSQL, которое позволяет собирать
события из различных источников внутри PostgreSQL и записывает их в
формате CSV c временной меткой, информацией о пользователе, объекте,
который был затронут командой (если такое произошло) и полным тек-
стом команды. Поддерживает все DDL, DML (включая SELECT) и прочие
команды. Данное расширение работает в PostgreSQL 9.3 и выше.
После установки расширения нужно добавит в конфиг PostgreSQL на-
стройки расширения:
Листинг 10.69 Pgaudit
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = pgaudi t
224
10.11. Ltree
-
- pga udi t . l o g = read , writ e , u s er
Далее перегрузить базу данных и установить расширение для базы:
Листинг 10.70 Pgaudit
Line 1 # CREATE EXTENSION pga udit ;
После этого в логах можно увидеть подобный результат от pgaudit:
Листинг 10.71 Pgaudit
Line 1 LOG: [AUDIT] , 2014 - 0 4 - 3 0 1 7 : 1 3: 5 5 . 20 2 8 54 + 0 9 , auditdb , ianb ,
ianb , DEFINITION ,CREATE TABLE,TABLE, p ub l i c . x ,CREATE TABLE
p u bl i c . x ( a pg_catalog . i n t 4 , b pg_catalog . i nt 4 )
WITH ( o id s=OFF)
- LOG: [AUDIT] , 2 014 - 04 - 30 17:1 4 : 0 6 . 5 48 9 2 3 +0 9 , auditdb , ianb ,
ianb ,WRITE, INSERT,TABLE, p ub l i c . x , INSERT INTO x VALUES
( 1 , 1) ;
- LOG: [AUDIT] , 2 014 - 04 - 30 17:1 4 : 2 1 . 2 21 8 7 9 +0 9 , auditdb , ianb ,
ianb ,READ,SELECT,TABLE, p u bl ic . x , SELECT * FROM x ;
- LOG: [AUDIT] , 2 014 - 04 - 30 17:1 5 : 2 5 . 6 20 2 1 3 +0 9 , auditdb , ianb ,
ianb ,READ,SELECT,VIEW, p ub li c . v_x ,SELECT * from v_x ;
5 LOG: [AUDIT] , 2 014 - 04 - 30 17:1 5 : 2 5 . 6 20 2 6 2 +0 9 , auditdb , ianb ,
ianb ,READ,SELECT,TABLE, p u bl ic . x , SELECT * from v_x ;
- LOG: [AUDIT] , 2 014 - 04 - 30 17:1 6 : 0 0 . 8 49 8 6 8 +0 9 , auditdb , ianb ,
ianb ,WRITE,UPDATE,TABLE, p u b l ic . x ,UPDATE x SET a=a+1;
- LOG: [AUDIT] , 2 014 - 04 - 30 17:1 6 : 1 8 . 2 91 4 5 2 +0 9 , auditdb , ianb ,
ianb ,ADMIN,VACUUM, , ,VACUUM x ;
- LOG: [AUDIT] , 2 014 - 04 - 30 17 : 1 8 : 0 1. 0 82 9 1+ 0 9 , auditdb , ianb , ianb
, DEFINITION ,CREATE FUNCTION,FUNCTION, p u b l ic . func_x ( ) ,
CREATE FUNCTION p u bl i c . func_x ( ) RETURNS pg_catalog . i n t 4
LANGUAGE s q l VOLATILE CALLED ON NULL INPUT SECURITY
INVOKER COST 10 0.00 0 000 AS $dprs_$SELECT a FROM x LIMIT
1 ; $dprs_$
Более подробную информацию про настройку расширения можно най-
ти в официальном README.
10.11 Ltree
Ltree расширение, которое позволяет хранить древовидные структу-
ры в виде меток, а также предоставляет широкие возможности поиска по
ним.
225
10.11. Ltree
Почему Ltree?
Реализация алгоритма Materialized Path (довольно быстра, как на
запись, так и на чтение);
Как правило данное решение будет быстрее, чем использование CTE
(Common Table Expressions) или рекурсивной функции (постоянно
будут пересчитываться ветвления);
Встроены механизмы поиска по дереву;
Индексы;
Установка и использование
Для начала активируем расширение для базы данных:
Листинг 10.72 Ltree
Line 1 # CREATE EXTENSION l t r e e ;
Далее создадим таблицу комментариев, которые будут храниться как
дерево:
Листинг 10.73 Ltree
Line 1 CREATE TABLE comments ( user_id i n t e ge r , d e s c r i p t i o n text ,
path l t r e e ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
1 , md5( random ( ) : : t ex t ) , 0001 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
2 , md5( random ( ) : : t ex t ) , 0 0 0 1 .0 001. 0 0 0 1 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
2 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 1 .0 0 0 1. 0 0 01 ) ;
5 INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
1 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 1 .0 0 0 1. 0 0 02 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
5 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 1 .0 0 0 1. 0 0 03 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
6 , md5( random ( ) : : t ex t ) , 0 0 01.00 0 2 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
6 , md5( random ( ) : : t ex t ) , 0 0 0 1 .0 002. 0 0 0 1 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
6 , md5( random ( ) : : t ex t ) , 0 0 01.00 0 3 ) ;
10 INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
8 , md5( random ( ) : : t ex t ) , 0 0 0 1 .0 003. 0 0 0 1 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
9 , md5( random ( ) : : t ex t ) , 0 0 0 1 .0 003. 0 0 0 2 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
11 , md5( random ( ) : : t e xt ) , 0 001 . 00 0 3 .0 0 02. 0 001 ) ;
226
10.11. Ltree
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
2 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 3 .0 0 0 2. 0 0 02 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
5 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 3 .0 0 0 2. 0 0 03 ) ;
15 INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
7 , md5( random ( ) : : t ex t ) , 0 00 1 .0 0 03 . 00 0 2. 0 0 0 2. 0 00 1 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
20 , md5( random ( ) : : t e xt ) , 0 00 1 .0 0 03 .0 0 02 . 00 0 2. 0 00 2 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
31 , md5( random ( ) : : t e xt ) , 0 00 1 .0 0 03 .0 0 02 . 00 0 2. 0 00 3 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
22 , md5( random ( ) : : t e xt ) , 0 00 1 .0 0 03 .0 0 02 . 00 0 2. 0 00 4 ) ;
- INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
34 , md5( random ( ) : : t e xt ) , 0 00 1 .0 0 03 .0 0 02 . 00 0 2. 0 00 5 ) ;
20 INSERT INTO comments ( user_id , d e s c r ip ti o n , path ) VALUES (
22 , md5( random ( ) : : t e xt ) , 0 00 1 .0 0 03 .0 0 02 . 00 0 2. 0 00 6 ) ;
Не забываем добавить индексы:
Листинг 10.74 Ltree
Line 1 # CREATE INDEX path_gist_comments_idx ON comments USING GIST
( path ) ;
- # CREATE INDEX path_comments_idx ON comments USING b tre e (
path ) ;
В данном примере я создаю таблицу comments с полем path, которое и
будет содержать полный путь к этому комментарию в дереве использую
4 цифры и точку для делителя узлов дерева). Для начала найдем все
комментарии, у которых путь начинается с 0001.0003:
Листинг 10.75 Ltree
Line 1 # SELECT user_id , path FROM comments WHERE path <@
000 1 .0003 ;
- user_id | path
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - -
- 6 | 0001. 0 003
5 8 | 0 00 1. 0003 . 0 0 01
- 9 | 0 00 1. 0003 . 0 0 02
- 11 | 0 001 . 00 0 3 .0 0 02. 0 00 1
- 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
- 5 | 0 0 01. 0 00 3 . 00 0 2.0 0 03
10 7 | 0 0 01 . 0 0 03 . 00 0 2. 0 00 2 .0 0 01
- 20 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 2
- 31 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 3
- 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 4
- 34 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 5
227
10.11. Ltree
15 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 6
- (12 rows )
И проверим как работают индексы:
Листинг 10.76 Ltree
Line 1 # SET enable _seqs can=f a l s e ;
- SET
- # EXPLAIN ANALYZE SELECT user_id , path FROM comments WHERE
path <@ 0001. 0 003 ;
-
QUERY PLAN
5 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Index Scan using path_gist_comments_idx on comments ( c os t
= 0 . 0 0 . . 8 . 2 9 rows=2 width =38) ( a ct u a l time = 0 . 0 2 3. .0 .0 3 4
rows=12 l o ops =1)
- Index Cond : ( path <@ 000 1 .0003 : : l t r e e )
- Total runtime : 0 . 07 6 ms
- (3 rows )
Данную выборку можно сделать другим запросом:
Листинг 10.77 Ltree
Line 1 # SELECT user_id , path FROM comments WHERE path ~
0 0 01. 0 00 3 . * ;
- user_id | path
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - -
- 6 | 0001. 0 003
5 8 | 0 00 1. 0003 . 0 0 01
- 9 | 0 00 1. 0003 . 0 0 02
- 11 | 0 001 . 00 0 3 .0 0 02. 0 00 1
- 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
- 5 | 0 0 01. 0 00 3 . 00 0 2.0 0 03
10 7 | 0 0 01 . 0 0 03 . 00 0 2. 0 00 2 .0 0 01
- 20 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 2
- 31 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 3
- 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 4
- 34 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 5
15 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 6
- (12 rows )
Не забываем про сортировку дерева:
Листинг 10.78 Ltree
228
10.11. Ltree
Line 1 # INSERT INTO comments ( user_id , d e s c r i p ti o n , path ) VALUES (
9 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 3 .0 0 0 1. 0 0 01 ) ;
- # INSERT INTO comments ( user_id , d e s c r i p ti o n , path ) VALUES (
9 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 3 .0 0 0 1. 0 0 02 ) ;
- # INSERT INTO comments ( user_id , d e s c r i p ti o n , path ) VALUES (
9 , md5( random ( ) : : t ex t ) , 0 0 01 . 000 3 .0 0 0 1. 0 0 03 ) ;
- # SELECT user_id , path FROM comments WHERE path ~
0 0 01. 0 00 3 . * ;
5 user_id | path
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - -
- 6 | 0001. 0 003
- 8 | 0 00 1. 0003 . 0 0 01
- 9 | 0 00 1. 0003 . 0 0 02
10 11 | 0 001 . 00 0 3 .0 0 02. 0 00 1
- 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
- 5 | 0 0 01. 0 00 3 . 00 0 2.0 0 03
- 7 | 0 0 01 . 0 0 03 . 00 0 2. 0 00 2 .0 0 01
- 20 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 2
15 31 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 3
- 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 4
- 34 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 5
- 22 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 6
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 01
20 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 02
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 03
- (15 rows )
- # SELECT user_id , path FROM comments WHERE path ~
0 0 01. 0 00 3 . * ORDER by path ;
- user_id | path
25 - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - -
- 6 | 0001. 0 003
- 8 | 0 00 1. 0003 . 0 0 01
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 01
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 02
30 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 03
- 9 | 0 00 1. 0003 . 0 0 02
- 11 | 0 001 . 00 0 3 .0 0 02. 0 00 1
- 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
- 7 | 0 0 01 . 0 0 03 . 00 0 2. 0 00 2 .0 0 01
35 20 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 2
- 31 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 3
- 22 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 4
- 34 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 5
- 22 | 0 00 1 .0 0 0 3 .0 0 02 . 00 0 2. 0 00 6
40 5 | 0 0 01. 0 00 3 . 00 0 2.0 0 03
- (15 rows )
Для поиска можно использовать разные модификаторы. Пример ис-
229
10.11. Ltree
пользования «или» (|):
Листинг 10.79 Ltree
Line 1 # SELECT user_id , path FROM comments WHERE path ~
0 0 0 1 . * { 1 , 2} .0 00 1 | 0 0 0 2 . * ORDER by path ;
- user_id | path
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - -
- 2 | 0 00 1. 0001 . 0 0 01
5 2 | 0 0 01. 0 00 1 . 00 0 1.0 0 01
- 1 | 0 0 01. 0 00 1 . 00 0 1.0 0 02
- 5 | 0 0 01. 0 00 1 . 00 0 1.0 0 03
- 6 | 0 00 1. 0002 . 0 0 01
- 8 | 0 00 1. 0003 . 0 0 01
10 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 01
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 02
- 9 | 0 0 01. 0 00 3 . 00 0 1.0 0 03
- 9 | 0 00 1. 0003 . 0 0 02
- 11 | 0 001 . 00 0 3 .0 0 02. 0 00 1
15 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
- 7 | 0 0 01 . 0 0 03 . 00 0 2. 0 00 2 .0 0 01
- 20 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 2
- 31 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 3
- 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 4
20 34 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 5
- 22 | 0 00 1 . 0 0 0 3 .0 0 02 . 00 0 2. 0 00 6
- 5 | 0 0 01. 0 00 3 . 00 0 2.0 0 03
- (19 rows )
Например, найдем прямых потомков от 0001.0003:
Листинг 10.80 Ltree
Line 1 # SELECT user_id , path FROM comments WHERE path ~
00 0 1 . 00 03.* { 1 } ORDER by path ;
- user_id | path
- - - - - - - - - -+ - - - - - - - - - - - - - - - -
- 8 | 0 00 1. 0003 . 0 0 01
5 9 | 0 00 1. 0003 . 0 0 02
- (2 rows )
Можно также найти родителя для потомка «0001.0003.0002.0002.0005»:
Листинг 10.81 Ltree
Line 1 # SELECT user_id , path FROM comments WHERE path = subpath (
0 0 01 . 00 0 3. 0 00 2 .0 0 0 2 .0 0 05 , 0 , - 1 ) ORDER by path ;
- user_id | path
230
10.12. PostPic
- - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - -
- 2 | 0 0 01. 0 00 3 . 00 0 2.0 0 02
5 (1 row )
Заключение
Ltree расширение, которое позволяет хранить и удобно управлять
Materialized Path в PostgreSQL.
10.12 PostPic
PostPic - расширение для PostgreSQL, которое позволяет обрабатывать
изображения в базе данных, как PostGIS делает это с пространственными
данными. Он добавляет новый типа поля image, а также несколько функ-
ций для обработки изображений (обрезка краев, создание миниатюр, по-
ворот и т. д.) и извлечений его атрибутов (размер, тип, разрешение). Более
подробно о возможностях расширения можно ознакомиться на официаль-
ной странице.
10.13 Fuzzystrmatch
Fuzzystrmatch расширение предоставляет несколько функций для опре-
деления сходства и расстояния между строками. Функция soundex исполь-
зуется для согласования сходно звучащих имен путем преобразования их
в одинаковый код. Функция difference преобразует две строки в soundex
код, а затем сообщает количество совпадающих позиций кода. В soundex
код состоит из четырех символов, поэтому результат будет от нуля до че-
тырех: 0 не совпадают, 4 точное совпадение (таким образом, функция
названа неверно как название лучше подходит similarity):
Листинг 10.82 soundex
Line 1 # CREATE EXTENSION f u z z y s t r m atch ;
- CREATE EXTENSION
- # SELECT soundex ( h e l l o world ! ) ;
- soundex
5 - - - - - - - - -
- H464
- (1 row )
-
- # SELECT soundex ( Anne ) , soundex ( Ann ) , d i f f e r e n c e ( Anne ,
Ann ) ;
10 soundex | soundex | d i f f e r e n c e
- - - - - - - - - -+ - - - - - - - - -+ - - - - - - - - - - - -
231
10.13. Fuzzystrmatch
- A500 | A500 | 4
- (1 row )
-
15 # SELECT soundex ( Anne ) , soundex ( Andrew ) , d i f f e r e n c e (
Anne , Andrew ) ;
- soundex | soundex | d i f f e r e n c e
- - - - - - - - - -+ - - - - - - - - -+ - - - - - - - - - - - -
- A500 | A536 | 2
- (1 row )
20
- # SELECT soundex ( Anne ) , soundex ( Margaret ) , d i f f e r e n c e (
Anne , Margaret ) ;
- soundex | soundex | d i f f e r e n c e
- - - - - - - - - -+ - - - - - - - - -+ - - - - - - - - - - - -
- A500 | M626 | 0
25 (1 row )
-
- # CREATE TABLE s (nm t ex t ) ;
- CREATE TABLE
- # INSERT INTO s VALUES ( john ) , ( joan ) , ( wobbly ) , (
ja c k ) ;
30 INSERT 0 4
- # SELECT * FROM s WHERE soundex (nm) = soundex ( john ) ;
- nm
- - - - - - -
- john
35 joan
- (2 rows )
-
- # SELECT * FROM s WHERE d i f f e r e n c e ( s .nm, john ) > 2;
- nm
40 - - - - - -
- john
- joan
- j a c k
- (3 rows )
Функция levenshtein вычисляет расстояние Левенштейна между двумя
строками. levenshtein_less_equal ускоряется функцию levenshtein для ма-
леньких значений расстояния:
Листинг 10.83 levenshtein
Line 1 # SELECT l e v e n s h t e i n ( GUMBO , GAMBOL ) ;
- l e v e n s h t e i n
- - - - - - - - - - - - - -
- 2
5 (1 row )
232
10.13. Fuzzystrmatch
-
- # SELECT l e v e n s h t e i n ( GUMBO , GAMBOL , 2 , 1 , 1) ;
- l e v e n s h t e i n
- - - - - - - - - - - - - -
10 3
- (1 row )
-
- # SELECT l ev e nsh t ei n _le s s_ e q ua l ( e x t e ns iv e , e xha u s tiv e ,
2) ;
- l eve n sh t e in _ les s _e q ual
15 - - - - - - - - - - - - - - - - - - - - - - - -
- 3
- (1 row )
-
- t e s t=# SELECT l e ven s ht e i n_ l es s _ eq u al ( e x t e ns i v e ,
ex h aus t i ve , 4) ;
20 l e ve n sh t e in _ les s _e q ual
- - - - - - - - - - - - - - - - - - - - - - - - -
- 4
- (1 row )
Функция metaphone, как и soundex, построена на идее создания кода
для строки: две строки, которые будут считаться похожими, будут иметь
одинаковые коды. Последним параметром указывается максимальная дли-
на metaphone кода. Функция dmetaphone вычисляет два «как звучит» кода
для строки «первичный» и «альтернативный»:
Листинг 10.84 metaphone
Line 1 # SELECT metaphone ( GUMBO , 4) ;
- metaphone
- - - - - - - - - - - -
- KM
5 (1 row )
- # SELECT dmetaphone ( p o s t g r e s q l ) ;
- dmetaphone
- - - - - - - - - - - - -
- PSTK
10 (1 row )
-
- # SELECT dmetaphone_alt ( p o s t g r e s q l ) ;
- dmetaphone_alt
- - - - - - - - - - - - - - - - -
15 PSTK
- (1 row )
233
10.14. Pg_trgm
10.14 Pg_trgm
Автодополнение — функция в программах, предусматривающих ин-
терактивный ввод текста по дополнению текста по введённой его части.
Реализуется это простым LIKE ’some%’ запросом в базу, где «some» то,
что пользователь успел ввести в поле ввода. Проблема в том, что в огром-
ной таблице такой запрос будет работать очень медленно. Для ускорения
запроса типа LIKE ’bla%’ можно использовать text_pattern_ops для text
поля или varchar_pattern_ops для varchar поля класс операторов в опреде-
лении индекса (данные типы индексов не будут работать для стандартных
операторов <, <=, =>, > и для работы с ними придется создать обычный
btree индекс).
Листинг 10.85 text_pattern_ops
Line 1 # c r e a t e t a b l e ta g s (
- # tag t ext primary key ,
- # name t e xt not n u l l ,
- # shortname t ext ,
5 # s t a t u s char d e f a u l t S ,
- #
- # check ( s t a t u s i n ( S , R ) )
- # ) ;
- NOTICE: CREATE TABLE / PRIMARY KEY w i l l c r e a t e i m p l i c i t
in dex " tags_pkey" f o r t a b l e " ta g s "
10 CREATE TABLE
-
- # CREATE INDEX i_tag ON t a g s USING bt r ee ( low er ( tag )
text_pattern_ops ) ;
- CREATE INDEX
-
15 # EXPLAIN ANALYZE s e l e c t * from t a g s where l ower ( tag ) LIKE
low er ( 0146% ) ;
- QUERY
PLAN
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Bitmap Heap Scan on tag s ( c os t = 5 . 4 9 . . 9 7 . 7 5 rows=121 width
=26) ( a c t u al time = 0 . 0 2 5 . . 0 . 02 5 rows=1 lo o ps =1)
- F i l t e r : ( lower ( tag ) ~~ 0146% : : t e x t )
20 -> Bitmap Index Scan on i_tag ( c o s t = 0 . 0 0 . . 5 . 4 6 rows=120
width =0) ( a ct u a l time = 0 . 0 1 6 . .0 .0 1 6 rows=1 lo o ps =1)
- Index Cond : ( ( l ower ( tag ) ~>=~ 0146 : : t e xt ) AND (
low er ( tag ) ~<~ 0147 : : t ex t ) )
- Total runtime : 0 . 05 0 ms
- (5 rows )
234
10.14. Pg_trgm
Для более сложных вариантов поиска, таких как LIKE ’%some%’ или
LIKE ’so%me%’ такой индекс не будет работать, но эту проблему можно
решить через расширение.
Pg_trgm PostgreSQL расширение, которое предоставляет функции и
операторы для определения схожести алфавитно-цифровых строк на ос-
нове триграмм, а также классы операторов индексов, поддерживающие
быстрый поиск схожих строк. Триграмма это группа трёх последо-
вательных символов, взятых из строки. Можно измерить схожесть двух
строк, подсчитав число триграмм, которые есть в обеих. Эта простая идея
оказывается очень эффективной для измерения схожести слов на многих
естественных языках. Модуль pg_trgm предоставляет классы операторов
индексов GiST и GIN, позволяющие создавать индекс по текстовым колон-
кам для очень быстрого поиска по критерию схожести. Эти типы индексов
поддерживают % и <-> операторы схожести и дополнительно поддержи-
вают поиск на основе триграмм для запросов с LIKE, ILIKE, ~ и ~* (эти
индексы не поддерживают простые операторы сравнения и равенства, так
что может понадобиться и обычный btree индекс).
Листинг 10.86 pg_trgm
Line 1 # CREATE TABLE test_trgm ( t t ex t ) ;
- # CREATE INDEX trgm_idx ON test_trgm USING g i s t ( t
gist_trgm_ops ) ;
- - - or
- # CREATE INDEX trgm_idx ON test_trgm USING gi n ( t
gin_trgm_ops ) ;
После создания GIST или GIN индекса по колонке t можно осуществ-
лять поиск по схожести. Пример запроса:
Листинг 10.87 pg_trgm
Line 1 SELECT t , s i m i l a r i t y ( t , word ) AS sml
- FROM test_trgm
- WHERE t % word
- ORDER BY sml DESC, t ;
Он выдаст все значения в текстовой колонке, которые достаточно схо-
жи со словом word, в порядке сортировки от наиболее к наименее схожим.
Другой вариант предыдущего запроса (может быть довольно эффективно
выполнен с применением индексов GiST, а не GIN):
Листинг 10.88 pg_trgm
Line 1 SELECT t , t <-> word AS d i s t
- FROM test_trgm
- ORDER BY d i s t LIMIT 1 0 ;
235
10.15. Cstore_fdw
Начиная с PostgreSQL 9.1, эти типы индексов также поддерживают
поиск с операторами LIKE и ILIKE, например:
Листинг 10.89 pg_trgm
Line 1 SELECT * FROM test_trgm WHERE t LIKE ’%f o o%bar ;
Начиная с PostgreSQL 9.3, индексы этих типов также поддерживают
поиск по регулярным выражениям (операторы ~ и ~*), например:
Листинг 10.90 pg_trgm
Line 1 SELECT * FROM test_trgm WHERE t ~ ( f o o | bar ) ;
Относительно поиска по регулярному выражению или с LIKE, нужно
принимать в расчет, что при отсутствии триграмм в искомом шаблоне по-
иск сводится к полному сканирования индекса. Выбор между индексами
GiST и GIN зависит от относительных характеристик производительности
GiST и GIN, которые здесь не рассматриваются. Как правило, индекс GIN
быстрее индекса GiST при поиске, но строится или обновляется он мед-
леннее; поэтому GIN лучше подходит для статических, а GiST для часто
изменяемых данных.
10.15 Cstore_fdw
Cstore_fdw расширение реализует модель хранения данных на базе се-
мейства столбцов (column-oriented systems) для PostgreSQL (колоночное
хранение данных). Такое хранение данных обеспечивает заметные пре-
имущества для аналитических задач (OLAP, data warehouse), поскольку
требуется считывать меньше данных с диска (благодаря формату хра-
нения и компрессии). Расширение использует Optimized Row Columnar
(ORC) формат для размещения данных на диске, который имеет следую-
щие преимущества:
Уменьшение жатие) размера данных в памяти и на диске в 2-4 раза.
Можно добавить в расширение другой кодек для сжатия (алгоритм
Лемпеля-Зива, LZ присутствует в расширении);
Считывание с диска только тех данных, которые требуются. Повы-
шается производительность по I/O диска для других запросов;
Хранение минимального/максимального значений для групп полей
(skip index, индекс с пропусками), что помогает пропустить не тре-
буемые данные на диске при выборке;
236
10.15. Cstore_fdw
Установка и использование
Для работы cstore_fdw требуется protobuf-c для сериализации и десери-
ализации данных. Далее требуется добавить в postgresql .conf расширение:
Листинг 10.91 Cstore_fdw
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = cstore_fdw
И активировать его для базы:
Листинг 10.92 Cstore_fdw
Line 1 # CREATE EXTENSION cstore_fdw ;
Для загрузки данных в cstore таблицы существует два варианта:
Использование команды COPY для загрузки или добавления данных
из файлов или STDIN;
Использование конструкции INSERT INTO cstore_table SELECT ...
для загрузки или добавления данных из другой таблицы;
Cstore таблицы не поддерживают INSERT (кроме выше упомянутого
INSERT INTO ... SELECT), UPDATE или DELETE команды.
Для примера загрузим тестовые данные:
Листинг 10.93 Cstore_fdw
Line 1 $ wget http : / / examples . c i t u sd a t a . com/ customer_reviews_1998 .
cs v . gz
- $ wget http : // examples . c it u s d a t a . com/ customer_reviews_1999 .
cs v . gz
-
- $ gzi p -d customer_reviews_1998 . csv . gz
5 $ gzi p -d customer_reviews_1999 . csv . gz
Далее загрузим эти данные в cstore таблицу (расширение уже активи-
ровано для PostgreSQL):
Листинг 10.94 Cstore таблицы
Line 1 - - c r e a te s e r v e r o b j e c t
- CREATE SERVER cs t or e_ s er ve r FOREIGN DATA WRAPPER cstore_fdw ;
-
- - - c r e a t e f o r e i g n t a b l e
5 CREATE FOREIGN TABLE customer_reviews
- (
- customer_id TEXT,
237
10.15. Cstore_fdw
- review_date DATE,
- re v i e w_rating INTEGER,
10 review_vot es INTEGER,
- re v i e w_helpful _ v o t es INTEGER,
- product_id CHAR( 1 0 ) ,
- p ro d uc t _ t it l e TEXT,
- product_sales_rank BIGINT,
15 product_group TEXT,
- product_category TEXT,
- product_subcategory TEXT,
- s i m i lar_pro d u ct_ids CHAR( 1 0) [ ]
- )
20 SERVER cs t o r e_ s e r ve r
- OPTIONS( com pressio n p g l z ) ;
-
- COPY customer_reviews FROM /tmp/ customer_reviews_1998 . csv
WITH CSV ;
- COPY customer_reviews FROM /tmp/ customer_reviews_1999 . csv
WITH CSV ;
25
- ANALYZE customer_reviews ;
После этого можно проверить как работает расширение:
Листинг 10.95 Cstore запросы
Line 1 - - Find a l l r evi e w s a p a r t i c u l a r customer made on the Dune
s e r i e s i n 1 9 9 8.
- # SELECT
- customer_id , review_date , re vie w_r ati ng , product_id ,
p r od u c t _t i tl e
- FROM
5 customer_reviews
- WHERE
- customer_id = A27T7HVDXA3K2A AND
- p ro d uc t _ t it l e LIKE ’%Dune% AND
- review_date >= 1998 -01 -01 AND
10 review_date <= 1998 -12 -31 ;
- customer_id | review_date | review_r a t i ng | product_id |
p r od u c t _t i tl e
- - -
- - - - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- A27T7HVDXA3K2A | 1998 -04 -10 | 5 | 0399128964 |
Dune ( Dune C hr o ni c le s ( Econo - Clad Hardcover ) )
- A27T7HVDXA3K2A | 1998 -04 -10 | 5 | 044100590X |
Dune
238
10.16. Postgresql-hll
15 A27T7HVDXA3K2A | 1998 -04 -10 | 5 | 0441172717 |
Dune ( Dune Ch r o n i c le s , Book 1)
- A27T7HVDXA3K2A | 1998 -04 -10 | 5 | 0881036366 |
Dune ( Dune C hr o ni c le s ( Econo - Clad Hardcover ) )
- A27T7HVDXA3K2A | 1998 -04 -10 | 5 | 1559949570 |
Dune Audio C o l l e c t i o n
- (5 rows )
-
20 Time : 238.626 ms
-
- - - Do we have a c o r r e l a t i o n between a book s t i t l e ’ s l en gth
and i t s revie w r a t i n g s ?
- # SELECT
- width_bucket ( l e ngt h ( p ro d uc t _t i t l e ) , 1 , 50 , 5)
title_ l e n g t h _ bucket ,
25 round ( avg ( review_ r a t ing ) , 2) AS review_average ,
- count ( *)
- FROM
- customer_reviews
- WHERE
30 product_group = Book
- GROUP BY
- tit l e _ le n gt h _b uc k et
- ORDER BY
- tit l e _ le n gt h _b uc k et ;
35 ti t l e _l e ng t h_ bu c ke t | review_average | count
- - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - -
- 1 | 4. 2 6 | 139034
- 2 | 4. 2 4 | 411318
- 3 | 4. 3 4 | 245671
40 4 | 4. 3 2 | 167361
- 5 | 4. 3 0 | 118422
- 6 | 4. 4 0 | 116412
- (6 rows )
-
45 Time : 1285. 0 5 9 ms
Заключение
Более подробно об использовании расширения можно ознакомиться че-
рез официальную документацию.
10.16 Postgresql-hll
На сегодняшний день широко распространена задача подсчета количе-
ства уникальных элементов (count-distinct problem) в потоке данных, кото-
239
10.16. Postgresql-hll
рые могут содержать повторяющиеся элементы. Например, сколько уни-
кальных IP-адресов подключалось к серверу за последний час? Сколько
различных слов в большом куске текстов? Какое количество уникальных
посетителей побывало на популярном сайте за день? Сколько уникаль-
ных URL было запрошено через прокси-сервер? Данную задачу можно
решить «в лоб»: пройтись по всем элементам и убрать дубликаты, после
этого посчитать их количество (например использовать множество, set).
Трудности в таком подходе возникают при увеличении масштаба. С мини-
мальными затратами можно подсчитать тысячу или даже миллион уни-
кальных посетителей, IP-адресов, URL или слов. А что если речь идет о
100 миллионах уникальных элементов на один сервер при наличии тысяч
серверов? Теперь это уже становится интересным.
Текущее решение проблемы будет выглядеть так: необходимо сформи-
ровать множества (set) уникальных элементов для каждого из 1000 серве-
ров, каждое из которых может содержать около 100 миллионов уникаль-
ных элементов, а затем подсчитать количество уникальных элементов в
объединении этих множеств. Другими словами, мы имеем дело с распре-
деленным вариантом задачи подсчета уникальных элементов. Хоть это
решение является вполне логичным, на практике этот подход обойдется
высокой ценой. Для примера возьмем URL, средняя длина которого со-
ставляет 76 символов. В нашем случае один сервер обслуживает около
100 миллионов уникальных URL, следовательно, размер файла с их пе-
речнем составит около 7.6 ГБ. Даже если каждый URL преобразовать в
64-битный хеш, размер файла составит 800 МБ. Это намного лучше, но
не забывайте, что речь идет о 1000 серверов. Каждый сервер отправляет
файл с перечнем уникальных URL на центральный сервер, следовательно,
при наличии 1000 серверов функция объединения множеств должна обра-
ботать 800 ГБ данных. Если такая операция должна выполняться часто,
тогда необходимо будет либо установить систему для обработки больших
данных нанять команду для ее обслуживания), либо найти другое ре-
шение.
И вот на сцену выходит HyperLogLog алгоритм. Этот алгоритм реа-
лизует вероятностный подход к задаче подсчета уникальных элементов и
базируется на двух следующих положениях:
вероятность того, что любой данный бит двоичного представления
случайного числа равен единице, составляет 50%;
вероятность того, что совместно произойдут два независимых слу-
чайных события 𝐴 и 𝐵, вычисляется по формуле 𝑃 (𝐴) * 𝑃 (𝐵). Та-
ким образом, если вероятность равенства единице одного любого би-
та случайного числа составляет 50%, тогда вероятность равенства
единице двух любых битов составляет 25%, трех 12,5% и т.д;
Вспомним еще одно базовое положение теории вероятностей, согласно
которому ожидаемое количество испытаний, необходимое для наступле-
240
10.16. Postgresql-hll
ния события, вычисляется по формуле 1/𝑃 (𝑒𝑣𝑒𝑛𝑡). Следовательно, если
𝑃 (𝑜𝑛𝑒 𝑠𝑝𝑒𝑐𝑖𝑓𝑖𝑐 𝑏𝑖𝑡 𝑠𝑒𝑡) = 50%, то ожидаемое количество испытаний равно
2. Для двух битов 4, для трех битов 8 и т. д.
В общем случае входные значения не являются равномерно распреде-
ленными случайными числами, поэтому необходим способ преобразования
входных значений к равномерному распределению, т. е. необходима хеш-
функция. Обратите внимание, в некоторых случаях распределение, полу-
чаемое на выходе хеш-функции, не оказывает существенное влияние на
точность системы. Однако HyperLogLog очень чувствителен в этом отно-
шении. Если выход хеш-функции не соответствует равномерному распре-
делению, алгоритм теряет точность, поскольку не выполняются базовые
допущения, лежащие в его основе.
Рассмотрим алгоритм подробно. Вначале необходимо хешировать все
элементы исследуемого набора. Затем нужно подсчитать количество по-
следовательных начальных битов, равных единице, в двоичном представ-
лении каждого хеша и определить максимальное значение этого количе-
ства среди всех хешей. Если максимальное количество единиц обозначить
𝑛, тогда количество уникальных элементов в наборе можно оценить, как
2
𝑛
. То есть, если максимум один начальный бит равен единице, тогда ко-
личество уникальных элементов, в среднем, равно 2; если максимум три
начальных бита равны единице, в среднем, мы можем ожидать 8 уникаль-
ных элементов и т. д.
Подход, направленный на повышение точности оценки и являющийся
одной из ключевых идей HyperLogLog, заключается в следующем: раз-
деляем хеши на подгруппы на основании их конечных битов, определя-
ем максимальное количество начальных единиц в каждой подгруппе, а
затем находим среднее. Этот подход позволяет получить намного более
точную оценку общего количества уникальных элементов. Если мы име-
ем 𝑚 подгрупп и 𝑛 уникальных элементов, тогда, в среднем, в каждой
подгруппе будет 𝑛/𝑚 уникальных элементов. Таким образом, нахождение
среднего по всем подгруппам дает достаточно точную оценку величины
𝑙𝑜𝑔
2
(𝑛/𝑚), а отсюда легко можно получить необходимое нам значение. Бо-
лее того, HyperLogLog позволяет обрабатывать по отдельности различные
варианты группировок, а затем на основе этих данных находить итоговую
оценку. Следует отметить, что для нахождения среднего HyperLogLog ис-
пользует среднее гармоническое, которое обеспечивает лучшие результаты
по сравнению со средним арифметическим (более подробную информа-
цию можно найти в оригинальных публикациях, посвященных LogLog и
HyperLogLog).
Вернемся к задаче. По условию существует 1000 серверов и 100 миллио-
нов уникальных URL на каждый сервер, следовательно, центральный сер-
вер должен обрабатывать 800 ГБ данных при каждом выполнении просто-
го варианта алгоритма. Это также означает, что 800 ГБ данных каждый
раз необходимо передавать по сети. HyperLogLog меняет ситуацию карди-
241
10.16. Postgresql-hll
нально. Согласно анализу, проведенному авторами оригинальной публи-
кации, HyperLogLog обеспечивает точность около 98% при использовании
всего 1.5 КБ памяти. Каждый сервер формирует соответствующий файл
размером 1.5 КБ, а затем отправляет его на центральный сервер. При
наличии 1000 серверов, центральный сервер обрабатывает всего 1.5 МБ
данных при каждом выполнении алгоритма. Другими словами, обрабаты-
вается лишь 0.0002% данных по сравнению с предыдущим решением. Это
полностью меняет экономический аспект задачи. Благодаря HyperLogLog,
возможно выполнять такие операции чаще и в большем количестве. И все
это ценой всего лишь 2% погрешности.
Для работы с этим алгоритмом внутри PostgreSQL было создано рас-
ширение postgresql-hll. Оно добавляет новый тип поля hll , который пред-
ставляет собой HyperLogLog структуру данных. Рассмотрим пример его
использования.
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.96 Инициализация hll
Line 1 # CREATE EXTENSION h l l ;
Давайте предположим, что есть таблица users_visits, которая записы-
вает визиты пользователей на сайт, что они сделали и откуда они пришли.
В таблице сотни миллионов строк.
Листинг 10.97 users_visits
Line 1 CREATE TABLE u s e r s _ v i s i t s (
- date date ,
- user_id i nt e ge r ,
- act i v it y _ typ e s m al l in t ,
5 r e f e r r e r v archa r (255 )
- ) ;
Требуется получать очень быстро представление о том, сколько уни-
кальных пользователей посещают сайт в день на админ панели. Для этого
создадим агрегатную таблицу:
Листинг 10.98 daily_uniques
Line 1 CREATE TABLE dai l y_uniq u es (
- date date UNIQUE,
- u s e r s h l l
- ) ;
5
242
10.16. Postgresql-hll
- - - F i l l i t with the aggregated unique s t a t i s t i c s
- INSERT INTO da i ly_un i ques ( date , u s e r s )
- SELECT date , hll_add_agg ( hll_ha s h _integ e r ( user_id ) )
- FROM u s e r s _ v i s i t s
10 GROUP BY 1 ;
Далее хэшируется user_id и собираются эти хэш-значения в один hll за
день. Теперь можно запросить информацию по уникальным пользовате-
лям за каждый день:
Листинг 10.99 daily_uniques по дням
Line 1 # SELECT date , h l l _ c a r d i n a l i t y ( u s e r s ) FROM d aily_ u niques ;
- date | h l l _ c a r d i n a l i t y
- - - - - - - - - - - - - + - - - - - - - - - - - - - - - - -
- 2017 -02 -21 | 23123
5 2017 -02 -22 | 59433
- 2017 -02 -23 | 2134890
- 2017 -02 -24 | 3276247
- (4 rows )
Можно возразить, что такую задачу можно решить и через COUNT
DISTINCT и это будет верно. Но в примере только ответили на вопрос:
«Сколько уникальных пользователей посещает сайт каждый день?». А
что, если требуется знать сколько уникальных пользователей посетили
сайт за неделю?
Листинг 10.100 daily_uniques за неделю
Line 1 SELECT h l l _ c a r d i n a l i t y ( hll_union_agg ( u s e r s ) ) FROM
dai l y_uni q ues WHERE date >= 2017 -02 -20 : : date AND date
<= 2017 -02 -26 : : date ;
Или выбрать уникальных пользователей за каждый месяц в течение
года?
Листинг 10.101 daily_uniques за каждый месяц
Line 1 SELECT EXTRACT(MONTH FROM date ) AS month , h l l _ c a r d i n a l i t y (
hll_union_agg ( u s e r s ) )
- FROM dai l y_uni q ues
- WHERE date >= 2016 -01 -01 AND
- date < 2017 -01 -01
5 GROUP BY 1 ;
Или узнать количество пользователей, что посетили сайт вчера, но не
сегодня?
243
10.17. Tsearch2
Листинг 10.102 daily_uniques за вчера но не сегодня
Line 1 SELECT date , (#hll_union_agg ( u s e rs ) OVER two_days ) - #u s e r s
AS lost_uni q u e s
- FROM dai l y_uni q ues
- WINDOW two_days AS (ORDER BY date ASC ROWS 1 PRECEDING) ;
Это всего пара примеров типов запросов, которые будут возвращать
результат в течение миллисекунд благодаря hll , но потребует либо пол-
ностью отдельные предварительно созданные агрегирующие таблицы или
self join/generate_series фокусы в COUNT DISTINCT мире.
Заключение
Более подробно об использовании расширения можно ознакомиться че-
рез официальную документацию.
10.17 Tsearch2
Как и многие современные СУБД, PostgreSQL имеет встроенный ме-
ханизм полнотекстового поиска. Отметим, что операторы поиска по тек-
стовым данных существовали очень давно, это операторы LIKE, ILIKE, ~,
~*. Однако, они не годились для эффективного полнотекстового поиска,
так как:
У них не было лингвистической поддержки, например, при поиске
слова satisfies будут не найдены документы со словом satisfy и ни-
какими регулярными выражениями этому не помочь. В принципе,
используя OR и все формы слова, можно найти все необходимые до-
кументы, но это очень неэффективно, так как в некоторых языках
могут быть слова со многими тысячами форм!;
Они не предоставляют никакой информации для ранжирования (сор-
тировки) документов, что делает такой поиск практически бесполез-
ным, если только не существует другой сортировки или в случае
малого количества найденных документов;
Они, в целом, очень медленные из-за того, что они каждый раз про-
сматривают весь документ и не имеют индексной поддержки;
Для улучшения ситуации Олег Бартунов и Федор Сигаев предложили
и реализовали новый полнотекстовый поиск, существовавший как модуль
расширения и интегрированный в PostgreSQL, начиная с версии 8.3 —
Tsearch2.
Идея нового поиска состояла в том, чтобы затратить время на обработ-
ку документа один раз и сохранить время при поиске, использовать специ-
альные программы-словари для нормализации слов, чтобы не заботиться,
например, о формах слов, учитывать информацию о важности различных
244
10.17. Tsearch2
атрибутов документа и положения слова из запроса в документе для ран-
жирования найденных документов. Для этого, требовалось создать новые
типы данных, соответствующие документу и запросу, и полнотекстовый
оператор для сравнения документа и запроса, который возвращает TRUE,
если запрос удовлетворяет запросу, и в противном случае - FALSE.
PostgreSQL предоставляет возможность как для создания новых типов
данных, операторов, так и создания индексной поддержки для доступа к
ним, причем с поддержкой конкурентности и восстановления после сбоев.
Однако, надо понимать, что индексы нужны только для ускорения поиска,
сам поиск обязан работать и без них. Таким образом, были созданы но-
вые типы данных - tsvector, который является хранилищем для лексем из
документа, оптимизированного для поиска, и tsquery - для запроса с под-
держкой логических операций, полнотекстовый оператор «две собаки» @@
и индексная поддержка для него с использованием GiST и GIN. tsvector
помимо самих лексем может хранить информацию о положении лексемы
в документе и ее весе (важности), которая потом может использоваться
для вычисления ранжирующей информации.
Установка и использование
Для начала активируем расширение:
Листинг 10.103 Активация tsearch2
Line 1 # CREATE EXTENSION ts e ar c h2 ;
Проверим его работу:
Листинг 10.104 Проверка tsearch2
Line 1 # SELECT This i s t e s t s t r i n g : : t s v e c t o r ;
- t s v e c t o r
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- This i s s t r i n g t e s t
5 (1 row )
-
- # SELECT s t r i p ( t o _ t s ve ct o r ( The a i r s m e l l s o f sea water . ) ) ;
- s t r i p
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 a i r sea s me l l water
- (1 row )
Заключение
Данное расширение заслуживает отдельной книги, поэтому лучше
ознакомиться с ним подробнее в «Введение в полнотекстовый поиск в
245
10.18. PL/Proxy
PostgreSQL» документе.
10.18 PL/Proxy
PL/Proxy представляет собой прокси-язык для удаленного вызова про-
цедур и партицирования данных между разными базами (шардинг). По-
дробнее можно почитать в «6.2 PL/Proxy» главе.
10.19 Texcaller
Texcaller это удобный интерфейс для командной строки TeX, кото-
рый обрабатывает все виды ошибок. Он написан в простом C, довольно
портативный и не имеет внешних зависимостей, кроме TeX. Неверный TeX
документ обрабатывается путем простого возвращения NULL, а не преры-
вается с ошибкой. В случае неудачи, а также в случае успеха, дополни-
тельная обработка информации осуществляется через NOTICEs.
10.20 Pgmemcache
Pgmemcache это PostgreSQL API библиотека на основе libmemcached
для взаимодействия с memcached. С помощью данной библиотеки
PostgreSQL может записывать, считывать, искать и удалять данные из
memcached. Подробнее можно почитать в «9.2 Pgmemcache» главе.
10.21 Prefix
Prefix реализует поиск текста по префиксу (prefix @> text). Prefix ис-
пользуется в приложениях телефонии, где маршрутизация вызовов и рас-
ходы зависят от вызывающего/вызываемого префикса телефонного номе-
ра оператора.
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.105 Инициализация prefix
Line 1 # CREATE EXTENSION p r e f i x ;
После этого можем проверить, что расширение функционирует:
246
10.21. Prefix
Листинг 10.106 Проверка prefix
Line 1 # s e l e c t 123 : : p r e f i x _ r a n g e @> 123456 ;
- ? column?
- - - - - - - - - - -
- t
5 (1 row )
-
- # s e l e c t a , b , a | b as union , a & b as i n t e r s e c t
- from ( s e l e c t a : : pr efix _range , b : : prefix_range
- from ( v a lue s ( 123 , 123 ) ,
10 ( 123 , 124 ) ,
- ( 123 , 1 2 3 [ 4 - 5 ] ) ,
- ( 1 2 3 [ 4 - 5] , 1 2 3 [ 2 - 7 ] ) ,
- ( 123 , [ 2 - 3 ] ) ) as t ( a , b )
- ) as x ;
15
- a | b | union | i n t e r s e c t
- - - - - - - - - - - + - - - - - - - - - -+ - - - - - - - - - - + - - - - - - - - - - -
- 123 | 123 | 123 | 123
- 123 | 124 | 1 2 [ 3 - 4 ] |
20 123 | 1 2 3[ 4 - 5 ] | 123 | 1 2 3[ 4 - 5 ]
- 1 23 [ 4 - 5 ] | 1 2 3 [ 2 - 7 ] | 1 2 3 [ 2 - 7] | 1 2 3 [ 4 - 5 ]
- 123 | [ 2 - 3 ] | [ 1 - 3 ] |
- (5 rows )
В примере 10.107 производится поиск мобильного оператора по номеру
телефона:
Листинг 10.107 Проверка prefix
Line 1 $ wget h tt p s : // g ithu b . com/ d i m i t r i / p r e f i x /raw/ master / p r e f i x e s
. f r . csv
- $ p s q l
-
- # c r e a t e t a b l e p r e f i x e s (
5 p r e f i x pref i x _ r a n g e primary key ,
- name t ex t not n ul l ,
- shortname te xt ,
- s ta t u s char d e f a u l t S ,
-
10 check ( s t at us i n ( S , R ) )
- ) ;
- CREATE TABLE
- # comment on column p r e f i x e s . s t at us i s S : - R: r e se r v e d ;
- COMMENT
15 # \ copy p r e f i x e s from p r e f i x e s . f r . csv with d e l i m i t e r ;
cs v quote "
247
10.21. Prefix
- COPY 11966
- # c r e a t e index i d x_ pref i x on p r e f i x e s u s i n g g i s t ( p r e f i x ) ;
- CREATE INDEX
- # s e l e c t * from p r e f i x e s l i m i t 1 0;
20 p r e f i x | name
| shortname | s t at u s
- - -
- - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - -
- 010001 | COLT TELECOMMUNICATIONS FRANCE
| COLT | S
- 010002 | EQUANT France
| EQFR | S
- 010003 | NUMERICABLE
| NURC | S
25 010004 | PROSODIE
| PROS | S
- 010005 | INTERNATIONAL TELECOMMUNICATION NETWORK France (
Vivaction ) | ITNF | S
- 010006 | SOCIETE FRANCAISE DU RADIOTELEPHONE
| SFR | S
- 010007 | SOCIETE FRANCAISE DU RADIOTELEPHONE
| SFR | S
- 010008 | BJT PARTNERS
| BJTP | S
30 010009 | LONG PHONE
| LGPH | S
- 010010 | IPNOTIC TELECOM
| TLNW | S
- (10 rows )
-
- # s e l e c t * from p r e f i x e s where p r e f i x @> 0146640123 ;
35 p r e f i x | name | shortname | s ta t u s
- - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - -
- 0146 | FRANCE TELECOM | FRTE | S
- (1 row )
-
40 # s e l e c t * from p r e f i x e s where p r e f i x @> 0100091234 ;
- p r e f i x | name | shortname | s ta t u s
- - - - - - - - -+ - - - - - - - - - - - -+ - - - - - - - - - - - + - - - - - - - -
- 010009 | LONG PHONE | LGPH | S
- (1 row )
Заключение
Более подробно об использовании расширения можно ознакомиться че-
рез официальную документацию.
248
10.22. Dblink
10.22 Dblink
Dblink расширение, которое позволяет выполнять запросы к удален-
ным базам данных непосредственно из SQL, не прибегая к помощи внеш-
них скриптов.
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.108 Инициализация dblink
Line 1 # CREATE EXTENSION d bl in k ;
Для создания подключения к другой базе данных нужно использовать
dblink_connect функцию, где первым параметром указывается имя под-
ключения, а вторым - опции подключения к базе:
Листинг 10.109 Подключение через dblink
Line 1 # SELECT dblink_connect ( slave_db , h ost=s l a ve . example . com
por t =5432 dbname=exampledb u s e r=admin password=password )
;
- dblink_connect
- - - - - - - - - - - - - - - - -
- OK
5 (1 row )
При успешном выполнении команды будет выведен ответ «OK». Теперь
можно попробовать считать данные из таблиц через dblink функцию:
Листинг 10.110 SELECT
Line 1 # SELECT *
- FROM d b l i nk ( slave_db , SELECT id , username FROM u s e r s
LIMIT 3 )
- AS dblink_u s e r s ( i d i nt e g e r , username t e xt ) ;
-
5 i d | username
- - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 1 | 64 ec708 3d7fa cb7c5 d976 8 4e7f 4 15b6 5
- 2 | 404 c 3b639a920b5ba 814 fc01353368f2
- 3 | 153041 f9 9 2 e 3 e a b 6 8 9 1 f 0 e 8 d a 9 d 1 1 f 23
10 (3 rows )
По завершению работы с сервером, подключение требуется закрыть
через функцию dblink_disconnect:
249
10.22. Dblink
Листинг 10.111 dblink_disconnect
Line 1 # SELECT d bl i n k _ d i s c o n n e c t ( slave_db ) ;
- dblink_disconnect
- - - - - - - - - - - - - - - - - - - -
- OK
5 (1 row )
Курсоры
Dblink поддерживает курсоры инкапсулирующие запросы, которые
позволяют получать результат запроса по нескольку строк за раз. Одна из
причин использования курсоров заключается в том, чтобы избежать пе-
реполнения памяти, когда результат содержит большое количество строк.
Для открытия курсора используется функция dblink_open, где первый
параметр - название подключения, второй - название для курсора, а тре-
тий - сам запрос:
Листинг 10.112 dblink_open
Line 1 # SELECT dblink_open ( slave_db , u s e r s , SELECT id ,
username FROM u s e r s ) ;
- dblink_open
- - - - - - - - - - - - - -
- OK
5 (1 row )
Для получения данных из курсора требуется использовать dblink_fetch
, где первый параметр - название подключения, второй - название для
курсора, а третий - требуемое количество записей из курсора:
Листинг 10.113 dblink_fetch
Line 1 # SELECT id , username FROM d b l i n k _ f e t c h ( slave_db , u s e rs ,
2)
- AS ( id i n te g e r , username t ex t ) ;
- i d | username
- - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5 1 | 64 ec708 3d7fa cb7c5 d976 8 4e7f 4 15b6 5
- 2 | 404 c 3b639a920b5ba 814 fc01353368f2
- (2 rows )
-
- # SELECT id , username FROM dblink_fetch ( slave_db , u se rs ,
2)
10 AS ( id i n t eg e r , username t ex t ) ;
- i d | username
- - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
250
10.22. Dblink
- 3 | 153041 f9 9 2 e 3 e a b 6 8 9 1 f 0 e 8 d a 9 d 1 1 f 23
- 4 | 318 c 334 58b4840f90d87ee4ea8737515
15 (2 rows )
-
- # SELECT id , username FROM dblink_fetch ( slave_db , u se rs ,
2)
- AS ( id i n te g e r , username t ex t ) ;
- i d | username
20 - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 6 | 5 b79 5b0e7 3 b0022 0843f 8 2c4d 0 f81f3 7
- 8 | c267 3 ee98 6 c23f6 2aaeb 669c3 22614 02
- (2 rows )
После работы с курсором его нужно обязательно закрыть через
dblink_close функцию:
Листинг 10.114 dblink_close
Line 1 # SELECT dbli n k _c lose ( slave_db , u se r s ) ;
- d b l in k_cl o s e
- - - - - - - - - - - - - - -
- OK
5 (1 row )
Асинхронные запросы
Последним вариантом для выполнения запросов в dblink является
асинхронный запрос. При его использовании результаты не будут возвра-
щены до полного выполнения результата запроса. Для создания асинхрон-
ного запроса используется dblink_send_query функция:
Листинг 10.115 dblink_send_query
Line 1 # SELECT * FROM dblink_send_query ( slave_db , SELECT id ,
username FROM u s e r s ) AS u s e r s ;
- u s e r s
- - - - - - - -
- 1
5 (1 row )
Результат получается через dblink_get_result функцию:
Листинг 10.116 dblink_get_result
Line 1 # SELECT id , username FROM d b l i n k _ g e t _ r e s u l t ( slave_db )
- AS ( id i n te g e r , username t ex t ) ;
- i d | username
251
10.23. Postgres_fdw
- - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5 1 | 64 ec70 83d7f acb7c 5d976 84e7f 415b6 5
- 2 | 404 c 3b6 39a 920b5ba814 fc0 135 3368f2
- 3 | 153041 f992e 3 e a b 6 8 9 1 f 0 e 8 d a 9 d 1 1 f 2 3
- 4 | 318 c33458b4840f90d 87e e4e a87 375 15
- 6 | 5 b 7 95b0e 73b00 2 2084 3 f82c4 d0f81 f 37
10 8 | c267 3ee98 6 c23f 6 2aae b 669c 3 2261 4 02
- 9 | c 5 3 f1 4 0 4 0f e f 95 4 c d6 e 7 3 b9 a a 2 e3 1 d 0e
- 10 | 2 db e 2 7 f d 9 6 c d b 3 9 f 0 1 c e 1 1 5 c f 3 c 2 a 5 1 7
10.23 Postgres_fdw
Postgres_fdw расширение, которое позволяет подключить
PostgreSQL к PostgreSQL, которые могут находиться на разных хо-
стах.
Установка и использование
Для начала инициализируем расширение в базе данных:
Листинг 10.117 Инициализация postgres_fdw
Line 1 # CREATE EXTENSION postgres_fdw ;
Далее создадим сервер подключений, который будет содержать данные
для подключения к другой PostgreSQL базе:
Листинг 10.118 Создание сервера
Line 1 # CREATE SERVER slave_db
- FOREIGN DATA WRAPPER postgres_fdw
- OPTIONS ( host s l a v e . example . com , dbname exampledb , p o rt
5432 ) ;
После этого нужно создать USER MAPPING, которое создаёт сопостав-
ление пользователя на внешнем сервере:
Листинг 10.119 USER MAPPING
Line 1 # CREATE USER MAPPING FOR admin
- SERVER slave_db
- OPTIONS ( u se r admin , password password ) ;
Теперь можно импортировать таблицы:
252
10.23. Postgres_fdw
Листинг 10.120 Импорт таблицы
Line 1 # CREATE FOREIGN TABLE fdw_users (
- id s e r i a l ,
- username t e xt not n u ll ,
- password tex t ,
5 created_on timestamptz not n u l l ,
- last_logged_on timestamptz not n u l l
- )
- SERVER slave_db
- OPTIONS ( schema_name p u b l ic , table_name u se rs ) ;
Для того, чтобы не импортировать каждую таблицу отдельно, можно
воспользоваться IMPORT FOREIGN SCHEMA командой:
Листинг 10.121 Импортируем таблицы
Line 1 # IMPORT FOREIGN SCHEMA p ub l ic
- LIMIT TO ( u s e r s , pages )
- FROM SERVER slave_db INTO fdw ;
Теперь можно проверить таблицы:
Листинг 10.122 SELECT
Line 1 # SELECT * FROM fdw_users LIMIT 1 ;
- - [ RECORD 1 ] - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- i d | 1
- username | 64 ec708 3d7fa cb7c5 d9768 4e7f 4 15b6 5
5 password | b82af 3966b 49c9e f0f78 2 9107 d b642 b c
- created_on | 2017 -02 -21 05:07 : 2 5 . 6 19561+00
- last_logged_on | 2017 -02 -19 21:03:3 5 . 6 5 1 5 61+00
По умолчанию из таблиц можно не только читать, но и изменять в них
данные (INSERT/UPDATE/DELETE). updatable опция может использовать
для подключения к серверу в режиме «только на чтение»:
Листинг 10.123 Read-only mode
Line 1 # ALTER SERVER slave_db
- OPTIONS (ADD up dat able f a l s e ) ;
- ALTER SERVER
- # DELETE FROM fdw_users WHERE i d < 1 0;
5 ERROR: f o r e i g n t a b le " fdw_users " does not all o w d e l e t e s
Данную опцию можно установить не только на уровне сервера, но и на
уровне отдельных таблиц:
253
10.24. Pg_cron
Листинг 10.124 Read-only mode для таблицы
Line 1 # ALTER FOREIGN TABLE fdw_users
- OPTIONS (ADD up dat able f a l s e ) ;
Postgres_fdw и DBLink
Как можно было заметить, postgres_fdw и dblink выполняют одну и
ту же работу подключение одной PostgreSQL базы к другой. Что лучше
использовать в таком случае?
PostgreSQL FDW (Foreign Data Wrapper) более новый и рекомендуе-
мый метод подключения к другим источникам данных. Хотя функцио-
нальность dblink похожа на FDW, последний является более SQL совме-
стимым и может обеспечивать улучшеную производительность по срав-
нению с dblink подключениями. Кроме того, в отличии от postgres_fdw,
dblink не способен сделать данные «только на чтение». Это может быть
достаточно важно, если требуется обеспечить, чтобы данные в другой базе
нельзя было изменять.
В dblink подключения работают только в течение работы сессии и
их требуется пересоздавать каждый раз. Postgres_fdw создает постоян-
ное подключение к другой базе данных. Это может быть как хорошо, так
плохо, в зависимости от потребностей.
Из положительных сторон dblink можно отнести множество полезных
команд, которые позволяют использовать его для программирования по-
лезного функционала. Также dblink работает в версиях PostgreSQL 8.3 и
выше, в то время как postgres_fdw работает только в PostgreSQL 9.3 и вы-
ше акое может возникнуть, если нет возможности обновить PostgreSQL
базу).
10.24 Pg_cron
Pg_cron cron-подобный планировщик задач для PostgreSQL 9.5 или
выше, который работает как расширение к базе. Он может выполнять
несколько задач параллельно, но одновременно может работать не более
одного экземпляра задания (если при запуске задачи предыдущий запуск
будет еще выполняться, то запуск будет отложен до выполнения текущей
задачи).
Установка и использование
После установки расширения требуется добавить его в postgresql .conf и
перезапустить PostgreSQL:
Листинг 10.125 pg_cron
254
10.24. Pg_cron
Line 1 s h ar e d_ p re l oa d _l i br a ri e s = pg_cron
Далее требуется активировать расширение для postgres базы:
Листинг 10.126 pg_cron
Line 1 # CREATE EXTENSION pg_cron ;
По умолчанию pg_cron ожидает, что все таблицы с метаданными будут
находиться в postgres базе данных. Данное поведение можно изменить и
указать через параметр cron.database_name в postgresql .conf другую базу
данных, где pg_cron будет хранить свои данные.
Внутри pg_cron использует libpq библиотеку, поэтому потребуется раз-
решить подключения с localhost без пароля (trust в pg_hba.conf) или же
создать .pgpass файл для настройки подключения к базе.
Для создания cron задач используется функция cron.schedule:
Листинг 10.127 Cron.schedule
Line 1 - - D e l e t e old data on Saturday at 3 :30am (GMT)
- SELECT cron . s che d u le ( 30 3 * * 6 , $$DELETE FROM e v e n ts
WHERE event_time < now ( ) - i n t e r v a l 1 week $$ ) ;
- s c he dul e
- - - - - - - - - - -
5 42
Для удаления созданых задач используется cron.unschedule:
Листинг 10.128 Cron.unschedule
Line 1 - - Vacuum ev ery day at 1 0: 0 0am (GMT)
- SELECT cron . s c he dul e ( 0 10 * * * , VACUUM ) ;
- sc hed u l e
- - - - - - - - - - -
5 43
-
- - - Stop s ch e du l in g a j ob
- SELECT cron . unsc hedu le ( 43 ) ;
- un sche dule
10 - - - - - - - - - - - -
- t
В целях безопасности cron задачи выполняются в базе данных, в ко-
торой cron.schedule функция была вызвана с правами доступа текущего
пользователя.
Поскольку pg_cron использует libpq библиотеку, это позволяет запус-
кать cron задачи на других базах данных (даже на других серверах). С
помощью суперпользователя возможно модифицировать cron.job таблицу
255
10.25. PGStrom
и добавить в нее параметры подключения к другой базе через nodename и
nodeport поля:
Листинг 10.129 Cron.job
Line 1 INSERT INTO cron . j ob ( sch e dule , command , nodename , nodeport ,
database , username )
- VALUES ( 0 4 * * * , VACUUM , worker - node -1 , 5432 ,
p o s t g r e s , marco ) ;
В таком случае нужно будет создать .pgpass файл для настройки под-
ключения к базе на другом сервере.
10.25 PGStrom
PGStrom PostgreSQL расширение, которое позволяет использовать
GPU для выполнения некоторых SQL операций. В частности, за счёт при-
влечения GPU могут быть ускорены такие операции как сравнительный
перебор элементов таблиц, агрегирование записей и слияние хэшей. Код
для выполнения на стороне GPU генерируется в момент разбора SQL-
запроса при помощи специального JIT-компилятора и в дальнейшем вы-
полняется параллельно с другими связанными с текущим запросом опе-
рациями, выполняемыми на CPU. Для выполнения заданий на GPU за-
действован OpenCL. Увеличение производительности операций слияния
таблиц (JOIN) при использовании GPU увеличивается в десятки раз.
Областью применения PG-Strom являются огромные отчеты с исполь-
зованием агрегации и объединения таблиц. Эти рабочие нагрузки чаще
используются в пакетной обработке данных для OLAP систем.
10.26 ZomboDB
ZomboDB PostgreSQL расширение, которое позволяет использовать
Elasticsearch индексы внутри базы (используется интерфейс для методов
доступа индекса). ZomboDB индекс для PostgreSQL ничем не отличается
от стандартного btree индекса. Таким образом, стандартные команды SQL
полностью поддерживаются, включая SELECT, BEGIN, COMMIT, ABORT,
INSERT, UPDATE, DELETE, COPY и VACUUM и данные индексы являются
MVCC-безопасными.
На низком уровне ZomboDB индексы взаимодействуют с Elasticsearch
сервером через HTTP запросы и автоматически синхронизируются в про-
цессе изменения данных в PostgreSQL базе.
256
10.26. ZomboDB
Рис. 10.3: PGStrom
Установка и использование
ZomboDB состоит из двух частей: PostgreSQL расширения (написан на
C и SQL/PLPGSQL) и Elasticsearch плагина (написан на Java).
После установки требуется добавить в postgresql .conf zombodb библио-
теку:
Листинг 10.130 Zombodb
Line 1 l o c a l _ p r e l o a d _ l i b r a r i e s = zombodb . so
И после перегрузки PostgreSQL активировать его для базы данных:
Листинг 10.131 Zombodb
Line 1 CREATE EXTENSION zombodb ;
После этого требуется установить Elasticsearch плагин на все ноды сер-
вера и изменить конфигурацию в elasticsearch .yml:
257
10.26. ZomboDB
Листинг 10.132 Elasticsearch
Line 1 thre a d p o ol . bulk . qu eue_size : 1024
- t h re ad p oo l . bulk . s i z e : 12
-
- http . compre ssio n : t r ue
5
- http . max_content_length : 1024mb
- index . query . b ool . max_clause_count : 1000000
Для примера создадим таблицу с продуктами и заполним её данными:
Листинг 10.133 Products table
Line 1 # CREATE TABLE pr oduct s (
- id SERIAL8 NOT NULL PRIMARY KEY,
- name t ex t NOT NULL,
- keywords varc har ( 6 4) [ ] ,
5 short_summary phrase ,
- l o ng _ de s cri p ti o n f u l l t e x t ,
- p r i c e b i gi n t ,
- inventory_count i nt eg e r ,
- d i sc o nt i nu e d boolean d e f a u l t f a l s e ,
10 a v a i l a b i l i t y _ d a t e date
- ) ;
-
- # COPY produ c ts FROM PROGRAM c u r l http s : // raw .
g i t hu b use r con t ent . com/zombodb/zombodb/ master /TUTORIAL-
data . dmp ;
zdb(record) zombodb функция конвертирует запись в JSON формат
(обертка поверх row_to_json(record)):
Листинг 10.134 Zdb
Line 1 # SELECT zdb ( pr o d ucts ) FROM prod u cts WHERE i d = 1;
- zdb
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {" id " : 1 , "name" : " Magical Widget " , " keywords " : [ " magical " , "
widget " , " round " ] , "short_summary" : "A widget that i s q u i t e
ma gical " , " l o ng _ des c ri p ti o n " : " Magical Widgets come from
the land o f M a g i c v i l l e and ar e c a p able o f t hi n gs you can
t imagine " , " p r i c e " :9 9 00 , " inventory_count " : 4 2 , "
d i sc o nt i n ue d " : f a l s e , " a v a i l a b i l i t y _ d a t e " : " 2015 -08 -31 "}
zdb(regclass , tid) zombodb функция, которая используется для стати-
ческого определения ссылок на таблицу/индекс в контексте последова-
тельного сканирования. Благодаря этим двум функциям можно создать
zombodb индекс для products таблицы:
258
10.26. ZomboDB
Листинг 10.135 Zdb
Line 1 # CREATE INDEX idx_zdb_products
- ON produ c t s
- USING zombodb ( zdb ( p roduct s , produc t s . c t i d ) , zdb (
pro d ucts ) )
- WITH ( u r l= http : / / l o c a l h o s t :9200/ ) ;
Теперь можно проверить работу индекса:
Листинг 10.136 Index usage
Line 1 # SELECT id , name , short_summary FROM prod u c ts WHERE zdb (
pro d ucts , prod u cts . c t i d ) ==> s p o r t s or box ;
- i d | name | short_summary
- - - - - + - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 2 | B a s eb a l l | I t s a b a s e b a l l
5 4 | Box | J ust an empty box made o f wood
- (2 rows )
- # EXPLAIN SELECT * FROM produc t s WHERE zdb ( p r oducts , c t i d )
==> s p o r t s or box ;
- QUERY PLAN
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 Index Scan using idx_zdb_products on p roduct s ( c os t
= 0 . 0 0 . . 4 . 0 2 rows=2 width =153)
- Index Cond : ( zdb ( p roduct s : : r e g c l a s s , c t i d ) ==> s p o r t s
or box : : t ext )
- (2 rows )
ZomboDB содержит набор функций для агрегационных запросов. На-
пример, если нужно выбрать уникальный набор ключевых слов для всех
продуктов в keywords поле вместе с их количеством, то можно воспользо-
ваться zdb_tally функцией:
Листинг 10.137 Zdb_tally
Line 1 # SELECT * FROM zdb_tally ( prod u c ts , keywords , ^.* , ,
5000 , term ) ;
- term | count
- - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - -
- al e x a n de r graham b e l l | 1
5 b a s e b a l l | 1
- box | 1
- communication | 1
- magical | 1
- n ega t iv e spac e | 1
10 p r i m i t i v e | 1
- round | 2
259
10.27. Заключение
- s p or t s | 1
- sq u are | 1
- widget | 1
15 wooden | 1
- (12 rows )
Более подробно с использованием ZomboDB можно ознакомиться в
официальной документации.
10.27 Заключение
Расширения помогают улучшить работу PostgreSQL в решении специ-
фических проблем. Расширяемость PostgreSQL позволяет создавать соб-
ственные расширения, или же наоборот, не нагружать СУБД лишним, не
требуемым функционалом.
260
11
Бэкап и восстановление PostgreSQL
Есть два типа
администраторов те, кто не
делает бэкапы, и те, кто уже
делает
Народная мудрость
Если какая-нибудь
неприятность может
произойти, она случается
Закон Мэрфи
11.1 Введение
Любой хороший сисадмин знает бэкапы нужны всегда. Насколько
бы надежной ни казалась Ваша система, всегда может произойти случай,
который был не учтен, и из-за которого могут быть потеряны данные.
Тоже самое касается и PostgreSQL баз данных. Посыпавшийся винче-
стер на сервере, ошибка в файловой системе, ошибка в другой програм-
ме, которая перетерла весь каталог PostgreSQL и многое другое приведет
только к плачевному результату. И даже если у Вас репликация с множе-
ством слейвов, это не означает, что система в безопасности неверный
запрос на мастер (DELETE/DROP/TRUNCATE), и у слейвов такая же пор-
ция данных (точнее их отсутствие).
Существуют три принципиально различных подхода к резервному ко-
пированию данных PostgreSQL:
SQL бэкап;
Бэкап уровня файловой системы;
Непрерывное резервное копирование;
Каждый из этих подходов имеет свои сильные и слабые стороны.
261
11.2. SQL бэкап
11.2 SQL бэкап
Идея этого подхода в создании текстового файла с командами SQL.
Такой файл можно передать обратно на сервер и воссоздать базу данных
в том же состоянии, в котором она была во время бэкапа. У PostgreSQL
для этого есть специальная утилита pg_dump. Пример использования
pg_dump:
Листинг 11.1 Создаем бэкап с помощью pg_dump
Line 1 $ pg_dump dbname > o u t f i l e
Для восстановления такого бэкапа достаточно выполнить:
Листинг 11.2 Восстанавливаем бэкап
Line 1 $ p s ql dbname < i n f i l e
При этом базу данных dbname потребуется создать перед восстановле-
нием. Также потребуется создать пользователей, которые имеют доступ к
данным, которые восстанавливаются (это можно и не делать, но тогда про-
сто в выводе восстановления будут ошибки). Если нам требуется, чтобы
восстановление прекратилось при возникновении ошибки, тогда потребу-
ется восстанавливать бэкап таким способом:
Листинг 11.3 Восстанавливаем бэкап
Line 1 $ p s ql - - s e t ON_ERROR_STOP=on dbname < i n f i l e
Также, можно делать бэкап и сразу восстанавливать его в другую базу:
Листинг 11.4 Бэкап в другую БД
Line 1 $ pg_dump - h host1 dbname | p sql -h hos t2 dbname
После восстановления бэкапа желательно запустить ANALYZE, чтобы
оптимизатор запросов обновил статистику.
А что, если нужно сделать бэкап не одной базы данных, а всех, да и
еще получить в бэкапе информацию про роли и таблицы? В таком слу-
чае у PostgreSQL есть утилита pg_dumpall. pg_dumpall используется для
создания бэкапа данных всего кластера PostgreSQL:
Листинг 11.5 Бэкап кластера PostgreSQL
Line 1 $ pg_dumpall > o u t f i l e
Для восстановления такого бэкапа достаточно выполнить от супер-
пользователя:
262
11.2. SQL бэкап
Листинг 11.6 Восстановления бэкапа PostgreSQL
Line 1 $ p s ql - f i n f i l e p o s t g r e s
SQL бэкап больших баз данных
Некоторые операционные системы имеют ограничения на максималь-
ный размер файла, что может вызывать проблемы при создании больших
бэкапов через pg_dump. К счастью, pg_dump можете бэкапить в стандарт-
ный вывод. Так что можно использовать стандартные инструменты Unix,
чтобы обойти эту проблему. Есть несколько возможных способов:
Использовать сжатие для бэкапа
Можно использовать программу сжатия данных, например GZIP:
Листинг 11.7 Сжатие бэкапа PostgreSQL
Line 1 $ pg_dump dbname | g zi p > f ile n am e . gz
Восстановление:
Листинг 11.8 Восстановление бэкапа PostgreSQL
Line 1 $ gunzip - c f il e n am e . gz | p sq l dbname
или
Листинг 11.9 Восстановление бэкапа PostgreSQL
Line 1 ca t f i le n a me . gz | gunzip | p sq l dbname
Использовать команду split
Команда split позволяет разделить вывод в файлы меньшего разме-
ра, которые являются подходящими по размеру для файловой систе-
мы. Например, бэкап делится на куски по 1 мегабайту:
Листинг 11.10 Создание бэкапа PostgreSQL
Line 1 $ pg_dump dbname | s p l i t -b 1m - f i len a me
Восстановление:
Листинг 11.11 Восстановление бэкапа PostgreSQL
Line 1 $ cat f i len a me * | ps q l dbname
263
11.3. Бэкап уровня файловой системы
Использовать пользовательский формат дампа pg_dump
PostgreSQL построен на системе с библиотекой сжатия Zlib, поэтому
пользовательский формат бэкапа будет в сжатом виде. Это похоже
на метод с использованием GZIP, но он имеет дополнительное пре-
имущество таблицы могут быть восстановлены выборочно. Минус
такого бэкапа восстановить возможно только в такую же версию
PostgreSQL (отличаться может только патч релиз, третья цифра по-
сле точки в версии):
Листинг 11.12 Создание бэкапа PostgreSQL
Line 1 $ pg_dump - Fc dbname > f il e n am e
Через psql такой бэкап не восстановить, но для этого есть утилита
pg_restore:
Листинг 11.13 Восстановление бэкапа PostgreSQL
Line 1 $ p g _res tore - d dbname f ile n am e
При слишком большой базе данных, вариант с командой split нужно
комбинировать со сжатием данных.
11.3 Бэкап уровня файловой системы
Альтернативный метод резервного копирования заключается в непо-
средственном копировании файлов, которые PostgreSQL использует для
хранения данных в базе данных. Например:
Листинг 11.14 Бэкап PostgreSQL файлов
Line 1 $ t ar - c f backup . t a r / us r / l o c a l / p g sql / data
Но есть два ограничения, которые делает этот метод нецелесообраз-
ным, или, по крайней мере, уступающим SQL бэкапу:
PostgreSQL база данных должна быть остановлена, для того, чтобы
получить актуальный бэкап (PostgreSQL держит множество объек-
тов в памяти, буферизация файловой системы). Излишне говорить,
что во время восстановления такого бэкапа потребуется также оста-
новить PostgreSQL;
Не получится восстановить только определенные данные с такого
бэкапа;
264
11.4. Непрерывное резервное копирование
Как альтернатива, можно делать снимки (snapshot) файлов системы
(папки с файлами PostgreSQL). В таком случае останавливать PostgreSQL
не требуется. Однако, резервная копия, созданная таким образом, сохра-
няет файлы базы данных в состоянии, как если бы сервер базы данных
был неправильно остановлен. Поэтому при запуске PostgreSQL из резерв-
ной копии, он будет думать, что предыдущий экземпляр сервера вышел
из строя и восстановит данные в соответствии с данными журнала WAL.
Это не проблема, просто надо знать про это не забыть включить WAL
файлы в резервную копию). Также, если файловая система PostgreSQL
распределена по разным файловым системам, то такой метод бэкапа бу-
дет очень ненадежным снимки файлов системы должны быть сделаны
одновременно. Почитайте документацию файловой системы очень внима-
тельно, прежде чем доверять снимкам файлов системы в таких ситуациях.
Также возможен вариант с использованием rsync утилиты. Первым
запуском rsync мы копируем основные файлы с директории PostgreSQL
(PostgreSQL при этом продолжает работу). После этого мы останавлива-
ем PostgreSQL и запускаем повторно rsync. Второй запуск rsync пройдет
гораздо быстрее, чем первый, потому что будет передавать относительно
небольшой размер данных, и конечный результат будет соответствовать
остановленной СУБД. Этот метод позволяет делать бэкап уровня файло-
вой системы с минимальным временем простоя.
11.4 Непрерывное резервное копирование
PostgreSQL поддерживает упреждающую запись логов (Write Ahead
Log, WAL) в pg_xlog директорию, которая находится в директории дан-
ных СУБД. В логи пишутся все изменения, сделанные с данными в СУБД.
Этот журнал существует прежде всего для безопасности во время кра-
ха PostgreSQL: если происходят сбои в системе, базы данных могут быть
восстановлены с помощью «перезапуска» этого журнала. Тем не менее, су-
ществование журнала делает возможным использование третьей страте-
гии для резервного копирования баз данных: мы можем объединить бэкап
уровня файловой системы с резервной копией WAL файлов. Если требу-
ется восстановить такой бэкап, то мы восстанавливаем файлы резервной
копии файловой системы, а затем «перезапускаем» с резервной копии фай-
лов WAL для приведения системы к актуальному состоянию. Этот подход
является более сложным для администрирования, чем любой из предыду-
щих подходов, но он имеет некоторые преимущества:
Не нужно согласовывать файлы резервной копии системы. Любая
внутренняя противоречивость в резервной копии будет исправлена
путем преобразования журнала (не отличается от того, что происхо-
дит во время восстановления после сбоя);
265
11.4. Непрерывное резервное копирование
Восстановление состояния сервера для определенного момента вре-
мени;
Если мы постоянно будем «скармливать» файлы WAL на другую
машину, которая была загружена с тех же файлов резервной базы, то
у нас будет находящийся всегда в актуальном состоянии резервный
сервер PostgreSQL (создание сервера горячего резерва);
Как и бэкап файловой системы, этот метод может поддерживать толь-
ко восстановление всей базы данных кластера. Кроме того, он требует
много места для хранения WAL файлов.
Настройка
Первый шаг активировать архивирование. Эта процедура будет
копировать WAL файлы в архивный каталог из стандартного каталога
pg_xlog. Это делается в файле postgresql .conf:
Листинг 11.15 Настройка архивирования
Line 1 archive_mode = on # enable a r c h i vi ng
- archive_command = cp - v %p / data / p g s ql / a rc h i v e s/%f
- ar chive_ti meout = 300 # timeout t o c l o s e b u f f e r s
После этого необходимо перенести файлы порядке их появления) в
архивный каталог. Для этого можно использовать функцию rsync. Можно
поставить функцию в cron и, таким образом, файлы могут автоматически
перемещаться между хостами каждые несколько минут:
Листинг 11.16 Копирование WAL файлов на другой хост
Line 1 $ rs y n c - avz - - d e l e t e prod1 : / data / p gs ql / a r c h i v e s / \
- / data / pg s q l / a r c hi v e s / > / dev / n u l l
В конце необходимо скопировать файлы в каталог pg_xlog на сервере
PostgreSQL (он должен быть в режиме восстановления). Для этого необхо-
димо в каталоге данных PostgreSQL создать файл recovery.conf с заданной
командой копирования файлов из архива в нужную директорию:
Листинг 11.17 recovery.conf
Line 1 restore_command = cp / data / p g sq l / a r c h i v e s/%f "%p"
Документация PostgreSQL предлагает хорошее описание настройки
непрерывного копирования, поэтому данная глава не будет углубляться
в детали (например, как перенести директорию СУБД с одного сервера
на другой, какие могут быть проблемы). Более подробно вы можете почи-
тать по этой ссылке.
266
11.5. Утилиты для непрерывного резервного копирования
11.5 Утилиты для непрерывного резервного
копирования
Непрерывное резервное копирования - один из лучших способов для со-
здания бэкапов и их восстановления. Нередко бэкапы сохраняются на той
же файловой системе, на которой расположена база данных. Это не очень
безопасно, т.к. при выходе дисковой системы сервера из строя вы можете
потерять все данные базу, и бэкапы), или попросту столкнуться с тем,
что на жестком диске закончится свободное место. Поэтому лучше, когда
бэкапы складываются на отдельный сервер или в «облачное хранилище»
(например AWS S3). Чтобы не писать свой «велосипед» для автомати-
зации этого процесса на сегодняшний день существует набор программ,
которые облегчают процесс настройки и поддержки процесса создания
бэкапов на основе непрерывного резервного копирования.
WAL-E
WAL-E предназначена для непрерывной архивации PostgreSQL WAL-
logs в Amazon S3 или Windows Azure (начиная с версии 0.7) и управления
использованием pg_start_backup и pg_stop_backup. Утилита написана на
Python и разработана в компании Heroku, где её активно используют.
Установка
У WAL-E есть пара зависимостей: lzop, psql, pv старых версиях ис-
пользуется mbuffer), python 3.4+ и несколько python библиотек (gevent, boto
, azure). Также для удобства настроек переменных среды устанавливается
daemontools. На Ubuntu это можно все поставить одной командой:
Листинг 11.18 Установка зависимостей для WAL-E
Line 1 $ a p t itu d e i n s t a l l g it - c o re python - dev python - s e t u p t o o l s
python - pip bui ld - e s s e n t i a l li be v en t - dev l z op pv
daemontools daemontools - run
Теперь установим WAL-E:
Листинг 11.19 Установка WAL-E
Line 1 $ pip i n s t a l l h tt p s : // g ithu b . com/wal - e /wal - e / a r ch iv e /v1 . 0 . 3 .
t a r . gz
После успешной установки можно начать работать с WAL-E.
267
11.5. Утилиты для непрерывного резервного копирования
Настройка и работа
Как уже писалось, WAL-E сливает все данные в AWS S3, поэтому нам
потребуются «Access Key ID», «Secret Access Key» и «AWS Region» (эти
данные можно найти в аккаунте Amazon AWS). Команда для загрузки
бэкапа всей базы данных в S3:
Листинг 11.20 Загрузка бэкапа всей базы данных в S3
Line 1 AWS_REGION= . . . AWS_SECRET_ACCESS_KEY= . . . wal - e
\
- -k AWS_ACCESS_KEY_ID \
- - - s3 - p r e f i x=s3 : / / some - bucket / d i r e c t o r y / or / whatever \
- backup - push / var / l i b / p o s t g r e s q l / 9 .2 / main
Где s3- prefix URL, который содержит имя S3 бакета (bucket) и путь к
папке, куда следует складывать резервные копии. Команда для загрузки
WAL-логов на S3:
Листинг 11.21 Загрузка WAL-логов на S3
Line 1 AWS_REGION= . . . AWS_SECRET_ACCESS_KEY= . . . wal - e
\
- -k AWS_ACCESS_KEY_ID \
- - - s3 - p r e f i x=s3 : / / some - bucket / d i r e c t o r y / or / whatever \
- wal - push / var / l i b / p o s t g r e s q l /9 . 2 / main/ pg_xlog/
WAL_SEGMENT_LONG_HEX
Для управления этими переменными окружения можно использовать
команду envdir (идет в поставке с daemontools). Для этого создадим envdir
каталог:
Листинг 11.22 WAL-E с envdir
Line 1 $ mkdir - p / e t c /wal - e . d/env
- $ echo " aws_region " > / e t c /wal - e . d/env /AWS_REGION
- $ echo " s e c r e t - key" > / e t c /wal - e . d/ env /AWS_SECRET_ACCESS_KEY
- $ echo " ac ce ss - key " > / e t c /wal - e . d/env /AWS_ACCESS_KEY_ID
5 $ echo s3 : / / some - bucket / d i r e c t o r y / or / whatever > / e t c /wal - e
. d/ env /WALE_S3_PREFIX
- $ chown -R r o ot : p os tg r e s / e t c /wal - e . d
После создания данного каталога появляется возможность запускать
WAL-E команды гораздо проще и с меньшим риском случайного исполь-
зования некорректных значений:
Листинг 11.23 WAL-E с envdir
268
11.5. Утилиты для непрерывного резервного копирования
Line 1 $ env d i r / et c /wal - e . d/env wal - e backup - push . . .
- $ envdi r / e tc /wal - e . d/ env wal - e wal - push . . .
Теперь настроим PostgreSQL для сбрасывания WAL-логов в S3 c помо-
щью WAL-E. Отредактируем postgresql .conf:
Листинг 11.24 Настройка PostgreSQL
Line 1 w a l _ l e vel = hot_standby # или a r c h i v e , если PostgreSQL < 9. 0
- archive_mode = on
- archive_command = env d i r / et c /wal - e . d/ env / u s r / l o c a l / bin /
wal - e wal - push %p
- ar chive_ti meout = 60
Лучше указать полный путь к WAL-E (можно узнать командой which
wal-e), поскольку PostgreSQL может его не найти. После этого нужно пе-
регрузить PostgreSQL. В логах базы вы должны увидеть что-то подобное:
Листинг 11.25 Логи PostgreSQL
Line 1 2016 -11 -07 1 4 : 5 2 : 1 9 UTC LOG: d ata base system was shut down
at 2016 -11 -07 1 4 : 5 1 : 4 0 UTC
- 2016 -11 -07 1 4 : 5 2 : 1 9 UTC LOG: d ata base system i s ready to
accept c o nn e c ti o ns
- 2016 -11 -07 1 4 : 5 2 : 1 9 UTC LOG: autovacuum l a u n ch e r s t a r t ed
- 2016 -11 -07 T14 :5 2 :1 9 .7 8 4+ 0 0 pid =7653 wal_e . worker . s3_worker
INFO MSG: b egin a r c hi vi ng a f i l e
5 DETAIL: Uploading " pg_xlog /000000010000000000000001 "
to " s3 : / / c l everdb - pg - backups/pg/wal_005
/000 000010000000000000001. l z o " .
- 2016 -11 -07 1 4 : 5 2 : 1 9 UTC LOG: incomplete s t art u p packet
- 2016 -11 -07 T14 :5 2 :2 8 .2 3 4+ 0 0 pid =7653 wal_e . worker . s3_worker
INFO MSG: completed a rc h i v i n g to a f i l e
- DETAIL: A rchivi ng to " s3 : / / c l e v e r d b - pg - backups /pg/
wal_005 /00000001000000 0000000001. l z o " complete at 21583.3
KiB/ s .
- 2016 -11 -07 T14 :5 2 :2 8 .3 4 1+ 0 0 pid =7697 wal_e . worker . s3_worker
INFO MSG: b egin a r c hi vi ng a f i l e
10 DETAIL: Uploading " pg_xlog
/0 0000 0010 0000 0000 0000 002. 0000 0020 . backup" to " s3 : / /
cleverd b - pg - backups /pg/wal_005
/0 0000 0010 0000 0000 0000 002. 0000 0020 . backup . l z o " .
- 2016 -11 -07 T14 :5 2 :3 4 .0 2 7+ 0 0 pid =7697 wal_e . worker . s3_worker
INFO MSG: completed a rc h i v i n g to a f i l e
- DETAIL: A rchivi ng to " s3 : / / c l e v e r d b - pg - backups /pg/
wal_005 /000 0000 1000 0000 0000 0000 2.00 0000 20. backup . l z o "
complete at 00KiB/ s .
269
11.5. Утилиты для непрерывного резервного копирования
- 2016 -11 -07 T14 :5 2 :3 4 .1 8 7+ 0 0 pid =7711 wal_e . worker . s3_worker
INFO MSG: b egin a r c hi vi ng a f i l e
- DETAIL: Uploading " pg_xlog /000000010000000000000002 "
to " s3 : / / c l everdb - pg - backups/pg/wal_005
/000 000010000000000000002. l z o " .
15 2016 -11 -07 T14 :5 2 :4 0 .2 3 2+ 0 0 pid =7711 wal_e . worker . s3_worker
INFO MSG: completed a rc h i v i n g to a f i l e
- DETAIL: A rchivi ng to " s3 : / / c l e v e r d b - pg - backups /pg/
wal_005 /00000001000000 0000000002. l z o " complete at 2466.67
KiB/ s .
Если ничего похожего в логах не видно, тогда нужно смотреть что за
ошибка появляется и исправлять её. Для того, чтобы бэкапить всю базу,
достаточно выполнить данную команду:
Листинг 11.26 Загрузка бэкапа всей базы данных в S3
Line 1 $ env d i r / et c /wal - e . d/env wal - e backup - push / var / l i b /
p o s t g r e s q l / 9 . 2 / main
- 2016 -11 -07 T14 :4 9 :2 6 .1 7 4+ 0 0 pid =7493 wal_e . o p era t o r .
s3_operator INFO MSG: s t a r t upload p o s tg re s v e r si on
metadata
- DETAIL: Uploading to s3 : // cleverdb - pg - backups /pg/
basebackups_005/ base_000000010000000000000006_00000032 /
ex tended_version . tx t .
- 2016 -11 -07 T14 :4 9 :3 2 .7 8 3+ 0 0 pid =7493 wal_e . o p era t o r .
s3_operator INFO MSG: p o s t g r e s v er si o n metadata
upload complete
5 2016 -11 -07 T14 :4 9 :3 2 .8 5 9+ 0 0 pid =7493 wal_e . worker . s3_worker
INFO MSG: beginning volume co mpre ssio n
- DETAIL: Bu i l d in g volume 0 .
- . . .
- HINT : Check th a t your archive_command i s e x ec uti n g p r ope r ly
. pg_stop_backup can be c a n cel e d s a f e l y , but the
da tabase backup w i l l not be u s a ble without a l l the WAL
segments .
- NOTICE: pg_stop_backup complete , a l l r e q u i r ed WAL segments
have been a r c hi ve d
Данный бэкап лучше делать раз в сутки (например, добавить в crontab
). На рис 11.1-11.3 видно как хранятся бэкапы на S3. Все бэкапы сжаты
через lzop. Данный алгоритм сжимает хуже чем gzip, но скорость сжатия
намного быстрее (приблизительно 25 Мб/сек используя 5% ЦПУ). Чтобы
уменьшить нагрузку на чтение с жесткого диска бэкапы отправляются че-
рез pv утилиту (опцией cluster -read-rate- limit можно ограничить скорость
чтения, если это требуется).
Теперь перейдем к восстановлению данных. Для восстановления базы
из резервной копии используется backup-fetch команда:
270
11.5. Утилиты для непрерывного резервного копирования
Рис. 11.1: Папка бэкапов на S3
Рис. 11.2: Папка бэкапов базы на S3
Рис. 11.3: Папка WAL-логов на S3
Листинг 11.27 Восстановление бэкапа базы из S3
Line 1 $ sudo -u p os tg re s bash - c " en vd ir / et c /wal - e . d/ env wal - e
271
11.5. Утилиты для непрерывного резервного копирования
- - s3 - p r e f i x=s3 : / / some - bucket / d i r e c t o r y / or / whatever backup
- f e t c h / var / l i b / p o s t g r e s q l / 9 . 2/ main LATEST"
Где LATEST означает, что база восстановится из последнего актуаль-
ного бэкапа (PostgreSQL в это время должен быть остановлен). Для вос-
становления из более поздней резервной копии:
Листинг 11.28 Восстановление из поздней резервной копии
Line 1 $ sudo -u p os tg re s bash - c " en vd ir / et c /wal - e . d/ env wal - e
- - s3 - p r e f i x=s3 : / / some - bucket / d i r e c t o r y / or / whatever backup
- f e t c h / var / l i b / p o s t g r e s q l / 9 . 2/ main
base_LONGWALNUMBER_POSITION_NUMBER"
Для получения списка доступных резервных копий есть команда backup
-list:
Листинг 11.29 Список резервных копий
Line 1 $ env d i r / et c /wal - e . d/env wal - e backup - l i s t
- name la s t_ mo di fi e d expanded_size_bytes
wal_segment_backup_start
wal_segment_offset_backup_start wal_segment_backup_stop
wal_segment_offset_backup_stop
- base_000000010000000000000008_00000032 2016 -11 -07 T14
: 0 0 : 0 7 . 0 0 0 Z 000000010000000000000008
00000032
- base_00000001000000000000000C_00000032 2016 -11 -08T15
: 0 0 : 0 8 . 0 0 0 Z 00000001000000000000000C
00000032
После завершения работы с основной резервной копией для полного
восстановления нужно считать WAL-логи (чтобы данные обновились до
последнего состояния). Для этого используется recovery.conf:
Листинг 11.30 recovery.conf
Line 1 restore_command = e n vd ir / et c /wal - e . d/ env / u s r / l o c a l / bin /
wal - e wal - f e t c h "%f " "%p"
После создания этого файла нужно запустить PostgreSQL. Через
небольшой интервал времени база станет полностью восстановленной.
Для удаления старых резервных копий (или вообще всех) используется
команда delete:
Листинг 11.31 Удаление резервных копий
Line 1 # удаление старых бэкапов старше
base_00000004000002DF000000A6_03626144
272
11.5. Утилиты для непрерывного резервного копирования
- $ envdi r / e tc /wal - e . d/ env wal - e d e l e t e - - con f irm b ef or e
base_00000004000002DF000000A6_03626144
- # удаление всех бэкапов
- $ envdi r / e tc /wal - e . d/ env wal - e d e l e t e - - con f irm ev e ryt h i ng
5 # удалить все старше последних 20 бэкапов
- $ envdi r / e tc /wal - e . d/ env wal - e d e l e t e - - con f irm r e t a i n 20
Без опции --confirm команды будут запускаться и показывать, что будет
удаляться, но фактического удаления не будет производиться (dry run).
Заключение
WAL-E помогает автоматизировать сбор резервных копий с PostgreSQL
и хранить их в достаточно дешевом и надежном хранилище Amazon S3
или Windows Azure.
Barman
Barman, как и WAL-E, позволяет создать систему для бэкапа и вос-
становления PostgreSQL на основе непрерывного резервного копирования.
Barman использует для хранения бэкапов отдельный сервер, который мо-
жет собирать бэкапы как с одного, так и с нескольких PostgreSQL баз
данных.
Установка и настройка
Рассмотрим простой случай с одним экземпляром PostgreSQL дин
сервер) и пусть его хост будет pghost. Наша задача автоматизировать
сбор и хранение бэкапов этой базы на другом сервере (его хост будет brhost
). Для взаимодействия эти два сервера должны быть полностью открыты
по SSH (доступ без пароля, по ключам). Для этого можно использовать
authorized_keys файл.
Листинг 11.32 Проверка подключения по SSH
Line 1 # Проверка подключения с сервера PostgreSQL ( pghost )
- $ s s h barman@brhost
- # Проверка подключения с сервера бэкапов ( b r h o s t )
- $ s s h p ostg res@p ghos t
Далее нужно установить на сервере для бэкапов barman. Сам barman
написан на python и имеет пару зависимостей: python 2.6+, rsync и python
библиотеки (argh, psycopg2, python-dateutil, distribute ). На Ubuntu все зави-
симости можно поставить одной командой:
Листинг 11.33 Установка зависимостей barman
273
11.5. Утилиты для непрерывного резервного копирования
Line 1 $ a p t itu d e i n s t a l l python - dev python - argh python - psycopg2
python - d a t e u t i l rsync python - s e t u p t o o l s
Далее нужно установить barman:
Листинг 11.34 Установка barman
Line 1 $ t ar - x zf barman - 2 . 1 . t ar . gz
- $ cd barman - 2 . 1 /
- $ . / s etup . py bu i ld
- $ sudo . / setu p . py i n s t a l l
Или используя PostgreSQL Community APT репозиторий:
Листинг 11.35 Установка barman
Line 1 $ apt - g e t i n s t a l l barman
Теперь перейдем к серверу с PostgreSQL. Для того, чтобы barman мог
подключаться к базе данных без проблем, нам нужно выставить настройки
доступа в конфигах PostgreSQL:
Листинг 11.36 Отредактировать в postgresql.conf
Line 1 l i s t e n _ a d r e s s = *
Листинг 11.37 Добавить в pg_hba.conf
Line 1 h o st a l l a l l b rhost /32 t r u s t
После этих изменений нужно перегрузить PostgreSQL. Теперь можем
проверить с сервера бэкапов подключение к PostgreSQL:
Листинг 11.38 Проверка подключения к базе
Line 1 $ p s ql - c SELECT v er s i o n ( ) -U p o s tg re s - h pghost
- v e r si on
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- PostgreSQL 9 . 3 . 1 on x86_64 - unknown - l i n ux - gnu , compiled by
gcc ( Ubuntu/ Linaro 4 . 7 . 2 - 2 ubuntu1 ) 4 . 7 . 2 , 64 - b i t
5 (1 row )
Далее создадим папку на сервере с бэкапами для хранения этих самых
бэкапов:
Листинг 11.39 Папка для хранения бэкапов
Line 1 $ sudo mkdir -p / s rv /barman
- $ sudo chown barman : barman / s r v /barman
274
11.5. Утилиты для непрерывного резервного копирования
Для настройки barman создадим /etc/barman.conf:
Листинг 11.40 barman.conf
Line 1 [ barman ]
- ; Main d i r e c t o r y
- barman_home = / s rv /barman
-
5 ; Log l o c a t i o n
- l o g _ f i l e = / var / l og /barman/barman . l o g
-
- ; D ef a ul t comp ression l e v e l : p o s s i b l e v alu e s a r e None (
d e f a u l t ) , bzip2 , g z ip or custom
- com pres sion = gzi p
10
- ; main PostgreSQL S e rv er c o n f i g u r a t i o n
- [ main ]
- ; Human r e ada b l e d e s c r i p t i o n
- d e s c r i p t i o n = "Main PostgreSQL Database "
15
- ; SSH op t io n s
- ssh_command = s s h p ostg res@p ghos t
-
- ; PostgreSQL conn e c t io n s t r i n g
20 c on ni nf o = hos t=pghost us e r=p o s t g r e s
Секция «main» (так мы назвали для barman наш PostgreSQL сервер)
содержит настройки для подключения к PostgreSQL серверу и базе. Про-
верим настройки:
Листинг 11.41 Проверка barman настроек
Line 1 $ barman show - s e r v e r main
- S er v er main :
- a c t i v e : t r ue
- d e s c r i p t i o n : Main PostgreSQL Database
5 ssh_command : s s h po stgre s@pg host
- c o n ni nf o : host=pghost us e r=p o s t g r e s
- backup_directory : / sr v /barman/main
- bas e backup s _dire c tory : / srv /barman/main/ base
- w a l s _ di r ec t or y : / s r v /barman/main/ wals
10 incoming_wals_directory : / sr v /barman/main/ incoming
- l o c k _ f i l e : / s rv /barman/main/main . l oc k
- c ompr essi on : gzi p
- cust o m _compr e s sion_fi l t er : None
- cus t om_dec o mpres s i on_fi l ter : None
15 r e t e n ti on _ p o li cy : None
- wal_re t e n t i on_policy : None
275
11.5. Утилиты для непрерывного резервного копирования
- pre_backup_script : None
- post_backup_script : None
- cur rent_ x log : None
20 last_shipped_wal : None
- archive_command : None
- se r v e r _ t x t_ v e r s i on : 9 . 3 . 1
- data_directory : / var / l i b / p o s t g r e s q l /9. 3 / main
- archive_mode : o f f
25 c o n f i g _ f i l e : / et c / p o s t g r e s q l / 9 . 3 / main/ p o s t g r e s q l .
conf
- h b a _f il e : / e t c / p o s t g r e s q l / 9 .3 / main/pg_hba . c o n f
- i d e n t _ f i l e : / et c / p o s t g r e s q l / 9 . 3 / main/ pg_ident . conf
-
- # barman check main
30 S er v er main :
- ssh : OK
- PostgreSQL : OK
- archive_mode : FAILED ( p l e a s e s e t i t to on )
- archive_command : FAILED ( p l e a s e s e t i t a cc o r d i n g l y
to documentation )
35 d i r e c t o r i e s : OK
- c ompr essi on s e t t i n g s : OK
Все хорошо, вот только PostgreSQL не настроен. Для этого на сервере
с PostgreSQL отредактируем конфиг базы:
Листинг 11.42 Настройка PostgreSQL
Line 1 w a l _ l e vel = hot_standby # a rc h i v e для PostgreSQL < 9 .0
- archive_mode = on
- archive_command = r s y nc - a %p barman@brhost :
INCOMING_WALS_DIRECTORY/%f
где INCOMING_WALS_DIRECTORY — директория для складывания
WAL-логов. Её можно узнать из вывода команды barman show-server main
(листинг 11.41, указано /srv/barman/main/incoming). После изменения на-
строек нужно перегрузить PostgreSQL. Теперь проверим статус на сервере
бэкапов:
Листинг 11.43 Проверка
Line 1 $ barman check main
- S er v er main :
- ssh : OK
- PostgreSQL : OK
5 archive_mode : OK
- archive_command : OK
- d i r e c t o r i e s : OK
276
11.5. Утилиты для непрерывного резервного копирования
- c ompr essi on s e t t i n g s : OK
Все готово. Для добавления нового сервера процедуру потребуется по-
вторить, а в barman.conf добавить новый сервер.
Создание бэкапов
Получение списка серверов:
Листинг 11.44 Список серверов
Line 1 $ barman l i s t - s e r v e r
- main - Main PostgreSQL Database
Запуск создания резервной копии PostgreSQL (сервер указывается по-
следним параметром):
Листинг 11.45 Создание бэкапа
Line 1 $ barman backup main
- S t a r t in g backup f o r s e r v e r main in / srv /barman/main/ base
/20121109 T090806
- Backup s t a r t a t x l o g l o c a t i o n : 0/3000020
(000 000010000000000000003 , 00000020)
- Copying f i l e s .
5 Copy done .
- Asking PostgreSQL s e r v e r to f i n a l i z e the backup .
- Backup end at x l o g l o c a t i o n : 0/30000D8
(000 000010000000000000003 , 000000D8)
- Backup completed
Такую задачу лучше выполнять раз в сутки (добавить в cron). Посмот-
реть список бэкапов для указаной базы:
Листинг 11.46 Список бэкапов
Line 1 $ barman l i s t - backup main
- main 20121110 T091608 - F r i Nov 10 0 9 : 2 0 : 5 8 2012 - S i z e : 1 .0
GiB - WAL S i z e : 4 4 6 . 0 KiB
- main 20121109 T090806 - F r i Nov 9 0 9 : 0 8 : 1 0 2012 - S i z e : 2 3 .0
MiB - WAL S i z e : 4 7 7 . 0 MiB
Более подробная информация о выбраной резервной копии:
Листинг 11.47 Информация о выбраной резервной копии
Line 1 $ barman show - backup main 20121110 T091608
- Backup 20121109 T091608 :
- Se r v er Name : main
277
11.5. Утилиты для непрерывного резервного копирования
- S t a t u s : : DONE
5 PostgreSQL V ersio n : 90201
- PGDATA d i r e c t o r y : / var / l i b / p o s t g r e s q l / 9 . 3 / main
-
- Base backup i n f orm a ti on :
- Disk usage : 1 .0 GiB
10 Timeline : 1
- Begin WAL : 00000001000000000000008C
- End WAL : 000000010000000000000092
- WAL number : 7
- Begin time : 2012 -11 -10 0 9: 1 6 : 0 8 .8 56 8 8 4
15 End time : 2012 -11 -10 0 9: 20 : 5 8 . 4 78 53 1
- Begin O ff s e t : 32
- End O f fs e t : 3576096
- Begin XLOG : 0/8 C000020
- End XLOG : 0/92369120
20
- WAL i n f or m a tio n :
- No o f f i l e s : 1
- Disk usage : 446 . 0 KiB
- Last a v a i l a b l e : 000000010000000000000093
25
- Catalog i nfo r m ati o n :
- Pr e v ious Backup : 20121109 T090806
- Next Backup : - ( t h i s i s the l a t e s t base backup )
Также можно сжимать WAL-логи, которые накапливаются в каталогах
командой «cron»:
Листинг 11.48 Архивирование WAL-логов
Line 1 $ barman cron
- Pr o ce s s in g xlog segments f o r main
- 000000010000000000000001
- 000000010000000000000002
5 000000010000000000000003
- 0 00000010 0 0000 0000 0000 03.0 0000 020. backup
- 000000010000000000000004
- 000000010000000000000005
- 000000010000000000000006
Эту команду требуется добавлять в crontab. Частота выполнения дан-
ной команды зависит от того, как много WAL-логов накапливается (чем
больше файлов - тем дольше она выполняется). Barman может сжи-
мать WAL-логи через gzip, bzip2 или другой компрессор данных (коман-
ды для сжатия и распаковки задаются через custom_compression_filter и
custom_decompression_filter соответственно). Также можно активировать
компрессию данных при передачи по сети через опцию network_compression
278
11.5. Утилиты для непрерывного резервного копирования
(по умолчанию отключена). Через опции bandwidth_limit (по умолчанию
0, ограничений нет) и tablespace_bandwidth_limit возможно ограничить ис-
пользования сетевого канала.
Для восстановления базы из бэкапа используется команда recover:
Листинг 11.49 Восстановление базы
Line 1 $ barman r e co v e r - - remote - ssh - command " ss h p ostgr es@p ghos t "
main 20121109 T090806 / var / l i b / p o s t g r e s q l /9 . 3 / main
- S t a r t in g remote r e s t o r e f o r s e r v e r main using backup
20121109 T090806
- De s ti n ati o n d i r e c t o r y : / var / l i b / p o s t g r e s q l /9. 3 / main
- Copying the base backup .
5 Copying r e q u ir ed wal segments .
- The archive_command was s e t to f a l s e to preve nt data
l o s s e s .
-
- Your PostgreSQL s e r v e r has been s u c c e s s f u l l y pre par ed f o r
re c ove r y !
-
10 P le as e revie w network and a r ch iv e r e l a t e d s e t t i n g s i n the
PostgreSQL
- c o n f i g u r a t i o n f i l e b e f o r e s t a r t i n g the j u s t r eco v e red
i n s t a n c e .
-
- WARNING: B e f o re s t a r t i n g up the r e c ove r ed PostgreSQL se r ver ,
- p le a s e review a l s o the s e t t i n g s o f the f o l l o w i n g
c o n f i g u r a t i o n
15 op t ion s as they might i n t e r f e r e with your c ur r ent r e c ove r y
attempt :
-
- data_directory = / var / l i b / p o s t g r e s q l / 9 . 3 / main
# use data i n another d i r e c t o r y
- e xt e r n a l _p i d _ fi le = / var /run / p o s t g r e s q l / 9.3 - main . pid
# wr i te an e x t ra PID f i l e
- hb a _ f il e = / e t c / p o s t g r e s q l / 9 .3 / main/pg_hba . c o n f #
host - based a u t h e n t i c a t i o n f i l e
20 i d e n t _ f i l e = / e t c / p o s t g r e s q l / 9 . 3/ main/ pg_ident . c on f
# id e nt c o n f i g u r a t i o n f i l e
Barman может восстановить базу из резервной копии на удаленном сер-
вере через SSH (для этого есть опция remote-ssh-command). Также barman
может восстановить базу, используя PITR: для этого используются опции
target-time (указывается время) или target-xid (id транзакции).
279
11.5. Утилиты для непрерывного резервного копирования
Заключение
Barman помогает автоматизировать сбор и хранение резервных копий
PostgreSQL данных на отдельном сервере. Утилита проста, позволяет хра-
нить и удобно управлять бэкапами нескольких PostgreSQL серверов.
Pg_arman
Pg_arman менеджер резервного копирования и восстановления для
PostgreSQL 9.5 или выше. Это ответвление проекта pg_arman, изначаль-
но разрабатываемого в NTT. Теперь его разрабатывает и поддерживает
Мишель Пакье. Утилита предоставляет следующие возможности:
Резервное копирование во время работы базы данных, включая таб-
личные пространства, с помощью всего одной команды;
Восстановление из резервной копии всего одной командой, с нестан-
дартными вариантами, включая использование PITR;
Поддержка полного и дифференциального копирования;
Управление резервными копиями со встроенными каталогами;
Использование
Сначала требуется создать «каталог резервного копирования», в ко-
тором будут храниться файлы копий и их метаданные. До инициали-
зации этого каталога рекомендуется настроить параметры archive_mode
и archive_command в postgresql .conf. Если переменные инициализирова-
ны, pg_arman может скорректировать файл конфигурации. В этом случае
потребуется задать путь к кластеру баз данных: переменной окружения
PGDATA или через параметр -D/--pgdata.
Листинг 11.50 init
Line 1 $ pg_arman i n i t -B / path / to /backup/
После этого возможен один из следующих вариантов резервного копи-
рования:
Полное резервное копирование (копируется весь кластер баз дан-
ных);
Листинг 11.51 backup
Line 1 $ pg_arman backup - - backup - mode=f u l l
- $ pg_arman v a l i d a t e
-
280
11.5. Утилиты для непрерывного резервного копирования
Дифференциальное резервное копирование: копируются только фай-
лы или страницы, изменённые после последней проверенной копии.
Для этого выполняется сканирование записей WAL от позиции по-
следнего копирования до LSN выполнения pg_start_backup и все из-
менённые блоки записываются и отслеживаются как часть резервной
копии. Так как просканированные сегменты WAL должны находить-
ся в архиве WAL, последний сегмент, задействованный после запуска
pg_start_backup, должен быть переключен принудительно;
Листинг 11.52 backup
Line 1 $ pg_arman backup - - backup - mode=page
- $ pg_arman v a l i d a t e
-
После резервного копирования рекомендуется проверять файлы копий
как только это будет возможно. Непроверенные копии нельзя использо-
вать в операциях восстановления и резервного копирования.
До начала восстановления через pg_arman PostgreSQL кластер должен
быть остановлен. Если кластер баз данных всё ещё существует, коман-
да восстановления сохранит незаархивированный журнал транзакций и
удалит все файлы баз данных. После восстановления файлов pg_arman
создаёт recovery.conf в $PGDATA каталоге. Этот конфигурационный файл
содержит параметры для восстановления. После успешного восстановле-
ния рекомендуется при первой же возможности сделать полную резерв-
ную копию. Если ключ --recovery-target-timeline не задан, целевой точкой
восстановления будет TimeLineID последней контрольной точки в файле
($PGDATA/global/pg_control). Если файл pg_control отсутствует, целевой
точкой будет TimeLineID в полной резервной копии, используемой при вос-
становлении.
Листинг 11.53 restore
Line 1 $ pg_ctl sto p -m immediate
- $ pg_arman r e s t o r e
- $ pg_ctl s t a r t
Pg_arman имеет ряд ограничений:
Требуются права чтения каталога баз данных и записи в каталог ре-
зервного копирования. Обычно для этого на сервере БД требуется
смонтировать диск, где размещён каталог резервных копий, исполь-
зуя NFS или другую технологию;
Основные версии pg_arman и сервера должны совпадать;
Размеры блоков pg_arman и сервера должны совпадать;
281
11.6. Заключение
Если в каталоге с журналами сервера или каталоге с архивом WAL
оказываются нечитаемые файлы/каталоги, резервное копирование
или восстановление завершится сбоем вне зависимости от выбран-
ного режима копирования;
11.6 Заключение
В любом случае, усилия и время, затраченные на создание оптималь-
ной системы создания бэкапов, будут оправданы. Невозможно предуга-
дать когда произойдут проблемы с базой данных, поэтому бэкапы должны
быть настроены для PostgreSQL (особенно, если это продакшн система).
282
12
Стратегии масштабирования для
PostgreSQL
В конце концов, все решают
люди, не стратегии
Ларри Боссиди
12.1 Введение
Многие разработчики крупных проектов сталкиваются с проблемой,
когда один-единственный сервер базы данных никак не может справиться
с нагрузками. Очень часто такие проблемы происходят из-за неверного
проектирования приложения (плохая структура БД для приложения, от-
сутствие кеширования). Но в данном случае пусть у нас есть «идеальное»
приложение, для которого оптимизированы все SQL запросы, использу-
ется кеширование, PostgreSQL настроен, но все равно не справляется с
нагрузкой. Такая проблема может возникнуть как на этапе проектирова-
ния, так и на этапе роста приложения. И тут возникает вопрос: какую
стратегию выбрать при возникновении подобной ситуации?
Если Ваш заказчик готов купить супер сервер за несколько тысяч дол-
ларов по мере роста десятков тысяч и т. д.), чтобы сэкономить время
разработчиков, но сделать все быстро, можете дальше эту главу не читать.
Но такой заказчик мифическое существо и, в основном, такая проблема
ложится на плечи разработчиков.
Суть проблемы
Для того, чтобы сделать какой-то выбор, необходимо знать суть про-
блемы. Существуют два предела, в которые могут уткнуться сервера баз
данных:
283
12.2. Проблема чтения данных
Ограничение пропускной способности чтения данных;
Ограничение пропускной способности записи данных;
Практически никогда не возникает одновременно две проблемы, по
крайне мере, это маловероятно (если вы, конечно, не Twitter или Facebook
пишете). Если вдруг такое происходит возможно, система неверно спро-
ектирована, и её реализацию следует пересмотреть.
12.2 Проблема чтения данных
Проблема с чтением данных обычно начинается, когда СУБД не в со-
стоянии обеспечить то количество выборок, которое требуется. В основном
такое происходит в блогах, новостных лентах и т. д. Хочу сразу отметить,
что подобную проблему лучше решать внедрением кеширования, а потом
уже думать как масштабировать СУБД.
Методы решения
PgPool-II v.3 + PostgreSQL v.9 с Streaming Replication отличное ре-
шение для масштабирования на чтение, более подробно можно озна-
комиться по ссылке. Основные преимущества:
– Низкая задержка репликации между мастером и слейвом;
– Производительность записи падает незначительно;
– Отказоустойчивость (failover);
– Пулы соединений;
– Интеллектуальная балансировка нагрузки проверка задерж-
ки репликации между мастером и слейвом (сам проверяет
pg_current_xlog_location и pg_last_xlog_receive_location);
– Добавление слейвов СУБД без остановки pgpool-II;
– Простота в настройке и обслуживании;
PgPool-II v.3 + PostgreSQL с Slony/Londiste/Bucardo
аналогично предыдущему решению, но с использованием
Slony/Londiste/Bucardo. Основные преимущества:
– Отказоустойчивость (failover);
– Пулы соединений;
– Интеллектуальная балансировка нагрузки проверка задержки
репликации между мастером и слейвом;
– Добавление слейв СУБД без остановки pgpool-II;
– Можно использовать Postgresql ниже 9 версии;
Citus подробнее можно прочитать в «6.5 Citus» главе;
Postgres-X2 подробнее можно прочитать в «6.3 Postgres-X2» главе;
Postgres-XL подробнее можно прочитать в «6.4 Postgres-XL» главе;
284
12.3. Проблема записи данных
12.3 Проблема записи данных
Обычно такая проблема возникает в системах, которые производят
анализ больших объемов данных апример аналог Google Analytics). Дан-
ные активно пишутся и мало читаются (или читается только суммарный
вариант собранных данных).
Методы решения
Один из самых популярных методов решения проблемы размазать
нагрузку по времени с помощью систем очередей.
PgQ это система очередей, разработанная на базе PostgreSQL. Раз-
работчики компания Skype. Используется в Londiste (подробнее
«5.6 Londiste»). Особенности:
– Высокая производительность благодаря особенностям
PostgreSQL;
– Общая очередь, с поддержкой нескольких обработчиков и
нескольких генераторов событий;
– PgQ гарантирует, что каждый обработчик увидит каждое собы-
тие как минимум один раз;
– События достаются из очереди «пачками» (batches);
– Чистое API на SQL функциях;
– Удобный мониторинг;
Citus подробнее можно прочитать в «6.5 Citus» главе;
Postgres-X2 подробнее можно прочитать в «6.3 Postgres-X2» главе;
Postgres-XL подробнее можно прочитать в «6.4 Postgres-XL» главе;
12.4 Заключение
В данной главе показаны только несколько возможных вариантов ре-
шения задач масштабирования PostgreSQL. Таких стратегий существует
огромное количество и каждая из них имеет как сильные, так и слабые
стороны. Самое важное то, что выбор оптимальной стратегии масштабиро-
вания для решения поставленных задач остается на плечах разработчиков
и/или администраторов СУБД.
285
13
Утилиты для PostgreSQL
Ум всегда занят
исследованием чего-либо
Цицерон
13.1 Введение
В данной главе собраны полезные утилиты для PostgreSQL, которые
не упоминались в других разделах книги.
13.2 Pgcli
Pgcli интерфейс командной строки для PostgreSQL с автозаполнени-
ем и подсветкой синтаксиса. Написан на Python.
Рис. 13.1: Pgcli автозаполнение
286
13.3. Pgloader
13.3 Pgloader
Pgloader консольная утилита для переноса данных с CSV файлов,
HTTP ресурсов, SQLite, dBase или MySQL баз данных в PostgreSQL. Для
быстрой загрузки данных используется COPY протокол. Pgloader содер-
жит модули для преобразование данных, которые позволяют преобразо-
вывать данные во время переноса апример, преобразование набора цифр
в IP адрес или разбить строку на два текстовых поля).
13.4 Postgres.app
Postgres.app полнофункциональный PostgreSQL, который упакован в
качестве стандартного Mac приложения, поэтому работает только на Mac
OS системах. Приложение имеет красивый пользовательский интерфейс
и работает в системной строке меню.
13.5 pgAdmin
pgAdmin инструмент c графическим интерфейсом для управления
PostgreSQL и производных от него баз данных. Он может быть запу-
щен в качестве десктоп или веб-приложения. Написан на Python ис-
пользованием Flask фреймворка) и JavaScript использованием jQuery и
Bootstrap).
Существуют альтернативные программные продукты для PostgreSQL
ак платные, так и бесплатные). Вот примеры бесплатных альтернатив:
DBeaver;
DBGlass;
Metabase;
pgModeler;
Pgweb;
Postbird;
SQL Tabs;
13.6 PostgREST
PostgREST инструмент создания HTTP REST API для PostgreSQL
базы. Написан на Haskell.
13.7 Ngx_postgres
Ngx_postgres модуль для Nginx, который позволяет напрямую рабо-
тать с PostgreSQL базой. Ответы генерируется в формате RDS (Resty DBD
287
13.8. Заключение
Stream), поэтому он совместим с ngx_rds_json, ngx_rds_csv и ngx_drizzle
модулями.
13.8 Заключение
В данной главе рассмотрено лишь несколько полезных утилит для
PostgreSQL. Каждый день для это базы данных появляется все больше
интересных инструментов, которые улучшают, упрощают или автомати-
зируют работу с данной базой данных.
288
14
Полезные мелочи
Быстро найти правильный
ответ на трудный вопрос ни
с чем не сравнимое
удовольствие
Макс Фрай. Обжора-Хохотун
14.1 Введение
Иногда возникают очень интересные проблемы по работе с PostgreSQL,
которые при нахождении ответа поражают своей лаконичностью, красо-
той и простым исполнением. В данной главе я решил собрать интересные
методы решения разных проблем, с которыми сталкиваются люди при ра-
боте с PostgreSQL.
14.2 Мелочи
Размер объектов в базе данных
Данный запрос показывает размер объектов в базе данных (например,
таблиц и индексов).
Скачать snippets/biggest_relations.sql
Line 1 SELECT nspname | | . | | relname AS " r e l a t i o n " ,
- pg_size_pretty ( p g _r el at io n_si z e (C. oid ) ) AS " s i z e "
- FROM pg_clas s C
- LEFT JOIN pg_namespace N ON (N. oid = C. r elnames pace )
5 WHERE nspname NOT IN ( pg_catalog , information_schema )
- ORDER BY p g _ r el at io n_si z e (C. oid ) DESC
- LIMIT 2 0;
289
14.2. Мелочи
Пример вывода:
Листинг 14.1 Поиск самых больших объектов в БД. Пример вывода
Line 1 r e l a t i o n | s i z e
- - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- p ub li c . accoun t s | 326 MB
- p ub li c . accounts_pkey | 44 MB
5 p ub li c . h i s t o r y | 592 kB
- p ub li c . t el l e r s_ p k e y | 16 kB
- p ub li c . branches_pkey | 16 kB
- p ub li c . t e l l e r s | 16 kB
- p ub li c . branc hes | 8192 byt e s
Размер самых больших таблиц
Данный запрос показывает размер самых больших таблиц в базе дан-
ных.
Скачать snippets/biggest_tables.sql
Line 1 SELECT nspname | | . | | relname AS " r e l a t i o n " ,
- pg_size_pretty ( p g _tot a l _re l a ti on_ s i z e (C. oid ) ) AS "
t o t a l _ s i z e "
- FROM pg_clas s C
- LEFT JOIN pg_namespace N ON (N. oid = C. r elnames pace )
5 WHERE nspname NOT IN ( pg_catalog , information_schema )
- AND C. r e l k i n d <> i
- AND nspname !~ ^pg_toast
- ORDER BY p g _ to tal _ r el at ion _ s iz e (C. oid ) DESC
- LIMIT 2 0;
Пример вывода:
Листинг 14.2 Размер самых больших таблиц. Пример вывода
Line 1 r e l a t i o n | t o t a l _ s i z e
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - -
- p ub li c . a c ti o n s | 4249 MB
- p ub li c . produ c t _hist o ry_rec o rds | 197 MB
5 p ub li c . product_updates | 52 MB
- p ub li c . import_products | 34 MB
- p ub li c . produc t s | 29 MB
- p ub li c . v i s i t s | 25 MB
290
14.2. Мелочи
«Средний» count
Данный метод позволяет узнать приблизительное количество записей
в таблице. Для огромных таблиц этот метод работает быстрее, чем обык-
новенный count.
Скачать snippets/count_estimate.sql
Line 1 CREATE FUNCTION count_estimate ( query t e xt ) RETURNS i n t e g e r
AS $$
- DECLARE
- r e c r e cor d ;
- rows i n t e g e r ;
5 BEGIN
- FOR re c IN EXECUTE EXPLAIN | | query LOOP
- rows := s u b s t r i n g ( r ec . "QUERY PLAN" FROM rows = ( [ [ :
d i g i t : ] ] + ) ) ;
- EXIT WHEN rows IS NOT NULL;
- END LOOP;
10
- RETURN rows ;
- END;
- $$ LANGUAGE p l p g s q l VOLATILE STRICT ;
Пример:
Листинг 14.3 «Средний» count. Пример
Line 1 CREATE TABLE f o o ( r double p r e c i s i o n ) ;
- INSERT INTO f o o SELECT random ( ) FROM g e n e r a t e _ s e r i e s ( 1 ,
1000) ;
- ANALYZE fo o ;
-
5 # SELECT count ( * ) FROM f o o WHERE r < 0 . 1 ;
- count
- - - - - - - -
- 92
- (1 row )
10
- # SELECT count_estimate ( SELECT * FROM f oo WHERE r < 0 .1 ) ;
- count_estimate
- - - - - - - - - - - - - - - - -
- 94
15 (1 row )
Случайное число из диапазона
Данный метод позволяет взять случайное число из указаного диапазо-
на (целое или с плавающей запятой).
291
14.2. Мелочи
Скачать snippets/random_from_range.sql
Line 1 CREATE OR REPLACE FUNCTION random ( numeric , numeric )
- RETURNS numeric AS
- $$
- SELECT ( $1 + ( $2 - $1 ) * random ( ) ) : : numeric ;
5 $$ LANGUAGE s q l VOLATILE;
Пример:
Листинг 14.4 Случайное число из диапазона. Пример
Line 1 SELECT random ( 1 , 10 ) : : i n t , random ( 1 , 1 0) ;
- random | random
- - - - - - - - - + - - - - - - - - - - - - - - - - - -
- 6 | 5.11675184825435
5 (1 row )
-
- SELECT random ( 1 , 1 0) : : int , random (1 , 10 ) ;
- random | random
- - - - - - - - - + - - - - - - - - - - - - - - - - - -
10 7 | 1.37060070643201
- (1 row )
Алгоритм Луна
Алгоритм Луна или формула Луна алгоритм вычисления контроль-
ной цифры, получивший широкую популярность. Он используется, в част-
ности, при первичной проверке номеров банковских пластиковых карт, но-
меров социального страхования в США и Канаде. Алгоритм был разрабо-
тан сотрудником компании «IBM» Хансом Петером Луном и запатентован
в 1960 году.
Контрольные цифры вообще и алгоритм Луна в частности предназна-
чены для защиты от случайных ошибок, а не преднамеренных искажений
данных.
Алгоритм Луна реализован на чистом SQL. Обратите внимание, что
эта реализация является чисто арифметической.
Скачать snippets/luhn_algorithm.sql
Line 1 CREATE OR REPLACE FUNCTION l u h n _ v e r i f y ( i n t 8 ) RETURNS BOOLEAN
AS $$
- - - Take the sum o f the
- - - doubled d i g i t s and the even - numbered undoubled d i g i t s ,
and s e e i f
- - - the sum i s e v e n ly d i v i s i b l e by ze r o .
5 SELECT
292
14.2. Мелочи
- - - Doubled d i g i t s might in turn be two d i g i t s . In
tha t case ,
- - - we must add each d i g i t i n d i v i d u a l l y r a th er than
adding the
- - - doubled d i g i t v a l u e t o the sum . I e i f the
o r i g i n a l d i g i t was
- - - 6 the doubled r e s u l t was 12 and we must add
1+2 to the
10 - - sum r a t h er than 1 2 .
- MOD(SUM( dou b led_d i git / INT8 10 + doub l ed_di g i t %
INT8 10 ) , 10) = 0
- FROM
- - - Double odd - numbered d i g i t s ( counti n g l e f t with
- - - l e a s t s i g n i f i c a n t as z e ro ) . I f the doubled d i g i t s end up
15 - - having v alu e s
- - - > 10 ( i e they r e two d i g i t s ) , add t h e i r d i g i t s t o g et h er .
- (SELECT
- - - Extr act d i g i t n counti n g l e f t from l e a s t
s i g n i f i c a n t
- - - as z e r o
20 MOD( ( $1 : : i n t 8 / (10^n ) : : i n t 8 ) , 1 0 : : i n t 8 )
- - - Double odd - numbered d i g i t s
- * (MOD( n , 2 ) + 1)
- AS do ubled_ d igit
- FROM g e n e r a t e _ s e r i e s ( 0 , CEIL (LOG( $1 ) ) : : INTEGER -
1) AS n
25 ) AS doubl e d _ d i g i t s ;
-
- $$ LANGUAGE SQL
- IMMUTABLE
- STRICT;
30
- COMMENT ON FUNCTION luhn_verify ( i n t 8 ) IS Return t r u e i f f
the l a s t d i g i t of the
- inpu t i s a c o r r e c t check d i g i t f o r the r e s t o f the input
ac c o rd in g to Luhn s
- a l g o r i t h m . ;
- CREATE OR REPLACE FUNCTION luhn _ gener a t e_che c kdigi t ( i n t 8 )
RETURNS i n t 8 AS $$
35 SELECT
- - - Add the d i g i t s , doubl i n g even - numbered d i g i t s (
coun t ing l e f t
- - - with l e as t - s i g n i f i c a n t as z e r o ) . Subtr a c t the
remainder o f
- - - d i v i di n g the sum by 10 from 10 , and take the
remainder
- - - o f d i v i d i ng that by 10 in turn .
293
14.2. Мелочи
40 (( INT8 10 - SUM( dou b led_d i git / INT8 10 +
dou b led_di g it % INT8 10 ) %
- INT8 10 ) % INT8 10 ) : : INT8
- FROM (SELECT
- - - Extr act d i g i t n counti n g l e f t from l e a s t
s i g n i f i c a n t \
- - - as z e r o
45 MOD( ( $1 : : i nt 8 / (10^n ) : : i n t 8 ) , 1 0 : : i n t 8 )
- - - double even - numbered d i g i t s
- * (2 - MOD( n , 2 ) )
- AS do ubled_ d igit
- FROM g e n e r a t e _ s e r i e s ( 0 , CEIL (LOG( $1 ) ) : : INTEGER - 1)
AS n
50 ) AS doubl e d _ d i g i t s ;
-
- $$ LANGUAGE SQL
- IMMUTABLE
- STRICT;
55
- COMMENT ON FUNCTION lu h n_gene r ate_c h e ckdig i t ( i n t 8 ) IS For
the inp ut
- value , g ene r a te a check d i g i t a cc ordi n g to Luhn s algorit h m
;
- CREATE OR REPLACE FUNCTION luhn_generate ( i nt 8 ) RETURNS in t 8
AS $$
- SELECT 10 * $1 + luhn_ g enerat e _check d igit ( $1 ) ;
60 $$ LANGUAGE SQL
- IMMUTABLE
- STRICT;
-
- COMMENT ON FUNCTION luhn_generate ( i n t 8 ) IS Append a check
d i g i t g e n e r a t e d
65 a cc ordi n g to Luhn s algorith m to the inp ut v a l u e . The inp u t
value must be no
- g r e a t e r than ( maxbigint /10) . ;
- CREATE OR REPLACE FUNCTION luhn_strip ( i nt 8 ) RETURNS i n t 8 AS
$$
- SELECT $1 / 10 ;
- $$ LANGUAGE SQL
70 IMMUTABLE
- STRICT;
-
- COMMENT ON FUNCTION luhn_ s t r i p ( i n t 8 ) IS S t r i p the l e a s t
s i g n i f i c a n t d i g i t from
- the inp ut v a l u e . Intended f o r use when s t r i p p i n g the check
d i g i t from a number
75 i nc l u d i n g a Luhn s algorithm check d i g i t . ;
294
14.2. Мелочи
Пример:
Листинг 14.5 Алгоритм Луна. Пример
Line 1 S e l e c t l u h n _ v e r i f y ( 4992 7398716 ) ;
- luhn_verify
- - - - - - - - - - - - - -
- t
5 (1 row )
-
- S e l e c t l u h n _ v e r i f y (499 273 9871 4) ;
- luhn_verify
- - - - - - - - - - - - - -
10 f
- (1 row )
Выборка и сортировка по данному набору данных
Выбор данных по определенному набору данных можно сделать с по-
мощью обыкновенного IN. Но как сделать подобную выборку и отсорти-
ровать данные в том же порядке, в котором передан набор данных? На-
пример:
Дан набор: (2,6,4,10,25,7,9). Нужно получить найденные данные в та-
ком же порядке т. е. 2 2 2 6 6 4 4
Скачать snippets/order_like_in.sql
Line 1 SELECT foo . * FROM fo o
- JOIN (SELECT i d . val , row_number ( ) ove r ( ) FROM (VALUES( 3) , ( 2 )
, (6 ) , ( 1 ) , ( 4 ) ) AS
- i d ( v al ) ) AS i d
- ON ( f oo . c a talog_id = i d . v a l ) ORDER BY row_number ;
где
VALUES(3),(2),(6),(1),(4) наш набор данных
foo таблица, из которой идет выборка
foo.catalog_id — поле, по которому ищем набор данных (замена foo.
catalog_id IN(3,2,6,1,4) )
Quine запрос который выводит сам себя
Куайн, квайн (англ. quine) компьютерная программа (частный слу-
чай метапрограммирования), которая выдаёт на выходе точную копию
своего исходного текста.
Скачать snippets/quine.sql
295
14.2. Мелочи
Line 1 s e l e c t a | | from ( s e l e c t | | q u o t e _ l i t e r a l ( a ) | | b | | ,
| | q u o t e _ l i t e r a l ( b ) | | : : t ex t as b ) as quine from
- ( s e l e c t s e l e c t a | | from ( s e l e c t | | q u o t e _ l i t e r a l ( a )
| | b | | , | | q u o t e _ l i t e r a l (b ) | | : : t e xt as b ) as
- quine : : te x t as a , : : t ex t as a : : t e xt as b ) as quine ;
Поиск дубликатов индексов
Запрос находит индексы, созданные на одинаковый набор столбцов (та-
кие индексы эквивалентны, а значит бесполезны).
Скачать snippets/duplicate_indexes.sql
Line 1 SELECT pg_size_pretty (sum( p g _ re la ti on _siz e ( idx ) ) : : b i g i n t ) AS
s i z e ,
- ( array_agg ( idx ) ) [ 1 ] AS idx1 , ( array_agg ( idx ) ) [ 2 ] AS
idx2 ,
- ( array_agg ( idx ) ) [ 3 ] AS idx3 , ( array_agg ( idx ) ) [ 4 ] AS
id x4
- FROM (
5 SELECT i n d e x r e l i d : : r e g c l a s s AS idx , ( i n d r e l i d : : t ex t | | E
\n | | i n d c l a s s : : t e xt | | E \n | | indkey : : t ex t | | E \n | |
- c o a l e s c e ( i n de xp rs : :
te xt , ) | | E \n | | c o a l e s c e ( indpre d : : text , ) ) AS KEY
- FROM pg_index ) sub
- GROUP BY KEY HAVING count ( *)>1
- ORDER BY sum( p g _ re la ti on _s ize ( i dx ) ) DESC;
Размер и статистика использования индексов
Скачать snippets/indexes_statustic.sql
Line 1 SELECT
- t . tablename ,
- indexname ,
- c . r e l t u p l e s AS num_rows ,
5 pg_size_pretty ( p g _r el at io n_si z e ( quote_ident ( t . tablename )
: : t e xt ) ) AS ta ble_ s i ze ,
- pg_size_pretty ( p g _r el at io n_si z e ( quote_ident ( i nde xre lna me
) : : t e xt ) ) AS index_s ize ,
- CASE WHEN x . is_unique = 1 THEN Y
- ELSE N
- END AS UNIQUE,
10 idx_scan AS number_of_scans ,
- idx_tup_read AS tuples_read ,
- idx_tup_fetch AS t u p les_ f et ch ed
- FROM pg_tables t
296
14.2. Мелочи
- LEFT OUTER JOIN pg_class c ON t . tablename=c . relname
15 LEFT OUTER JOIN
- (SELECT i n d r e l i d ,
- max(CAST( i n d is un i q u e AS i n t e g e r ) ) AS is_unique
- FROM pg_index
- GROUP BY i n d r e l i d ) x
20 ON c . o i d = x . i n d r e l i d
- LEFT OUTER JOIN
- ( SELECT c . relname AS ctablename , i p g . relname AS
indexname , x . i n dn a tt s AS number_of_columns , idx_scan ,
idx_tup_read , idx_tup_fetch , indexrelname FROM pg_index x
- JOIN pg_cla ss c ON c . o i d = x . i n d r e l i d
- JOIN pg_cla ss i pg ON ip g . oid = x . i n d e x r e l i d
25 JOIN pg_stat_all_indexes p s a i ON x . i n d e x r e l i d =
p s a i . i n d e x r e l i d )
- AS f o o
- ON t . tablename = foo . ctablename
- WHERE t . schemaname= p u b l ic
- ORDER BY 1 , 2 ;
Размер распухания (bloat) таблиц и индексов в базе данных
Запрос, который показывает «приблизительный» bloat (раздутие) таб-
лиц и индексов в базе:
Скачать snippets/bloating.sql
Line 1 WITH co n sta n ts AS (
- SELECT cu r re n t_ s et t in g ( b l o c k_si z e ) : : numeric AS bs , 23 AS
hdr , 4 AS ma
- ) , b l oa t_ info AS (
- SELECT
5 ma, bs , schemaname , tablename ,
- ( datawidth+(hdr+ma- ( c a s e when hdr%ma=0 THEN ma ELSE hdr%
ma END) ) ) : : numeric AS datahdr ,
- ( maxfracsum *( n ull h dr+ma- ( c as e when n u ll h dr%ma=0 THEN ma
ELSE n u ll h dr%ma END) ) ) AS n u llh d r2
- FROM (
- SELECT
10 schemaname , tablename , hdr , ma, bs ,
- SUM( ( 1 - n ul l _f r ac ) * avg_width ) AS datawidth ,
- MAX( n u ll _ f r ac ) AS maxfracsum ,
- hdr+(
- SELECT 1+count ( * ) /8
15 FROM pg_stats s2
- WHERE n ul l _ fr a c <>0 AND s2 . schemaname = s . schemaname
AND s2 . tablename = s . tablename
- ) AS n ul l h dr
- FROM pg_stats s , c o ns ta n ts
297
14.2. Мелочи
- GROUP BY 1 , 2 , 3 , 4 , 5
20 ) AS f o o
- ) , t a b l e _ b l oa t AS (
- SELECT
- schemaname , tablename , cc . r e lpa g es , bs ,
- CEIL ( ( cc . r e l t u p l e s * ( ( datahdr+ma-
25 (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END) )+
nu l lhd r 2 +4) ) /( bs - 2 0 : : f l o a t ) ) AS o t t a
- FROM b l oa t_in f o
- JOIN pg _class cc ON cc . relname = b lo at _i nfo . tablename
- JOIN pg_namespace nn ON cc . relname spac e = nn . o i d AND nn .
nspname = blo a t _ info . schemaname AND nn . nspname <>
information_schema
- ) , index_bloat AS (
30 SELECT
- schemaname , tablename , bs ,
- COALESCE( c2 . relname , ? ) AS iname , COALESCE( c2 . r e l t u p l e s
, 0 ) AS i t u p l e s , COALESCE( c2 . r el p age s , 0 ) AS i p a g e s ,
- COALESCE(CEIL ( ( c2 . r e l t u p l e s *( datahdr - 1 2 ) ) /( bs - 2 0 : : f l o a t )
) , 0 ) AS i o t t a - - very rough approximation , assumes a l l
c o l s
- FROM b l oa t_in f o
35 JOIN pg_class cc ON cc . relname = b lo at _i nfo . tablename
- JOIN pg_namespace nn ON cc . relname spac e = nn . o i d AND nn .
nspname = blo a t _ info . schemaname AND nn . nspname <>
information_schema
- JOIN pg_index i ON i n d r e l i d = cc . o i d
- JOIN pg _class c2 ON c2 . oid = i . i n d e x r e l i d
- )
40 SELECT
- type , schemaname , object_name , b loat , pg_size_pretty (
raw_waste ) as waste
- FROM
- (SELECT
- t a b l e as type ,
45 schemaname ,
- tablename as object_name ,
- ROUND(CASE WHEN o t ta=0 THEN 0 .0 ELSE table _ b l o a t . r e l p a g e s /
ot t a : : numeric END, 1 ) AS bl o at ,
- CASE WHEN r e l p a g e s < ot ta THEN 0 ELSE ( bs *( t a b l e_ b l o at .
r e lpa g es - o tta ) : : b i g i n t ) : : b i g i n t END AS raw_waste
- FROM
50 ta b l e _ b lo a t
- UNION
- SELECT
- index as type ,
- schemaname ,
298
14.2. Мелочи
55 tablename | | : : | | iname as object_name ,
- ROUND(CASE WHEN i o t t a =0 OR i p a g es=0 THEN 0. 0 ELSE i p ag e s /
i o t t a : : numeric END, 1 ) AS b loat ,
- CASE WHEN i p a ge s < i o t t a THEN 0 ELSE ( bs *( i pages - i o t t a ) )
: : b i g i n t END AS raw_waste
- FROM
- index_bloat ) bloat_summary
60 ORDER BY raw_waste DESC, b l oa t DESC
299
Литература
[1] Алексей Борзов (Sad Spirit) borz_off@cs.msu.su
PostgreSQL: настройка производительности
http://www.phpclub.ru/detail/store/pdf/postgresql-performance.pdf
[2] Eugene Kuzin eugene@kuzin.net Настройка репликации в PostgreSQL
с помощью системы Slony-I http://www.kuzin.net/work/sloniki-
privet.html
[3] Sergey Konoplev gray.ru@gmail.com Установка Londiste в подробностях
http://gray-hemp.blogspot.com/2010/04/londiste.html
[4] Dmitry Stasyuk Учебное руководство по pgpool-II
http://undenied.ru/2009/03/04/uchebnoe-rukovodstvo-po-pgpool-ii/
[5] Чиркин Дима dmitry.chirkin@gmail.com Горизонталь-
ное масштабирование PostgreSQL с помощью PL/Proxy
http://habrahabr.ru/blogs/postgresql/45475/
[6] Иван Блинков wordpress@insight-it.ru Hadoop http://www.insight-
it.ru/masshtabiruemost/hadoop/
[7] Padraig O’Sullivan Up and Running with HadoopDB
http://posulliv.github.com/2010/05/10/hadoopdb-mysql.html
[8] Иван Золотухин Масштабирование PostgreSQL: готовые решения от
Skype http://postgresmen.ru/articles/view/25
[9] Streaming Replication. http://wiki.postgresql.org/wiki/Streaming_Replication
[10] Den Golotyuk Шардинг, партиционирование, репликация - за-
чем и когда? http://highload.com.ua/index.php/2009/05/06/шардинг-
партиционирование-репликац/
[11] Postgres-XC A PostgreSQL Clustering Solution
http://www.linuxforu.com/2012/01/postgres-xc-database-clustering-
solution/
300
Литература
[12] Введение в PostgreSQL BDR http://habrahabr.ru/post/227959/
[13] Популярный обзор внутренностей базы данных. Часть пятая
http://zamotivator.livejournal.com/332814.html
[14] BRIN-индексы в PostgreSQL http://langtoday.com/?p=485
[15] Huge Pages в PostgreSQL https://habrahabr.ru/post/228793/
[16] Greenplum DB https://habrahabr.ru/company/tinkoff/blog/267733/
[17] Введение в PostGIS https://live.osgeo.org/ru/quickstart/postgis_quickstart.html
[18] Введение в полнотекстовый поиск в PostgreSQL
http://www.sai.msu.su/ megera/postgres/talks/fts_pgsql_intro.html
[19] pg_arman https://postgrespro.ru/docs/postgrespro/9.5/pg-arman.html
[20] It Probably Works http://queue.acm.org/detail.cfm?id=2855183
[21] Кластер PostgreSQL высокой надежности на базе Patroni, Haproxy,
Keepalived https://habrahabr.ru/post/322036/
301