database first! О распространённых ошибках использования...

80
Database First! О распространённых ошибках использования РСУБД Николай Самохвалов @postgresmen [email protected]

Upload: nikolay-samokhvalov

Post on 21-Jan-2018

587 views

Category:

Technology


4 download

TRANSCRIPT

Database First!

О распространённых ошибках использования РСУБД

Николай Самохвалов@[email protected]

О чём и зачем этот доклад

БД — сердце IT-системы

РСУБД и SQL занимают лидирующие позиции.И это надолго

Не использовать полноценно их возможности — глупость*

___________________* Получается глупо, потому что: 1) качество хуже, 2) производительность ниже, 3) потом всё равно придётся исправлять (возможно, уже не вам)

О докладчике• ФУПМ МФТИ / ИСП РАН / «Базы данных»

• MoiKrug (2005), MirTesen (2007), Postila (2013)

• PostgreSQL-консалтинг с 2007 в России, с 2013 в Калифорнии

• #RuPostgres (http://rupostgres.org) — вторая крупнейшая митап-группа в мире

• Postgres-твиттер (3500+ подписчиков): @postgresmen

• ПК конференций HLj, BackendConf, Highload++, PgDay Russia

Проблемы использования SQL & РСУБД

• боязнь и незнание возможностей РСУБД/SQL:• для контроля данных• для манипулирования данными

• архитектурные ошибки, связанные с этими страхами и невежеством

Ошибка №1:неиспользование ограничений целостности

Последствия ошибки №1

1. Отсутствие Primary Key (PK):• дубликаты, грязные данные;• нарушение реляционной модели и вытекающие проблемы (аномалии

данных, неожиданные результаты JOIN и т.д.)• рано или поздно приходится чистить (долго, дорого)

Последствия ошибки №1

1. Отсутствие Primary Key (PK):• дубликаты, грязные данные;• нарушение реляционной модели и вытекающие проблемы (аномалии

данных, неожиданные результаты JOIN и т.д.)• рано или поздно приходится чистить (долго, дорого)

2. Только «суррогатный» PK, но нет Unique Key(s) (UKs):• «скрытые» дубликаты, опять грязные данные и проблемы• опять необходимость чистки (долго, дорого)

Последствия ошибки №1

1. Отсутствие Primary Key (PK):• дубликаты, грязные данные;• нарушение реляционной модели и вытекающие проблемы (аномалии

данных, неожиданные результаты JOIN и т.д.)• рано или поздно приходится чистить (долго, дорого)

2. Только «суррогатный» PK, но нет Unique Key(s) (UKs):• «скрытые» дубликаты, опять грязные данные и проблемы• опять необходимость чистки (долго, дорого)

Числовые PK вида 1, 2, … («суррогатные») — это иллюзия правильного проектирования, легко забыть о потенциальных ключах!

Последствия ошибки №1

1. Отсутствие Primary Key (PK)2. Только суррогатный PK,

но нет Unique Key(s) (UKs)

3. Не используются Foreign Keys (FKs):• orphans (осиротевшие записи) → аномалии, ошибки 404, 500• …и опять: придётся чистить!

Последствия ошибки №1

1. Отсутствие Primary Key (PK)2. Только суррогатный PK, но нет Unique Key(s) (UKs)3. Не используются Foreign Keys (FKs)

4. Не используются NOT NULLs:• неполные данные, бессмысленные записи, аномалии

• опять. придётся. чистить.

Последствия ошибки №1

1. Отсутствие Primary Key (PK)2. Только суррогатный PK, но нет Unique Key(s) (UKs)3. Не используются Foreign Keys (FKs)4. Не используются NOT NULLs

5. Не используются CHECK constraints:• мусор, некорректные данные

• опять. придётся. чистить.

Ещё более фундаментальное — типы данных

0. Типы данных● недостаточно жёсткие ограничения текста (text, varchar(5000) и т.п.)

легко приводят к проблемам отображения (вёрстка и т.п.)

Ещё более фундаментальное — типы данных

0. Типы данных● недостаточно жёсткие ограничения текста (text, varchar(5000) и т.п.)

легко приводят к проблемам вёрстки

● varchar(250) или text превратить в varchar(50) — долго, дорого!

Ещё более фундаментальное — типы данных

0. Типы данных● недостаточно жёсткие ограничения текста (text, varchar(5000) и т.п.)

легко приводят к проблемам вёрстки

● varchar(250) или text превратить в varchar(50) — долго, дорого!

● PK типа integer в больших таблицах — бомба замедленного действия:○ максимум 2147483647 (2.1 млрд)○ перейти на int8 (aka bigint) — долго, дорого!

Ещё более фундаментальное — типы данных

0. Типы данных● недостаточно жёсткие ограничения текста (text, varchar(5000) и т.п.)

легко приводят к проблемам вёрстки

● varchar(250) или text превратить в varchar(50) — долго, дорого!

● PK типа integer в больших таблицах — бомба замедленного действия:○ максимум 2147483647 (2.1 млрд)○ перейти на int8 (aka bigint) — долго, дорого!

● Регистрозависимое поле email (varchar вместо citext и нет lower()):○ аккаунты-дубликаты, жалобы «не могу войти»

А что если проверять данные в приложении?

Data Checks (format, constraints, etc)in App (Ruby or Python or PHP or …)

А что если проверять данные в приложении? Oops

Data Checks (format, constraints, etc)in App (Ruby or Python or PHP or …)

Проверка данных только в приложении — не надо так!

Data Checks (format, constraints, etc)in App (Ruby or Python or PHP or …)

Проверка данных — а как правильно?

App (Ruby or Python or PHP or …)

CHECKS

Проверка данных — а как правильно?

App (Ruby or Python or PHP or …)

CHECKSКонтроль данных средствами СУБД:

- чёткая типизация,- все виды ограничений

целостности

Немного о JSON(b) – от неструктурированных данных к слабоструктурированным (semi-structured)

Немного о JSON(b) – от неструктурированных данных к слабоструктурированным (semi-structured)

Делаем ключ color обязательным

Немного о JSON(b) – от неструктурированных данных к слабоструктурированным (semi-structured)

Делаем ключ color обязательным

Без ключа color JSON-значение не принимается

Откуда взялось недоверие к СУБД?

Роль MySQL в росте страхов и невежества

Роль MySQL в росте страхов и невежества

Роль MySQL в росте страхов и невежества

Роль MySQL и MariaDB в росте страхов и невежества

Не допускаем ошибку №10. Полноценно используем типы данных

a. citext для регистронезависимых сравненийb. varchar(XX) построжеc. int8 в таблицах, которые вырастут, — сразуd. int2 для экономии, когда знаем, что диапазон значений точно будет малe. timestamptz вместо timestampf. tstzrange, int4range, int8range, numrange + exclusion constraint

A Tour of PostgreSQL Data Types (2013), Tour de (PostgreSQL) Data Types (2017)

1. PK – обязательно

2. Максимально используем UKs (особенно если PK суррогатный — выявляем наш “natural key” и добавляем unique index!)

3. Используем FKs

4. Используем NOT NULLs

5. Используем CHECK constraints

Ошибка №2:неиспользование возможностей SQL для манипуляции данными

Пример 2.1: инкремент

● Начинающий РНР-программист, задача массовой отправки писем.● Последовательный перебор по возрастанию User ID,

сохранение «указателя» на last User ID в БД.● Ошибки отправки отдельных писем не должны останавливать процесс.

Пример 2.1: инкрементЧто получим, если потоков несколько?

3 потока, каждый «отправил» по 100 писем

2.1) Что не так? Транзакция 1

Транзакция 2, использует значение из первой

2.1) СУБД сама может сделать инкремент!

Единственная транзакция, очень короткая,UPDATE с RETURNING,до каких-либо действий

2.1) СУБД сама может сделать инкремент!

3 потока, каждый «отправил» по 100 писем

Пример 2.2: обновление/удаление группы строк

Задача той самой «чистки» трубочиста. Ruby, опытный СТО.

Пример 2.2: обновление/удаление группы строк

Задача той самой «чистки» трубочиста. Ruby, опытный СТО.

Зачем-то «тянем» IDs на клиент

Пример 2.2: обновление/удаление группы строк

…и генерим длинный список из ID…(а что, если их миллионы?)

Зачем-то «тянем» IDs на клиент

Пример 2.2: обновление/удаление группы строкПолностью IDs, которые оставим

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

А это мы хотим «забрать с собой»

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

А это мы хотим «забрать с собой»

… и забираем

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

Само удаление с перечислением ID через запятую. Предварительно убрали

из массива все [keep_id]

Пример 2.2: более правильное решение — CTE

Используем CTE (Common Table Expressions):

Пример 2.2: как это делать с CTE

Boilerplate:

Пример 2.2: как это делать с CTE

Boilerplate:

Не забыли: PK, NOT NULL

Пример 2.2: как это делать с CTE

Boilerplate:

Используем типы правильно: int2, timestamp with time zone

Пример 2.2: как это делать с CTE

Boilerplate:

ЗАБЫЛИ!!Natural Key — пара (year, month)

(т.к. «успокоились» на суррогатном PK...)

Пример 2.2: как это делать с CTE

Вставляем данные:

Пример 2.2: как это делать с CTE

Вставляем данные: У природы нет плохой погоды(особенно с 2000-то года)

Пример 2.2: как это делать с CTE

Вставляем данные:

Но на самом деле есть.Май 2017, ураган.

Пример 2.2: как это делать с CTE

Вставляем данные:

Ooops! INSERT вместо UPDATE...

Пример 2.2: как это делать с CTE

Получили грязные данные, дубликаты:

Пример 2.2: как это делать с CTE

Получили грязные данные, дубликаты:

Пример 2.2: как это делать с CTE

UK уже не создать, поздно пить «Боржоми»:

Время собирать камни дубликаты. С помощью четырёхэтажного CTE:

Выборка данных

Правка created у тех, что останутся + RETURNING *

Удаление лишних + RETURNING *

Анализ результата (сколько UPDATEd, сколько DELETEd)

Пример 2.2: как это делать с CTE. Action!

Пример 2.2: как это делать с CTE. Action!IDs, которые оставим

Пример 2.2: как это делать с CTE. Action!

А это мы хотим «забрать с собой»

Пример 2.2: как это делать с CTE. Action!

А это мы хотим «забрать с собой»

… и забираем

Пример 2.2: как это делать с CTE. Action!

Так получаем IDs, которые будем удалять: все IDs группы за вычетом keep_id

Пример 2.2: как это делать с CTE. Action!

Так получаем IDs, которые будем удалять: все IDs группы за вычетом keep_id

… и вот так используем, с UNNEST, чтобы получить список ID для удаления

Пример 2.2: как это делать с CTE. Action!

У UPDATE и DELETE есть RETURNING *

Пример 2.2: как это делать с CTE. Action!

У UPDATE и DELETE есть RETURNING *

…которые дают нам получить 2 числа — количество затронутых строк

Пример 2.2: как это делать с CTE. Action!

Результат:

Пример 2.2: как это делать с CTE. Action!

Результат:

Пример 2.2: O CTE и современном SQL

Внимание: если строк удаляем/обновляем много,может понадобиться поэтапная работа с пачками строк!

(чтобы не блокировать много и надолго; а также давать возможность отработать VACUUM-у)

– нам нужен цикл вызовов извне (что угодно: bash, pyhon, ruby, php, etc)

Пример 2.2: O CTE и современном SQL

О современном SQL

Максим Богук:● «Неклассические приемы оптимизации запросов» (2014)● How to teach an elephant to rock'n'roll (2017)

Markus Winand:● http://use-the-index-luke.com/● http://modern-sql.com/

Внимание: если строк удаляем/обновляем много,может понадобиться поэтапная работа с пачками строк!

(чтобы не блокировать много и надолго; а также давать возможность отработать VACUUM-у)

– нам нужен цикл вызовов извне (что угодно: bash, pyhon, ruby, php, etc)

Пример 2.3: тянем данные на клиент Часто (Machine Learning, Data Science) – вытягивание огромного количества данных на клиент / app server для анализа (R, Python)Имитация:• 1 млн строк• клиент (psql) в Германии• сервер (Postgres 9.6) — в США

SeqScan, получение 1 числа: ~3 сек

SeqScan, получение 10000000 чисел: ~22 сек

MADlib: Machine Learning inside your DBMS- A lot of ML algorithms implemented (more in each release):

- PL/Python- Very easy and quick start to do machine learning with your Postgres data

http://madlib.incubator.apache.org/

MADlib: Machine Learning inside your DBMS

Source: Apache MADlib: Distributed In-Database Machine Learning for Fun and Profit

Details: MADlib Design Document

Data Manipulation Logicin App (Ruby or Python or PHP or …)

Something*

* ElasticSearch, Sphix, Analytics DBMS, etc

Пример 2.4

Пример 2.4

Data Manipulation Logicin App (Ruby or Python or PHP or …)

Something*

* ElasticSearch, Sphix, Analytics DBMS, etc

Пример 2.4

Data Manipulation Logicin App (Ruby or Python or PHP or …)

Something*

* ElasticSearch, Sphix, Analytics DBMS, etc

Пример 2.4

App (Ruby or Python or PHP or …)

Something*

* ElasticSearch, Sphix, Analytics DBMS, etc

DataManipulation

Use:- functions, triggers,- Foreign Data Wrappers (FDW),- Logical Decoding

Database First! Что важно помнить

● Делать вообще всё в СУБД можно, но в некоторых случаях неоправданно○ пример: работа с внешними API с непредсказуемым временем

— оккупация CPU мастера может быть фатальной ошибкой)

Database First! Что важно помнить

● Делать вообще всё в СУБД можно, но в некоторых случаях неоправданно○ пример: работа с внешними API с непредсказуемым временем

— оккупация CPU мастера может быть фатальной ошибкой)

● CTE в PostgreSQL – optimization fence○ планировщик «не знает», какие есть возможности (индексы) у «соседнего этажа»○ это может быть как и проблемой, так и помощью в ускорении запросов

Database First! Что важно помнить● Делать вообще всё в СУБД можно, но в некоторых случаях неоправданно

○ пример: работа с внешними API с непредсказуемым временем — оккупация CPU мастера может быть фатальной ошибкой)

● CTE в PostgreSQL – optimization fence○ планировщик «не знает», какие есть возможности (индексы) у «соседнего этажа»○ это может быть как и проблемой, так и помощью в ускорении запросов

● Иногда всё же очень нужна «внешняя сила»○ пример: дробление UPDATE/DELETE на батчи. Должен кто-то вызывать нас

Database First! Что важно помнить● Делать вообще всё в СУБД можно, но в некоторых случаях неоправданно

○ пример: работа с внешними API с непредсказуемым временем — оккупация CPU мастера может быть фатальной ошибкой)

● CTE в PostgreSQL – optimization fence○ планировщик не знает, какие есть возможности (индексы) у «соседнего этажа»○ это может быть как и проблемой, так и помощью в ускорении запросов

● Иногда всё же очень нужна «внешняя сила»○ пример: дробление UPDATE/DELETE на батчи. Нужны вызовы извне цикле!

● SQL- и PL/pgSQL-код важно правильно «готовить»:○ Code Style, подсветка кода (есть плагин vim для PL/pgSQL)○ версионирование и «миграции» (sqitch, liquibase, etc)○ тестировать (pgtap)○ мониторить (pg_stat_statements.track = all)○ дебажить (простые raise notice; pl/pgsql profiler, debugger)

Database First! Checklist➜ Типы данных

✓ varchar(N) построже✓ int8 для PK больших таблиц✓ int2, где диапазон мал✓ timestamptz вместо timestamp

✓ tstzrange, int4range, etc+ exclusion constraintA Tour of PostgreSQL Data Types (2013),Tour de (PostgreSQL) Data Types (2017)

➜ Манипуляции с данными — прежде всего с помощью средств СУБД!!!✓ если ничего не мешает, ищем/меняем данные средствами СУБД

✓ можно ли «гонять» меньше данных по сети, поручить работу по подготовке данных СУБД?

✓ много ли строк меняются запросом? нужна ли работа частями?

✓ хранимки — не зло, PL/pgSQL — прекрасен, используем; но можно ли обойтись SQL (или SQL-функцией)?

✓ используем современный SQL: CTE, lateral join, работа с массивами, JSONb, агрегация, оконные функции

«Неклассические приемы оптимизации запросов» (2014), How to teach an elephant to rock'n'roll (2017)Cайты: Use-the-Index-Luke.com, Modern-SQL.comTwitter: @postgresmen

➜ Ограничения целостности (constraints):✓ 5 главных: PK, UK, FK, NOT NULL, CHECK;

✓ а какой у таблицы Natural Key? Создаем UK(s);

✓ интервалы/геометрия? + exclusion constraint;

✓ сложные случаи (проверка данных в нескольких таблицах, денормализация, etc) – используем хранимки, триггеры

PostgreSQL Documentation