# База даних

## Що це таке

База даних — це масив даних, що зберігається в сталій структурі з правилами про
те, що в неї може потрапити, щоб програми могли знаходити, змінювати й
перевіряти її вміст; цим самим словом, яке часто скорочують до БД, нестрого
називають і програму, що веде це зберігання, хоча її точна назва — система
керування базами даних, або СКБД (англійською DBMS). Глосарій NIST наводить два
визначення, і ширше з них говорить про сховище інформації або даних, яке може
бути традиційною реляційною системою баз даних, а може й не бути.

Слово виконує три ролі, і розмова ходить по колу, коли двоє мають на увазі
різні. Воно називає самі дані: клієнтів, замовлення й показання сенсорів, які
зберігає організація. Воно називає рушій, що їх береже: PostgreSQL, SQL Server,
SQLite. А всередині сервера воно називає один контейнер серед кількох. Підручник
PostgreSQL розкладає рівні так: рядки з іменованими типізованими стовпцями
складають таблиці, таблиці групуються в бази даних, а бази, якими разом керує
один екземпляр сервера, утворюють кластер баз даних. SQL Server так само
розрізняє екземпляр і бази даних, які він обслуговує. Тож «база гальмує» може
означати одну таблицю, один контейнер або цілу машину.

Більшість рушіїв у щоденному вжитку реляційні: вони зберігають дані як
відношення, тобто, математичною мовою, як таблиці, і приймають запитання мовою
SQL. Підручник PostgreSQL згадує й інші способи впорядкування. Файли й каталоги
Unix-подібної системи утворюють ієрархічну базу даних, а об'єктно-орієнтовані
бази з'явилися пізніше. Redis називає себе data structure server: замість
таблиць він пропонує рядкові значення, хеші, списки, множини та інші вбудовані
типи, які застосовують для кешування, черг і обробки подій.

Рушії різняться й тим, де вони працюють. Клієнт-серверний рушій — це процес, що
працює постійно і до якого багато програм звертаються мережею. Вбудований рушій
— це бібліотека всередині однієї програми, і SQLite тримає всю базу даних, разом
із таблицями, індексами, тригерами та поданнями, в одному звичайному файлі; сам
проєкт каже, що змагається з `fopen()`, а не з серверними базами даних. Різницю
видно з того, як програма підключається:

```sh
# клієнт-сервер: підключитися до служби, яка вже працює
psql "host=db.internal dbname=shop user=app"

# вбудований: програма відкриває файл, і рушій працює всередині неї
sqlite3 shop.db
```

Третя родина створена для аналітики. BigQuery зберігає кожен стовпець окремо,
тоді як багато традиційних систем тримають поля одного рядка поруч на диску.
Рядкове зберігання підходить, щоб знайти один запис; стовпцеве — щоб підсумувати
одне поле по мільйонах рядків, бо запит читає лише цей стовпець і решту залишає
в спокої.

## Чому це важливо

Дані можна тримати й у файлах. База даних додає до них обіцянки на той момент,
коли з ними одночасно працює багато програм.

Перша обіцянка — узгоджена картина під час спільної роботи. PostgreSQL дає
кожній інструкції знімок даних такими, якими вони були в певний момент; цей
підхід називається multiversion concurrency control, або MVCC, і завдяки йому
читання ніколи не блокує запис, а запис ніколи не блокує читання. SQLite
дозволяє скільки завгодно читачів одночасно й одного записувача в кожну мить, а
решта записувачів чекає, доки той закінчить. Ці два підходи розраховані на різну
кількість записувачів, і саме ця кількість багато в чому визначає вибір.

Друга обіцянка — дані зберігають свою форму. Схема визначає, які стовпці має
таблиця і якого типу кожен; ключі та обмеження визначають, які рядки взагалі
можуть існувати. Коли ці правила застосовує сама база даних, їм підкоряється
кожна програма, що в неї пише. Коли правила живуть лише в коді застосунку, перша
ж програма, яка їх оминає, залишає рядки, від яких доведеться захищатися кожному
наступному запиту.

Третя — спільна мова з діалектами. SQL — це стандарт ISO/IEC 9075, востаннє
переглянутий як SQL:2023, проте додаток PostgreSQL про відповідність стандарту
зазначає, що повну відповідність його Core сьогодні не заявляє жодна система.
Схема й запити належать своєму рушію, а перенесення їх на інший — це окремий
проєкт із власним тестуванням.

Документація SQLite зводить вибір рушія до короткого списку: дані, відокремлені
мережею від коду, що надсилає SQL, багато процесів, які пишуть в одну й ту саму
мить, або обсяг, що може доповзти до терабайтів, — кожна з цих ознак вказує на
клієнт-серверний рушій, а в решті випадків SQLite — простіша відповідь.
Звітність у великому масштабі додає ще одне питання: чи варто винести важкі
читання на окрему аналітичну копію, яку наповнює ETL, щоб довгий звіт ніколи не
змагався із запитами, на які чекають клієнти.

## Як це виглядає на практиці

Найбільший масштаб мала роль інженера баз даних у технологічній компанії, що
стрімко зростала: вона полягала в тому, щоб
[створити, оптимізувати й вести 100+ баз даних у PostgreSQL, Microsoft SQL Server та Google BigQuery](/uk/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/),
переважно з GIS-даними й часовими рядами: показаннями сенсорів, логами та
метриками в реальному часі. З цієї роботи з'явилися нормалізовані й
денормалізовані схеми для PostgreSQL і SQL Server із просторовою індексацією
(PostGIS на боці PostgreSQL) і партиціонуванням часових рядів, а великі обсяги
часових рядів у BigQuery лягли в партиціоновані за часом і кластеризовані
таблиці. Коли додалися індексація, матеріалізовані подання й кешування, час
відповіді на запити до GIS-даних і часових рядів скоротився на 40–60%, а
автоматизований моніторинг скоротив час простою на 50%.

На другому кінці шкали
[одна база даних SQLite містить факти для CV, списку рекомендацій, портфоліо й супровідного листа](/uk/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/)
трьома мовами. Її двадцять три таблиці збирає пайплайн із дев'ятнадцяти кроків,
який однією командою перетворює порожній файл на повну базу даних, і кожен крок
можна безпечно повторювати. Сорок один модуль на Python, загалом 10 580 рядків,
генерує з неї шість вихідних форматів. Профіль визначає, які досягнення
з'являються і в якому порядку, тож документ для певного типу читачів, словами
сторінки, — «це запит, а не копія». Сторінка називає й ціну: тепер система —
єдиний спосіб отримати документ.

Між цими розмірами стоїть питання, з яким стикається кожна навантажена база
даних, і
[нотатка про оптимізацію перед шардуванням](/uk/notes/optimise-before-you-shard/)
відповідає на нього порядком дій: подивитися, підтягнути, зняти читання з
основної бази, розділити таблиці навколо гарячої точки на партиції і шардувати
лише тоді, коли одна машина повністю завантажена корисною роботою. Подивитися
означає спитати сам PostgreSQL: `pg_stat_statements` покаже інструкції, що
коштують найбільше сумарно, а `EXPLAIN (ANALYZE, BUFFERS)` — що робить
найдорожча з них.

## Де виникають проблеми

Більшість проблем із базами даних починається з рішення, ухваленого рано, коли
даних було мало, а рішення здавалося безкоштовним.

**Дві копії одного факту розходяться.** Генератор CV, описаний вище, існує саме
тому, що факти, які ведуть як чотири документи, доводиться виправляти чотири
рази, аж поки два з них не почнуть суперечити один одному, і ніщо не вкаже,
котрий актуальний. Те саме стається з таблицею, яку експортували в електронну
таблицю й правлять там, або з другим сервісом, що веде власний список клієнтів.
Дайте кожному факту одне місце зберігання, яке ним володіє, і нехай кожна інша
копія каже, звідки й коли вона взялася. Кеш із нотатки дотримується цього
правила суворо: він може сказати покупцеві, що товар повністю зарезервовано, але
кожне «так» дає база даних.

**Машин більшає раніше, ніж хтось прочитає плани.** Розкидання даних по серверах
розносить повільні інструкції на більше заліза, не роблячи жодну з них дешевшою,
і кладе тривалий рахунок на все інше: як перелічує нотатка, кожна міграція,
резервна копія, відновлення і звіт виконуються раз на кожен шард, а join між
шардами стає кодом застосунку. Спершу прочитайте `pg_stat_statements` і плани
запитів і збережіть те, що вони показали, як точку відліку.

**Читання потрапляє на копію, що відстає.** Потокова репліка знімає читання з
основної бази й трохи від неї відстає. Користувач, який щойно щось записав і
читає це з репліки, може побачити старе значення. Власні щойно зроблені записи
користувача читайте з основної бази.

**Звіти й клієнти ділять один рушій.** Звіт, що сканує два роки історії, займає
пам'ять і диск, потрібні власним запитам магазину, і покупці це відчувають.
Перенесіть важку аналітику на копію в сховищі даних, а робочій базі залиште
безліч дрібних читань і записів.

## Що почитати далі

- [NIST glossary: database](https://csrc.nist.gov/glossary/term/database) — два
  короткі визначення з публікацій NIST, одне з яких охоплює й нереляційні бази
  даних.
- [PostgreSQL tutorial: Concepts](https://www.postgresql.org/docs/current/tutorial-concepts.html)
  — таблиці, рядки, стовпці, бази даних і кластер баз даних на одній сторінці.
- [PostgreSQL: Introduction to MVCC](https://www.postgresql.org/docs/current/mvcc-intro.html)
  — як знімки дають читачам і записувачам працювати, не блокуючи одне одного.
- [PostgreSQL: SQL Conformance](https://www.postgresql.org/docs/current/features.html)
  — частини й версії ISO/IEC 9075 і що означає відповідність Core.
- [Appropriate Uses For SQLite](https://www.sqlite.org/whentouse.html) — де
  доречний вбудований рушій, де краще підходить клієнт-серверний, і контрольний
  список для вибору.
- [Overview of BigQuery storage](https://cloud.google.com/bigquery/docs/storage_overview)
  — рядкове й стовпцеве зберігання і чому аналітика тяжіє до другого.

<https://engineer.company/uk/glossary/database/>
