43
НАРОДНЫЕ СРЕДСТВА ОПТИМИЗАЦИИ ЗАПРОСОВ В PostgreSQL Писарев Николай

Народные средства оптимизации PostgreSQL

  • Upload
    -

  • View
    136

  • Download
    4

Embed Size (px)

Citation preview

Page 1: Народные средства оптимизации PostgreSQL

НАРОДНЫЕ СРЕДСТВА ОПТИМИЗАЦИИ ЗАПРОСОВ

В PostgreSQL

Писарев Николай

Page 2: Народные средства оптимизации PostgreSQL

МЫ РАССМОТРИМ

● Основные моменты работы с РБД● Что такое EXPLAIN и как с ним работать● Индексы и их особенности● Лайфхаки и хитрости

Page 3: Народные средства оптимизации PostgreSQL

Основы работы с РБД

● Запись в таблице должна соответствовать объекту

● Правильно выбрать уровень нормализации

● Уделить немало времени на проектирование

● Структура БД должна быть гибкой

● Простота в поддержке жизненого цикла БД

● Правильно расставлять индексы, не создавать бардак

● Строить запросы не выбирая все подряд (*), только необходимое

● Приступать к оптимизации, когда действительно это требуется

Page 4: Народные средства оптимизации PostgreSQL

ORM vs SQl

● ORM — удобно?

● ORM — быстро?

Total runtime: (~)117.092 ms*

JAVA@ManyToOne@JoinColumn(name = "CATEGORY_ID")public Category getCategory() { return category;}

PHP$customers = Customer::find() ->where(['status' => TRUE]) ->orderBy('id') ->limit(100, 10000);

* Подробный пример мы разберем ниже

Page 5: Народные средства оптимизации PostgreSQL

ORM vs SQL● SQL — удобно?

● SQL — быстро?

Total runtime: (~)11.926 ms

SELECT * FROM (SELECT * FROM posts WHERE author = 'nick' ORDER BY posts.publish DESC LIMIT 10

) as posts JOIN post_category ON posts.category_id = category.id ORDER BY posts.publish DESC LIMIT 10;

* Подробный пример мы разберем ниже

Page 6: Народные средства оптимизации PostgreSQL

Еще об ORM

● Позволяет представить запись в БД в виде объекта● Удобнее понимать и легче писать чем SQL● Поддержка и изменения не вызывают трудностей

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

индексы

Page 7: Народные средства оптимизации PostgreSQL

А что с SQL?

● Сложные выборки, запросы● Возможности оптимизации● Функции и различные возможности SQL● HighLoad — однозначно SQL (узкое место)

● Запрос с использованием индексов не всегда быстрый● Замусоривает код (JAVA и многострочные литералы)● Нет проверок на этапе компиляции

Page 8: Народные средства оптимизации PostgreSQL
Page 9: Народные средства оптимизации PostgreSQL

ANALYZE

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

Затем планировщик запросов будет использовать эту статистику для выбора эффективных планов запросов.

=> ANALYZE VERBOSE;WARNING: skipping "pg_statistic" --- only superuser or database owner can analyze itWARNING: skipping "pg_type" --- only superuser or database owner can analyze itINFO: analyzing "public.test"INFO: "test": scanned 16669 of 16669 pages, containing 2000200 live rows and 0 dead rows; 30000 rows in sample, 2000200 estimated total rows

Page 10: Народные средства оптимизации PostgreSQL

VACUUM

● Очистка места (помечание), занимаемое «мертвыми» кортежами

● По-умолчанию очищает все таблицы доступные пользователю

● Без опции FULL может работать параллельно, т. к. не требует исключительной блокировки

● With FULL работает медленно, требует блокировки и возвращает освобожденное место операционной системе.

● Autovacuum — демон очистки (VACUUM+ANALYZE)● ! table bloating

Page 11: Народные средства оптимизации PostgreSQL
Page 12: Народные средства оптимизации PostgreSQL

Таблица TEST

=> CREATE TABLE test (id integer, text text);

=> INSERT INTO test SELECT id, md5(random()::text) FROM generate_series(1, 1000000) AS id;

=> \d test Table "public.test"Column | Type | Modifiers ----------------+------------+-----------column_1 | integer | column_2 | text |

Page 13: Народные средства оптимизации PostgreSQL

=> EXPLAIN SELECT * FROM test;● Cost — у.е. для оценки затратности

операции. 1ое значение — затраты доступа к 1й строке2ое значение — затраты для доступа ко всем строкам.

● Rows — (~) количество строк при вызове Seq Scan к этой таблице

● With — длина строки в байтах

Page 14: Народные средства оптимизации PostgreSQL

Всё, что мы видели выше в выводе команды EXPLAIN — ожидания планировщика.

=> EXPLAIN (ANALYZE) SELECT * FROM test;● Actual time — реальное время в миллисекундах для

1ой и всех строк● Rows — реальное количество строк полученных● Loops — количество выполнений данной операции● Plannig time — время выполнения EXPLAIN● Execution time — общее время выполнения● Heap Fetches — число реальных обращений к таблице

!!! DANGER !!!

EXPLAIN (ANALYZE) исполняет команды

Page 15: Народные средства оптимизации PostgreSQL

=> EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM test;

● Buffers: shared read — количество блоков считанных с диска;

● Buffers: shared hit — количество блоков, считанных из кэша PostgreSQL.

CACHE

Page 16: Народные средства оптимизации PostgreSQL

WHERE

=> EXPLAIN (ANALYZE) SELECT * FROM test WHERE id

between 1500 and 1550;● Индексов нет поэтому Seq Scan● Каждая строка сравнивается с:

Filter: ((id >= 1500) AND (id <= 1550))

● Cost увеличилось

● Rows уменьшилось до ождаемого количества

Execution time: 222.979 ms

Page 17: Народные средства оптимизации PostgreSQL

Index Scan

=> CREATE INDEX ON test(id);

=> EXPLAIN (ANALYZE) SELECT * FROM test WHERE id between 1500 and 1550;

● Теперь Index Scan using test_id_idx on test;● Index Cond: ((id >= 1500) AND (id <= 1550))

Execution time: 0.127 ms

Page 18: Народные средства оптимизации PostgreSQL

Seq Scan

A C D

1) С < 10

2) С < 10

3) С < 10

С < 10

Page 19: Народные средства оптимизации PostgreSQL

Index Scan

A C D

С < 10

C

Page 20: Народные средства оптимизации PostgreSQL

ИНДЕКСЫ

● Но что будет, если поменять условие

=> EXPLAIN (ANALYZE) SELECT * FROM test WHERE id > 1550;

● Теперь Seq Scan on test● Пришлось прочитать все строки, кроме

первых 1500..● Время увеличилось, что не удивительно

Execution time: 678.852 ms

Page 21: Народные средства оптимизации PostgreSQL

ИНДЕКСЫ

● А если выключить Seq Scan

=> SET enable_seqscan TO off;● Теперь Index Scan using test_id_idx on test● Но время запроса стало еще больше

Execution time: 749.952 ms● И стоимость cost также увеличилась

Планировщик не дурак =)

Page 22: Народные средства оптимизации PostgreSQL

Про индексы

● Индекс это дополнительная структура данных (не SQL)

● Индексы требуют затраты на поддержание● Замедляют обновление● Замедляют репликацию● Малая селективность — неэфективно

Индексы не панацея!

Page 23: Народные средства оптимизации PostgreSQL

Index Only Scan

● EXPLAIN (ANALYZE) SELECT id FROM test WHERE id < 450;

● Index Only Scan using test_id_idx on test● Выбираем только поле id,

чтобы включить IOS● Скорость очень большая

Execution time: 0.659 ms

Page 24: Народные средства оптимизации PostgreSQL

Index Only Scan

A C D

С < 10

C

VisabilityMAP

Page 25: Народные средства оптимизации PostgreSQL

ИНДЕКСЫ ПО ТЕКСТУ

EXPLAIN (ANALYZE) SELECT * FROM test WHERE text LIKE 'ab%';

● Seq Scan

After CREATE INDEX ON test(text);● Также Seq Scan (211 ms), потому что UTF-8!● Нужно использовать класс оператора

text_pattern_ops

CREATE INDEX ON test(text text_pattern_ops);

Page 26: Народные средства оптимизации PostgreSQL

ИНДЕКСЫ ПО ТЕКСТУ

EXPLAIN (ANALYZE) SELECT * FROM test WHERE text LIKE 'ab%';

● Bitmap Index Scan on test_text_idx1● Сравниваем

Index Cond: ((text ~>=~ 'ab'::text) AND (text ~<~ 'ac'::text)

) ● Далее Bitmap Heap Scan on test

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

Page 27: Народные средства оптимизации PostgreSQL

Bitmap Index Scan

A C D

С < 10

C

1

1 1

1

1 0

Page 28: Народные средства оптимизации PostgreSQL

Создание индексов

● Требуют блокировки при создании● CONCURRENTLY создает в фоне, но долго (требует

2 прохода)● Можно и нужно мониторить неиспользуемые

индексы (расходуются рессурсы и время)● Можно находить дубликаты индексов● Можно строить индексы по функциям, но

необходимо точное её повторение при запросе● ! index bloating

Page 29: Народные средства оптимизации PostgreSQL

Когда создавать индексы?

CREATE TABLE test5 (id integer PRIMARY KEY, v float8);

ACREATE INDEX test5_v_idx ON test5(v);

INSERT INTO test5 (SELECT id, random() FROM generate_series(1,1000000) id);

CREATE TABLE test5 (id integer PRIMARY KEY, v float8);

BINSERT INTO test5 (SELECT id, random() FROM generate_series(1,1000000) id);

CREATE INDEX test5_v_idx ON test5(v);

Page 30: Народные средства оптимизации PostgreSQL

Когда создавать индексы?CREATE TABLE a (id integer PRIMARY KEY, v float8); 1,991 ms

CREATE INDEX a_v_idx ON a(v); 0,506 ms

INSERT INTO a (SELECT id, random() FROM generate_series(1,1000000) id);

4909,127 ms

A = Total: 4911 ms

CREATE TABLE b (id integer PRIMARY KEY, v float8); 1,990 ms

INSERT INTO b (SELECT id, random() FROM generate_series(1,1000000) id);

938,852 ms

CREATE INDEX b_v_idx ON b(v); 1195,492 ms

B = Total: 2136 ms

Page 31: Народные средства оптимизации PostgreSQL

Lifehack Show

Page 32: Народные средства оптимизации PostgreSQL

Мониторим неиспользуемые индексы

SELECT schemaname || '.' || relname AS table, indexrelname AS index,

pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size, idx_scan as index_scans

FROM pg_stat_user_indexes ui

JOIN pg_index i ON ui.indexrelid = i.indexrelid

WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 819

ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,

pg_relation_size(i.indexrelid) DESC;

table | index | index_size | index_scans ---------------------+---------------------------+---------------+------------- public.test | test_text_idx | 56 MB | 0

public.test5 | test5_v_idx | 28 MB | 0

public.test6 | test6_v_idx | 21 MB | 0

public.test | test_text_idx1 | 56 MB | 3

public.test | test_id_idx | 21 MB | 36

Page 33: Народные средства оптимизации PostgreSQL

Ищем дубликаты индексов

lk=> SELECT pg_size_pretty(SUM(pg_relation_size(idx))::BIGINT) AS SIZE,

(array_agg(idx))[1] AS idx1, (array_agg(idx))[2] AS idx2,

(array_agg(idx))[3] AS idx3, (array_agg(idx))[4] AS idx4

FROM ( SELECT indexrelid::regclass AS idx,

(indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'||

COALESCE(indexprs::text,'')||E'\n' || COALESCE(indpred::text,'')) AS KEY

FROM pg_index) sub

GROUP BY KEY HAVING COUNT(*)>1

ORDER BY SUM(pg_relation_size(idx)) DESC;

size | idx1 | idx2 | idx3 | idx4

---------+-----------------------+----------------------+------+------

32 kB | blocks_id_idx | blocks_id_idx1 | |

32 kB | blocks_type_idx1 | blocks_type_idx | |

32 kB | primary_key | ids | |

Page 34: Народные средства оптимизации PostgreSQL

Оптимизация OFFSET

Ситуация:

SELECT …FROM table1JOIN table2 using (table2id)JOIN table3 using (table3id)WHEREнабор условий ТОЛЬКО по table1Order by (набор полей table1) LIMIT ... OFFSET ...

Важно: сработает если соблюдается условие, что логика выборки и offset реализуется в table1, а также что присоединенные данные из таблиц table2 и table3 на запрос не влияют.

Page 35: Народные средства оптимизации PostgreSQL

EXPLAIN ANALYZE SELECT * FROM test JOIN vals ON vals.test_id = test.id WHERE val between 150 AND 9500 LIMIT 5 OFFSET 5000;

Execution time: 283.471 ms

EXPLAIN ANALYZE SELECT * FROM ( SELECT * FROM test

WHERE val between 150 AND 9500 LIMIT 5 OFFSET 5000) AS test

JOIN vals ON vals.test_id = test.id;

Execution time: 4.079 ms

Page 36: Народные средства оптимизации PostgreSQL

Оптимизация COUNT(*)

=> EXPLAIN ANALYZE SELECT count(*) FROM cache_customers_rates;

Execution time: 18632.959 ms

=> EXPLAIN ANALYZE SELECT (reltuples)::numeric FROM pg_class r WHERE relkind='r' AND relname='cache_customers_rates';

Execution time: 0.079 ms

Page 37: Народные средства оптимизации PostgreSQL

Получение строк в виде ROW

=> SELECT * FROM tasks;

id | type | status | params | out. | exc.----+---------------+---------+------------------+------+----- 1 | refill_cache | new | {"threads":-1} | | 2 | refill_cache | new | {"threads":-1} | |

=> SELECT tasks FROM tasks;

tasks

--------------------------------------------------------------------( 1, refill_cache, new, "{""threads"":-1}", "", "" )( 2, refill_cache, new, "{""threads"":-1}", "", "" )

Page 38: Народные средства оптимизации PostgreSQL

Получение строк в виде JSON

=> SELECT row_to_json(tasks) FROM tasks; row_to_json-----------------------------------------------------------------{ "id":1, "type":"refill_cache", "status":"new", "params":"{\"threads\":-1}", "output":"", "exception":"" }

Page 39: Народные средства оптимизации PostgreSQL

Выбранные поля в виде JSON

=> SELECT row_to_json( t ) FROM ( SELECT id, type FROM tasks) AS t;

row_to_json------------------------------------------------{ "id":1, "type":"refill_cache" }

{ "id":1, "type":"refill_cache" }

Page 40: Народные средства оптимизации PostgreSQL

Данные в JSON ARRAY

=> SELECT array_to_json( array_agg( row_to_json(tasks) ) ) FROM tasks;

[ { "id":1, "type":"refill_cache", "status":"new", "params":"{\"threads\":-1}", "output":"", "exception":"" }, { "id":2, "type":"refill_cache", "status":"new", "params":"{\"threads\":-1}", "output":"", "exception":"" } ]

Page 41: Народные средства оптимизации PostgreSQL

OUTPUT to FILE

=> \o tasks.json

=> SELECT array_to_json( array_agg( row_to_json(tasks) ) ) FROM tasks;

=> \o

# cat tasks.json

array_to_json -------------------------------------------------------------------------------[{"id":1,"type":"refill_cache","status":"new","params":"{\"threads\":-1}","output":"","exception":""},{"id":2,"type":"refill_cache","status":"new","params":"{\"threads\":-1}","output":"","exception":""}]

Page 42: Народные средства оптимизации PostgreSQL
Page 43: Народные средства оптимизации PostgreSQL

Полезная информация

● https://wiki.postgresql.org/wiki/Index_MaintenanceМонторинг индексов

● http://www.highload.ru/2013/abstracts/1170.htmlИндексы

● https://wiki.postgresql.org/wiki/Show_database_bloatTable and index bloat

● http://www.dalibo.org/_media/understanding_explain.pdfEXPLAIN