PostgreSQL проти Aurora PostgreSQL: несподівані витрати під час пікових навантажень (і як їх уникнути)

Було корисно?

Аварія завершилася. Затримки повернулися до норми. У Slack святкують. А потім у відділ фінансів заходить хтось із графіком, що нагадує трамплін, і питає, чому рахунок за базу даних виглядає так, ніби намагався покинути земну орбіту.

Пікові навантаження ламають не тільки системи; вони ламають припущення. І в AWS вони можуть ламати бюджети так особисто, ніби система мала на це намір. Поговорімо, чим відрізняються PostgreSQL на власних машинах (або самостійно керований на EC2), RDS PostgreSQL і Aurora PostgreSQL під час піків — і які важелі дійсно запобігають несподіваним витратам без саботажу надійності.

Податок на піки: куди витікають гроші під час сплесків

Більшість історій про «неочікувані витрати» не про те, що хтось вибрав неправильний тип інстансу. Йдеться про те, що робоче навантаження змінило форму на кілька хвилин, а платформа виставила рахунок за всі вторинні ефекти: більше I/O, більше реплік, більше обсягу логів, шторми повторних спроб, між‑AZ трафік, і більше читального посилення від «пофіксів», які такими не були.

Під час піку ваша база даних може стати множником. 3× зростання трафіку перетворюється на 10× зростання I/O, бо кеші пропускають, запити йдуть на диск, а ваш застосунок повторює запити при таймауті наче бере участь у кастингу на роль DoS.

Важлива відмінність:

  • Традиційний PostgreSQL (на власних потужностях або EC2) зазвичай виставляє рахунок за резервовані ресурси. Під час піку ви платите переважно болем — затримками, насиченням, збоями — а не грошима, якщо тільки не вмикаєте автоскейлінг інфраструктури.
  • Aurora PostgreSQL виставляє рахунок за ресурси та за споживання — метрики, за якими легко не помітити зростання, поки це не станеться. Піки можуть прямо перетворюватися на платні послуги: I/O, зростання сховища, одиниці потужності serverless, утримання бекапів і іноді мережеві патерни, про які ви й не підозрювали.

Жодне з рішень не є «завжди дешевше». Обидва можуть бути економними. Що змінюється — це який режим відмови проявиться першим: простої чи рахунок.

Цікаві факти та історія, що пояснюють сьогоднішні рахунки

Це не тривіальні дрібниці. Це причини, через які певні сюрпризи з витратами повторюються.

  1. PostgreSQL існує з середини 1990-х і успадкував припущення про дизайн з епохи, коли диск був повільним, RAM — дорогим, а «хмара» — лише погодою.
  2. Amazon RDS запущено у 2009 щоб знизити операційні витрати управління базами — патчі, бекапи, та аварійне відновлення. Це не ліквідувало інженерію продуктивності; просто перемістило деякі ручки.
  3. Aurora з’явився у 2014 з розподіленим шаром збереження даних для кращої надійності та пропускної здатності. Він також створив нові поверхні білінгу — особливо поведінку I/O і сховища.
  4. Сховище Aurora автоскейлиться, замість того щоб змушувати заздалегідь вказувати розмір тома. Це зручно операційно й небезпечно фінансово, якщо рости через блоут, безконтрольне утримання чи забуту таблицю.
  5. MVCC у PostgreSQL означає, що оновлення не перезаписують рядки; вони створюють нові версії. Під час піків це може означати більше WAL, тиск на vacuum і більший обмін даними у сховищі.
  6. Утримання WAL часто є «тихою» статтею бюджету в багатьох керованих сервісах. Зафіксована репліка або довгі транзакції можуть змусити зростати утримання WAL і, як наслідок, використання сховища.
  7. Пулінг з’єднань став масовим у Postgres‑командах, бо модель процес‑на‑з’єднання на бекенді є надійною, але не дешевою. Піки без пулінгу часто означають CPU‑перегрів і марнотратство пам’яті.
  8. Aurora Serverless пройшов дві генерації (v1 і v2). v2 зменшив деякі дивні моменти масштабування, але білінг все ще відстежує потужність у часі й може зростати швидше, ніж ви очікуєте під штурмом з’єднань.
  9. Більшість вибухів витрат корелюють з інцидентами надійності, бо повтори, відмови, промахи кешу та аварійні дії з масштабування створюють платне споживання. Вартість часто — другий сигнал після затримки.

Один цитат для чесності: перефразована ідея від John Allspaw: Інциденти виникають тому, що системи складні; стійкість приходить від навчання і покращення, а не від звинувачень.

Дві ментальні моделі: «коробка з дисками» vs «сервіс з лічильниками»

PostgreSQL (самовпорядкування): ви переважно платите за готовність

Коли ви запускаєте Postgres самостійно (bare metal, VM або EC2), витрати в основному прив’язані до провізіонованих ресурсів: CPU, RAM і сховища. Під час піків ваш рахунок мало змінюється, якщо ви не масштабуєтеся (нові репліки) або не підвищуєте інтенсити інстансів динамічно.

Перевага — передбачуваність. Недолік — ви платите за резерв протягом року, щоб пережити 30‑хвилинний пік у «чорну п’ятницю» — або не платите, і ваші клієнти отримують святковий досвід через 500‑ві помилки.

Aurora PostgreSQL: ви платите за готовність й за споживання

Aurora вводить розділення обчислень і сховища, що може бути відмінним для доступності і масштабування читань. Але економічна модель більше схожа на «лічильник комунальних послуг», ніж на оренду. Під час піків лічильники крутяться.

У Aurora найпоширеніші категорії сюрпризів:

  • Рішення про масштабування обчислень (включно з репліками і serverless потужністю).
  • Споживання I/O, включно з читаннями через промахи кешу та неефективні запити.
  • Зростання сховища через блоут, WAL, тимчасове використання і політику збереження бекапів.
  • Мережеві та між‑AZ ефекти, особливо коли архітектури ненавмисно переміщують дані.

Жарт №1: білінг Aurora схожий на абонемент у спортзал, який ще й бере плату за кожен крок на біговій доріжці. Ви підтягнетеся, але також зацікавитесь, чи не ходити менше.

Несподіванки з витратами за категоріями (і як вони проявляються)

1) Сюрприз «ми додали репліки»

Під час піку команди часто додають репліки для читання або масштабу інстанси. У самовпорядкованому Postgres це зазвичай означає нові EC2‑вузли (або більші). В Aurora репліки можна запускати швидко, що добре. Але «швидко» також означає «легко зробити імпульсивно».

Патерн сюрпризу: репліки стають липкими. Пік закінчився, репліки залишилися, і тепер ви платите за новий базовий рівень.

Як уникнути:

  • Встановіть політику на кількість реплік: максимум N, автоматичне зменшення після X хвилин стабільності.
  • Інструментуйте затримку репліки та використання reader endpoint, щоб знати, чи репліки реально допомогли.
  • Віддавайте перевагу оптимізації запитів і кешуванню для повторюваних сплесків; використовуйте репліки для тривалих читальнe‑навантажень.

2) Сюрприз «I/O пішло нелінійно»

Піки підсилюють неефективність. Посередній запит без індексу може бути «нормальним» при 50 QPS і катастрофічним при 500 QPS. В Aurora катастрофа часто приходить як рядок у рахунку за I/O плюс інцидент по затримці.

Поширені причини нелінійного зростання I/O під час піків:

  • Холодні кеші після failover або масштабування.
  • Великі сканування через погані плани (чутливість до параметрів, застарілі статистики, неправильні індекси).
  • Сортування/хеші, що виливаються на диск через недостатній work_mem або великі результати.
  • Патерни N+1, що множать роботу на запит під більшою паралельністю.

Ключова деталь: деякі оптимізації зменшують CPU, але збільшують I/O, і ви можете «оптимізуватися» в дорожчий рахунок. Про це буде історія далі.

3) Зростання сховища: автоскейлінг не означає авточистку

Сховище Aurora зростає автоматично. Це чудово. Але воно не звужується автоматично так, як більшість людей сподівається, особливо якщо зростання було спричинене блоутом, великими таблицями або тимчасовим сплеском утримання WAL.

Зростання сховища під час піків може йти від:

  • MVCC блоуту при масових оновленнях/видаленнях, коли vacuum не встигає.
  • Довгих транзакцій, що не дозволяють reclaim‑ити tuple‑и.
  • Логічних слотів реплікації, що тримають WAL.
  • Тимчасових файлів від диск‑спілів (зазвичай це також запах проблем з продуктивністю).

У самовпорядкованому Postgres ви бачите, як диск заповнюється і панікуєте. В Aurora ви бачите рахунок пізніше і панікуєте під підсвіткою.

4) Серверлес‑масштабування: рахунок слідує за конкуренцією, а не за вашими намірами

Aurora Serverless v2 може бути добрим варіантом для переривчастих навантажень. Він також може бути дорогим варіантом, якщо сплески спричинені штормами з’єднань, повторними спробами або «балакучою» поведінкою клієнта.

Механізм сюрпризу: потужність масштабується під сигнали попиту (з’єднання, CPU, тиск пам’яті). Пік, спричинений поганою поведінкою клієнта, виглядає так само, як і пік від реального бізнес‑зростання. Базі байдуже, чому їй важко.

5) Бекапи і утримання: нудно, поки не стане критично

Витрати на бекапи зазвичай не найпомітніша стаття, але вони стають помітними, коли сховище швидко зростає або політики утримання «встановили і забули». Під час піків, якщо ви пишете більше (важкі на WAL навантаження), це також може збільшити активність бекапів і знімків залежно від налаштувань.

Підступність у тому, що витрати на бекапи часто — затримана несподіванка: тижні після інциденту, що спричинив зростання сховища.

6) Крос‑AZ і «невидимі мережеві» сюрпризи

Деякі архітектури генерують значний між‑AZ трафік: реплікація, клієнти в різних AZ, аналітика, що тягне великі набори даних, або батчі, які переміщують дані між регіонами. Під час піків ці потоки масштабуються.

Практична порада: тримайте клієнтів застосунку в тому ж AZ, що й primary‑ендпоінт, коли це можливо, і будьте наміреними щодо розміщення рідерів. Multi‑AZ для надійності — добре; ненавмисний між‑AZ чат — ні.

Жарт №2: нічого так не вчить любити кешування, як платити за те, що одні й ті ж дані читають 10 мільйонів разів за день.

Швидкий план діагностики

Коли витрати зростають, зазвичай приховується інцидент продуктивності. Діагностуйте як SRE: знайдіть вузьке місце, а потім зіставте його з вимірювачем витрат.

Перший крок: підтвердіть, що змінилося

  • Форма трафіку: QPS, конкуренція, склад запитів. Це більше користувачів чи більше повторів?
  • Топологія бази: нові репліки, відмова, події масштабування, зміни параметрів.
  • Деплої: реліз застосунку, зміни схеми, новий індекс, новий шлях запиту.

Другий крок: знайдіть вузьке місце

  • CPU‑bound: високий CPU, низький I/O wait, повільні запити через обчислювальні оператори.
  • I/O‑bound: підвищена затримка чит/запис, пропуски в буфері, зростання тимчасових файлів.
  • Lock‑bound: заблоковані сесії, довгі транзакції, помилки серіалізації, deadlock‑и.
  • Connection‑bound: забагато з’єднань, таймаути, часті аутентифікації, тиск пам’яті.

Третій крок: зіставте вузьке місце з драйверами витрат

  • CPU‑bound → масштабування обчислень, більший клас інстансу, збільшення ACU в serverless.
  • I/O‑bound → витрати на I/O в Aurora, зростання сховища через churn, I/O від тимчасових спілів.
  • Lock‑bound → збільшені повтори, посилене навантаження, більше записів (WAL), іноді додаткові репліки як «поправка» для читів.
  • Connection‑bound → примусове масштабування вгору, додавання реплік, збільшення ACU, гірша поведінка кешу після рестартів/фейовера.

Четвертий крок: безпечно зупиніть кровотечу

  • Увімкніть або посиліть пулінг з’єднань; обмежте макс‑з’єднання на рівні застосунку.
  • Застосуйте обмеження частоти для найшумніших ендпоінтів.
  • Вимкніть поведінку «повторюй негайно вічно»; додайте джиттер і бюджети для повторів.
  • Якщо додаєте репліки, відразу встановіть таймер видалення і вимірюйте реальне використання рідерів.

Практичні завдання: команди, виводи та рішення (12+)

Це ті завдання, які я справді виконую під час і після сплесків. Кожне містить: команду, що типовий вивід означає, і рішення, яке ви приймаєте.

Завдання 1: Визначити топ‑запитів за сумарним часом (pg_stat_statements)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT queryid,
       calls,
       round(total_exec_time::numeric, 2) AS total_ms,
       round(mean_exec_time::numeric, 2) AS mean_ms,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;"
 queryid  | calls | total_ms  | mean_ms | rows
----------+-------+-----------+---------+-------
 91283412 | 80000 | 980000.12 |   12.25 | 400000
 77221110 |  3000 | 410000.55 |  136.66 |   3000
(2 rows)

Що це означає: Перший запит витратив найбільше сукупного часу, навіть якщо середня латентність не жахлива. Під час піків «помірно повільний, але масово частий» — типовий злочинець.

Рішення: Почніть з запиту з найбільшою сумою часу. Отримайте його план, додайте потрібний індекс або зменшіть частоту викликів (бачінг, кешування). Не ганяйтеся за найдовшим одиночним запитом, якщо він нечастий.

Завдання 2: Отримати план виконання з buffers (щоб бачити I/O)

cr0x@server:~$ psql "$DATABASE_URL" -c "
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT * FROM orders
WHERE customer_id = 12345
ORDER BY created_at DESC
LIMIT 50;"
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.56..25.12 rows=50 width=128) (actual time=2.114..5.882 rows=50 loops=1)
   Buffers: shared hit=120 read=450
   ->  Index Scan Backward using orders_customer_created_idx on public.orders  (cost=0.56..1200.00 rows=2500 width=128)
       Index Cond: (orders.customer_id = 12345)
       Buffers: shared hit=120 read=450
 Planning Time: 0.210 ms
 Execution Time: 6.050 ms

Що це означає: «shared read=450» вказує на фізичні читання. Під час піків це число вибухає, якщо кеші холодні або селективність індексу погана.

Рішення: Якщо читання домінують, виправте патерни доступу (кращі індекси, менші набори результатів, кешування) і не відповідайте одразу «додайте репліку».

Завдання 3: Перевірити коефіцієнт попадання в кеш (орієнтир)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT datname,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS cache_hit_pct
FROM pg_stat_database
WHERE datname = current_database();"
 datname | cache_hit_pct
---------+---------------
 appdb   |         93.41

Що це означає: Падіння від вашої нормальної бази часто корелює з більшим I/O в Aurora. Коефіцієнт попадання в кеш — не KPI, але димовий сигнал.

Рішення: Якщо він впав під час піку, дослідіть тригери холодного кешу (фейловер, масштабування, зміна складу запитів) і розгляньте нагрівання кешу або стабільніший розмір обчислень.

Завдання 4: Виявити шторми з’єднань і хто їх створює

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT usename, application_name, state, count(*)
FROM pg_stat_activity
GROUP BY 1,2,3
ORDER BY 4 DESC
LIMIT 10;"
 usename | application_name | state  | count
--------+-------------------+--------+-------
 app    | api               | active |   220
 app    | api               | idle   |   900
 app    | worker            | active |    80

Що це означає: 900 idle‑сесій — це пул, що неправильно налаштований, або клієнт, який вважає відкриття з’єднань своїм хобі.

Рішення: Впровадьте PgBouncer (або RDS Proxy там, де доречно), обмежте макс‑з’єднання і виправте налаштування пулу на боці застосунку. Якщо ви на Aurora Serverless v2, ставте кількість з’єднань як сигнал витрат.

Завдання 5: Визначити найдовші транзакції (блокери vacuum)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT pid,
       now() - xact_start AS xact_age,
       wait_event_type,
       state,
       left(query, 80) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 10;"
 pid  |  xact_age  | wait_event_type | state  | query
------+------------+-----------------+--------+-------------------------------
 4412 | 02:14:09   | Client          | idle   | BEGIN;
 9871 | 00:09:33   | Lock            | active | UPDATE orders SET status='X'

Що це означає: Транзакція відкрита 2 години може змусити утримання WAL і блоут, і підвищити використання сховища/ I/O після піку.

Рішення: Виправте патерн застосунку (немає idle‑in‑transaction), встановіть таймаути для statement/idle, і за потреби вбивайте гірших під час інцидентів.

Завдання 6: Швидко знайти блокування

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT blocked.pid AS blocked_pid,
       blocker.pid AS blocker_pid,
       now() - blocker.query_start AS blocker_age,
       left(blocker.query, 60) AS blocker_query
FROM pg_locks blocked
JOIN pg_stat_activity blocked_act ON blocked.pid = blocked_act.pid
JOIN pg_locks blocker ON blocker.locktype = blocked.locktype
  AND blocker.database IS NOT DISTINCT FROM blocked.database
  AND blocker.relation IS NOT DISTINCT FROM blocked.relation
  AND blocker.page IS NOT DISTINCT FROM blocked.page
  AND blocker.tuple IS NOT DISTINCT FROM blocked.tuple
  AND blocker.transactionid IS NOT DISTINCT FROM blocked.transactionid
  AND blocker.pid != blocked.pid
JOIN pg_stat_activity blocker ON blocker.pid = blocker.pid
WHERE NOT blocked.granted
LIMIT 5;"
 blocked_pid | blocker_pid | blocker_age | blocker_query
------------+------------+-------------+------------------------------
      12011 |      11888 | 00:03:12    | ALTER TABLE orders ADD COLUMN

Що це означає: DDL під час піку часто викликає черги блокувань, що спричиняє повтори, що підвищує навантаження і, як наслідок, витрати.

Рішення: Переносьте блокуючі DDL‑операції в низький трафік, використовуйте безпечніші патерни міграцій і обмежуйте lock timeout, щоб клієнти падали швидко замість того, щоб гуртуватися.

Завдання 7: Перевірити використання тимчасових файлів (спіли на диск = додатковий I/O)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT datname,
       temp_files,
       pg_size_pretty(temp_bytes) AS temp_written
FROM pg_stat_database
WHERE datname = current_database();"
 datname | temp_files | temp_written
---------+------------+--------------
 appdb   |      18220 | 48 GB

Що це означає: 48 GB тимчасових записів під час піку — це не просто знак, це неоновий вивісок. Це часто корелює з дорогими сортуваннями/хешами і може збільшити I/O‑витрати Aurora.

Рішення: Виправте запит і/або обережно збільшіть пам’ять (work_mem на сесію може вибухнути), і зменшіть паралельність через пулінг.

Завдання 8: Перевірити, чи autovacuum встигає (запобігання блоуту)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT relname,
       n_dead_tup,
       n_live_tup,
       last_autovacuum,
       last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;"
 relname | n_dead_tup | n_live_tup |     last_autovacuum      |     last_autoanalyze
---------+------------+------------+--------------------------+--------------------------
 events  |   9200000  |  11000000  |                          | 2025-12-30 08:12:40+00

Що це означає: Велика кількість мертвих рядків і відсутність недавнього autovacuum вказують, що vacuum відстає — класичне постпікове зростання сховища і регресія продуктивності.

Рішення: Налаштуйте autovacuum для «гарячих» таблиць, додайте індекси, що зменшують churn, і усуньте довгі транзакції, що блокують прибирання.

Завдання 9: Оціночний блоут таблиці (швидко)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
       n_dead_tup
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;"
 relname | total_size | n_dead_tup
---------+------------+-----------
 events  | 180 GB     | 9200000
 orders  |  95 GB     |  120000

Що це означає: Величезна таблиця з великою кількістю мертвих рядків — це множник сховища і I/O.

Рішення: Розгляньте партиціювання, агресивніші налаштування autovacuum або періодичне технічне обслуговування (VACUUM (FULL) в крайніх випадках, але плануйте вплив). В Aurora зробіть це до того, як зростання сховища стане постійним.

Завдання 10: Перевірити відставання реплікації (репліки, що не встигають)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT application_name,
       client_addr,
       state,
       write_lag,
       flush_lag,
       replay_lag
FROM pg_stat_replication;"
 application_name | client_addr |  state  | write_lag | flush_lag | replay_lag
-----------------+-------------+---------+-----------+-----------+------------
 aurora-replica-1 | 10.0.2.55   | streaming | 00:00:01 | 00:00:02 | 00:00:05

Що це означає: Невелике відставання — нормально. Велике відставання під час піків може спричинити повтори, застарілі читання і зростання утримання WAL.

Рішення: Якщо відставання росте, зменшіть записний тиск, виправте довгі транзакції і не направляйте критично‑латентні читання до відстаючих реплік.

Завдання 11: Виявити зростання обсягу WAL (пишуче підсилення)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS wal_since_boot;"
 wal_since_boot
----------------
 320 GB

Що це означає: Великий обсяг WAL може корелювати з підвищеним churn сховища, тиском реплікації і накладними витратами на бекапи.

Рішення: Зменшіть непотрібні оновлення, пакетну обробку записів і уникайте патернів, що часто оновлюють великі рядки. Розгляньте налаштування fillfactor для «гарячих» рядків.

Завдання 12: Перевірити відсутні індекси через повільні запити (тріаж)

cr0x@server:~$ psql "$DATABASE_URL" -c "
SELECT schemaname, relname, seq_scan, idx_scan,
       pg_size_pretty(pg_relation_size(relid)) AS table_size
FROM pg_stat_user_tables
WHERE seq_scan > 10000 AND idx_scan = 0
ORDER BY seq_scan DESC
LIMIT 10;"
 schemaname | relname | seq_scan | idx_scan | table_size
------------+---------+----------+----------+-----------
 public     | events  |   820000 |        0 | 120 GB

Що це означає: Велика таблиця з тоннами послідовних сканів — ймовірне джерело I/O‑сплесків.

Рішення: Додайте таргетовані індекси і перепишіть запити. Перевіряйте через EXPLAIN і статистики, наближені до продакшену; не індексуйте все підряд.

Завдання 13: Перевірити налаштування Postgres, що спричиняють несподіване споживання пам’яті

cr0x@server:~$ psql "$DATABASE_URL" -c "SHOW work_mem; SHOW max_connections; SHOW shared_buffers;"
 work_mem
---------
 64MB
 max_connections
-----------------
 2000
 shared_buffers
----------------
 8GB

Що це означає: work_mem 64MB при max_connections 2000 — не «безпечно для 128GB RAM». Це «будь ласка, насолоджуйтесь свопом і диск‑спілами».

Рішення: Зменшіть max_connections (пул), встановіть розумний work_mem і масштабуйте обчислення під реальну конкуренцію, а не під теоретичну.

Завдання 14: На Linux‑хості Postgres підтвердити iowait і насичення (самовпорядкований)

cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (db01)  12/30/2025  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.10    0.00    6.12   32.55    0.00   39.23

Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
nvme0n1         980.0   410.0 52000.0 18000.0  18.2   0.9   98.7

Що це означає: Високий iowait і ~99% util вказують, що ви I/O‑bound. На EC2/на власних ресурсах це часто справжній ліміт; в Aurora ви бачите це як затримку і плату за I/O.

Рішення: Зменшіть попит на I/O (індекси, виправлення запитів) або підвищте продуктивність сховища (швидші диски/IOPS). Не додавайте CPU, якщо стенд — диск.

Завдання 15: На Linux‑хості Postgres знайти найактивніших клієнтів (з’єднання і порти)

cr0x@server:~$ ss -tn sport = :5432 | head
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0      0      10.0.1.10:5432     10.0.4.22:52144
ESTAB 0      0      10.0.1.10:5432     10.0.4.22:52146
ESTAB 0      0      10.0.1.10:5432     10.0.9.17:60311

Що це означає: Швидко видно, які хости застосунку відкривають найбільше TCP‑з’єднань під час шторми.

Рішення: Обмежьте створення з’єднань в джерелі: налаштування пулу у застосунку, сайдкари‑пулери або навантажувальне відсікання. Зупиніть ситуацію, коли «горизонтальне масштабування» перетворюється на «горизонтальне множення з’єднань».

Три корпоративні міні‑історії з передової

Міні‑історія 1: Інцидент через неправильне припущення

Середня SaaS‑компанія мігрувала з самовпорядкованого PostgreSQL на EC2 до Aurora PostgreSQL. Обіцянка була приваблива: менше опсу, краща стабільність сховища, зручніше керування репліками. У команди також був регулярний піковий патерн: що година в годину клієнти оновлювали дашборди і фонові задачі запускалися.

Неправильне припущення було тонким: «Сховище Aurora автоскейлиться, тож нам не треба думати про диск». Вони припинили так агресивно відстежувати метрики блоуту й vacuum, як раніше. Не через лінь — через полегшення. Сигнали про диск психологічно асоціювалися зі старим світом.

Під час особливо важкого тижня щогодинний сплеск перетворився на щоденний режим постійного високого записного churn. Таблиця зі станом подій часто оновлювалася, autovacuum не встигав. Довгі аналітичні транзакції (лише для читання, але відкриті тривалий час) перешкоджали прибиранню. Сховище швидко росло, а потім залишалося великим.

Інцидент почався з латентності: більше читань через churn кешу та забиті індекси. Потім це переросло в подію з витратами: лічильник I/O піднявся, а рядок сховища стрибнув. Фінанси не помилилися в здивуванні; система поводилася так, як задумано: вона продовжувала працювати й зберігати.

Виправлення не було магічним налаштуванням Aurora. Це була стара добра дисципліна Postgres: вбивати idle‑in‑transaction сесії, налаштувати autovacuum для гарячої таблиці, додати більш селективний індекс щоб зменшити сканування і змінити модель даних, щоб знизити churn оновлень. Після цього зростання сховища сповільнилося. Рахунок перестав рости. І команда знову навчилася неприємної правди: керований сервіс не означає керовані дані.

Міні‑історія 2: Оптимізація, що відкотилася

Інша компанія мала API з великою часткою читань і регулярні піки. Хтось зробив слушну річ: додали індекс, щоб прискорити повільний запит. Це спрацювало. P95 латентності знизився. Команда святкувала і пішла далі.

Через два тижні інший графік виглядав гірше: витрати на I/O в Aurora зросли під час піків, хоча латентність була в нормі. Виною став новий індекс, у поєднанні з «невинним» патерном записів: індексований стовпець часто змінювався. Кожен запис тепер оновлював індекс, що підвищило записне підсилення і обсяг WAL.

Під час піків записне підсилення і висока конкуренція спричиняли більше фонового навантаження: більше WAL, тиск на реплікацію, більше churn кешу. Система в основному залишалася в SLO, але виконувала набагато більше роботи. Лічильник це помітив. Він завжди помічає.

Вони виправили це, переглянувши підхід: endpoint не мусив оновлювати індексований стовпець на кожен запит. Вони перенесли оновлення в батч‑процес і додали окрему таблицю для часто змінних атрибутів. Індекс залишили, але змінили поведінку записів. Витрати впали без втрати продуктивності.

Мораль: «оптимізація продуктивності», що ігнорує патерни записів, — це просто оптимізація витрат для вашого хмарного провайдера.

Міні‑історія 3: Скучно, але правильно, і це врятувало

Компанія, що працює з платіжними потоками, мала суворі вимоги до надійності і дивовижно «скучну» культуру планування потужності. Вони працювали на Aurora PostgreSQL, але ставилися до нього як до продакшен‑системи, а не як до магічного трюку. Кожного кварталу вони робили drill піків: відтворювали трафік, міряли склад запитів і перевіряли дашборди витрат і продуктивності.

Під час одного такого тесту вони помітили патерн: після failover кеші холодніли і I/O зростав близько 15 хвилин. Це не було катастрофою, але дорого і могло погіршитися під реальним інцидентом. Замість того, щоб погодитися на це, вони побудували процедуру нагрівання: контрольований набір репрезентативних запитів, які виконуються з низькою частотою після failover, щоб ініціювати кеші і стабілізувати продуктивність.

Вони також мали суворе правило: будь‑яке аварійне масштабування вимагало квитка на зменшення потужності з дедлайном. Якщо хтось додавав репліку або піднімав інстанс, з’являлося автоматичне нагадування і потрібен був огляд. Немає винятків, бо «тимчасове» — найтриваліше слово в інфраструктурі.

Через місяці реальний трафік зросв: інтеграція партнера стала вірусною. Система витримала навантаження. Рутинні нагрівання зменшили холодний I/O під час незначного фейловера. Політика зменшення потужності запобігла «прилипанню» реплік. CFO ніколи не дізнався, що таке «I/O‑запит», і це справжній критерій операційного успіху.

Типові помилки: симптом → корінна причина → виправлення

1) Симптом: Витрати I/O в Aurora зростають під час піків, хоча CPU в нормі

Корінна причина: Промахи кешу, послідовні сканування, диск‑спіли або регресії планів спричиняють багато фізичних читань.

Виправлення: Використовуйте pg_stat_statements + EXPLAIN (ANALYZE, BUFFERS), щоб знайти запити, що багато читають; додайте індекси, зменшіть набори результатів і усуньте тимчасові спіли. Уникайте масштабування обчислень як першого кроку, якщо ви I/O‑bound.

2) Симптом: Сховище швидко зростає і не здається, що зменшиться

Корінна причина: MVCC блоут, утримання WAL (слоти реплікації / відстаючі репліки), довгі транзакції або великі міграції.

Виправлення: Усуньте довгі транзакції, налаштуйте autovacuum, виправте відставання реплікації і заплануйте очищення блоуту. Очікуйте, що «звуження» вимагає цілеспрямованої роботи, а не сподівання.

3) Симптом: Додали репліки, але латентність записів погіршилася

Корінна причина: Вузьке місце було у записах або блокуваннях, а не в читаннях. Репліки додають складність і іноді додатковий тиск (шторм з’єднань, помилки маршрутизації).

Виправлення: Підтвердіть розподіл читів/записів і основні події очікування перед додаванням реплік. Якщо записи «гарячі», оптимізуйте їх, зменшіть конфлікти і пакетні операції.

4) Симптом: Aurora Serverless v2 агресивно масштабується під незначні сплески

Корінна причина: Шторми з’єднань, петлі повторів або балакучі ендпоінти створюють сигнали попиту, що виглядають як реальне навантаження.

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

5) Симптом: Після фейловеру витрати і затримки підстрибають на 10–30 хвилин

Корінна причина: Холодні кеші і нестабільність планів після зміни ролі; також шторми перепідключень у застосунку.

Виправлення: Налаштуйте нагрівання запитів, рознесіть перепідключення, застосуйте backoff і тримайте статистику та індекси в робочому стані.

6) Симптом: Використання тимчасових файлів вибухає під час піків

Корінна причина: Операції сортування/хешування виливаються на диск через обмеження пам’яті + висока паралельність.

Виправлення: Зменшіть набори результатів, додайте індекси, перепишіть запити і обмежте конкурентність пулером. Обережно налаштуйте work_mem з урахуванням обмежень з’єднань.

7) Симптом: «Ми підняли масштаб, але це майже не допомогло»

Корінна причина: Ви масштабували не ту вісь. Часто проблема в блокуваннях, I/O або мережевому шумі — не в CPU.

Виправлення: Використовуйте аналіз очікувань, метрики читання буферів і діагностику блокувань. Масштабуйте тільки після того, як зможете описати вузьке місце одним реченням.

8) Симптом: Рахунки залишаються високими після завершення піку

Корінна причина: «Прилипання» реплік, не повернений клас інстансу, зростання сховища або підвищена база через нові фічі.

Виправлення: Впровадьте політики зменшення потужності, робіть щотижневі перевірки «різниці рахунку», і порівнюйте поточний базовий QPS/суму запитів із минулим місяцем. Ставте регресії витрат у ті самі процеси, що й регресії продуктивності.

Чеклісти / покроковий план

Покроковий план, щоб запобігти несподіваним витратам під час піків

  1. Визначте пік: пікова QPS, пікова конкуренція і ендпоінти, що мають значення. Якщо ви не можете його визначити, ви не зможете його бюджетувати.
  2. Інструментуйте правильні метрики: топ‑запити за сумарним часом, buffer reads, temp bytes, кількість з’єднань, очікування блокувань, відставання репліки, обсяг WAL.
  3. Встановіть обмеження: макс‑з’єднання, розмір пулу, statement_timeout, idle_in_transaction_session_timeout.
  4. Напишіть бюджети повторів: експоненційні backoff з джиттером і ліміт на повтори, щоб ваш застосунок не перетворював один таймаут у десять.
  5. Передзарахуйте, де це вигідно: кешуйте і матеріалізуйте дорогі читання, які відбуваються під час піків. Віддавайте перевагу передбачуваним обчисленням замість непередбачуваного I/O.
  6. Використовуйте репліки усвідомлено: тільки коли читальне масштабування — реальний вузький місце; встановіть правило авто‑зменшення або явне завдання на прибирання після інциденту.
  7. Налаштуйте autovacuum для «гарячих» таблиць: орієнтуйтеся на виявлений churn, а не на значення за замовчуванням. За замовчуванням — консервативні, а не людяні.
  8. Плануйте фейловери: шторми перепідключень і холодні кеші — частина життя; тестуйте процедури нагрівання і backoff при перепідключенні.
  9. Бюджетуйте за вимірниками: години обчислень, години реплік, I/O, зростання сховища і утримання бекапів. Піки зачіпають більше ніж одну вимірність.
  10. Проводьте drill піків щоквартально: відтворюйте трафік, перевіряйте продуктивність і драйвери витрат. Якщо перевіряєте тільки одне — інше вас здивує.

Чекліст аварійної відповіді (під час піку)

  • Підтвердіть, чи навантаження реальне або це повтори (перевірте помилки застосунку і лічильники повторів).
  • Перевірте з’єднання й активні сесії; увімкніть пулінг або негайно зменшіть розміри пулів, якщо потрібно.
  • Знайдіть топ‑запити за сумарним часом і перевірте регресії планів; застосуйте безпечні індекси або виправлення запитів.
  • Перевірте на накопичення блокувань; зупиніть блокуючі DDL; при необхідності вбийте найгірших блокерів.
  • Якщо ви додаєте репліки або масштабуєте вгору, створіть квиток на зменшення потужності з терміном одразу.

Чекліст пост‑інциденту (наступного дня, коли емоції спадають)

  • Порівняйте вікно піку з базою: склад запитів, temp bytes, попадання в кеш, обсяг WAL.
  • Визначте «перший доміно» (деплой, міграція, партнерський трафік, узгодження cron‑ів).
  • Напишіть одну превентивну зміну, яка зменшить підсилення (пулінг, кешування, перепис запиту).
  • Аудитуйте зміни топології (репліки, розмір інстансів) і відкотіть тимчасову потужність.
  • Перегляньте драйвери зростання сховища (блоут, утримання WAL, довгі транзакції) і заплануйте ремедіацію.

Поширені питання

Чи Aurora завжди дорожча за стандартний PostgreSQL?

Ні. Aurora може бути дешевшою, якщо ви цінуєте керовану надійність збереження, швидкий фейловер і передбачувані операції — особливо якщо в іншому випадку ви б надмірно провізіонували EC2 і сховище для надійності. Вона може бути дорожчою, коли навантаження інтенсивно використовує I/O або є неефективною, бо білінг на основі споживання миттєво виявляє втрати.

Яка найпоширеніша причина стрибків витрат?

Підсилення навантаження: повтори + шторми з’єднань + неефективні запити. Невелике збільшення латентності запускає повтори, що збільшують навантаження, що ще більше підвищує латентність. Лічильник крутиться поки ви дебагуєте.

Чи варто використовувати Aurora Serverless v2 для спайків?

Використовуйте його, коли ваші піки реальні і ви вже контролюєте поведінку з’єднань. Якщо піки в основному самовикликані (таймаути, повтори, балакучі запити), serverless масштабуватиметься відповідно до хаосу і виставить вам рахунок.

Чи зменшують read replicas витрати I/O в Aurora?

Іноді — але не автоматично. Репліки можуть розподілити читальне навантаження, але вони також можуть створити більше роботи, якщо погано маршрутизувати, повторно нагрівати кеші або тримати репліки недовантаженими. Вимірюйте читальний I/O і розподіл запитів до і після додавання реплік.

Чому сховище не звузилося після видалення даних?

У PostgreSQL видалення створює мертві tuple‑и; простір стає внутрішньо повторно використаним, а не обов’язково повертається в underlying storage без важких операцій. В Aurora «автоскейлінг сховища» не означає миттєвого звуження. Плануйте звуження простору як проект, а не як бажання.

Які найкращі обмежувачі для контролю піків?

Пулінг з’єднань, розумні max_connections, statement_timeout і бюджети повторів. Це знижує класичну каскаду, коли база гальмує, клієнти панікують, і система з’їдає сама себе.

Як зрозуміти, CPU‑bound я чи I/O‑bound?

Дивіться плани запитів з BUFFERS, temp bytes і хост‑рівневий iowait (для самовпорядкованих). CPU‑bound інциденти показують високий CPU з відносно низькими фізичними читами; I/O‑bound — значні buffer reads і тимчасові спіли, часто при помірному CPU.

Чи можна прямо обмежити витрати Aurora?

Ви можете обмежити поведінки, що створюють витрати: лімітувати репліки, мін/макс потужності serverless, кількість з’єднань і повторів. Платформа не зупинить споживання I/O сама по собі; ваша архітектура та обмежувачі мають це робити.

Який найнадійніший «перший фікс» під час піку?

Зменште підсилення. Обмежте або відсічіть найгірший ендпоінт, увімкніть пулінг і уповільніть перепідключення/повтори. Потім оптимізуйте запити. Масштабування — пізніше, коли ви знаєте, що саме масштабуєте.

Коли обирати plain PostgreSQL замість Aurora?

Обирайте самовпорядкований (або простіший керований Postgres), коли ви хочете передбачуваність «платити за ємність», у вас є сильна оперативна майстерність, і робоче навантаження стабільне настільки, що надпровізіонування прийнятне. Обирайте Aurora, коли ви цінуєте швидкий фейловер і керовану надійність сховища і готові інженерити контролі витрат навколо споживання.

Висновок: наступні кроки, що дійсно зменшують несподіванки

Піки видають правду. Ваш рахунок за базу даних — лише один зі способів, яким система здає.

Якщо ви обираєте між PostgreSQL і Aurora PostgreSQL, не зводьте це до «керований проти некерованого». Зведіть до того, який режим відмови ви хочете боротися в першу чергу. З самовпорядкованим Postgres ви боротиметеся з насиченням і ємністю. З Aurora ви битиметеся з підсиленням і лічильниками. Обидва поєдинки можна виграти, але потрібні різні рефлекси.

Практичні наступні кроки:

  1. Інструментуйте реальність на рівні запитів: увімкніть pg_stat_statements і побудуйте дашборд для топ‑запитів за сумарним часом, temp bytes і buffer reads.
  2. Впровадьте пулінг і бюджети повторів: розглядайте з’єднання і повтори як одночасні контроли витрат і надійності.
  3. Налаштуйте vacuum серйозно: знайдіть таблиці з великим churn і дайте autovacuum ресурси і пороги, щоб справлятися.
  4. Напишіть політику масштабування: зміни реплік і розміру інстансів мають мати план зменшення з власником і дедлайном.
  5. Проведіть drill піку: симулюйте вашу найгіршу годину, потім перевірте і графіки латентності, і драйвери витрат. Якщо перевіряєте лише одне — інше вас здивує.

Зробіть це, і ваш наступний пік все ще буде стресовим — продакшен завжди стресує — але принаймні він не прийде з другим інцидентом у вигляді PDF‑рахунку.

← Попередня
Proxmox pveperf показує нісенітницю: як правильно тестувати продуктивність
Наступна →
Якірні посилання як у документації: іконки при наведенні, зсуви та клікабельні заголовки

Залишити коментар