Велика таблиця ще не діагноз

Сто мільйонів рядків не заважають швидкому пошуку за хорошим індексом. Менша таблиця може гальмувати через поганий план, довгу транзакцію або конкуренцію за гарячий рядок. Розмір потрібен для capacity planning, але не пояснює повільний запит.

Дивимося частоту запитів, час виконання, прочитані рядки, очікування пулу, блокування й дисковий I/O. Запит за 200 мс, який викликають десять тисяч разів, може коштувати більше рідкісного звіту за п'ять секунд. Розкладаємо навантаження за ендпоінтами й клієнтами.

EXPLAIN ANALYZE у PostgreSQL показує фактичне виконання. Порівнюємо очікувані й реальні рядки, час та ділянки плану. Велика різниця часто означає неправильну оцінку даних. ANALYZE запускає запит, зокрема той, що змінює записи. Для такого розбору потрібні відповідне середовище й контроль наслідків.

Беремо період, коли люди помітили проблему, й порівнюємо зі звичайним. Частота, розмір вибірки, активні транзакції, очікування: що змінилось? Середнє за добу приховує десятихвилинний пік. Вікно є частиною діагностики, а не лише зручним масштабом графіка.

Ранжуємо запити за сумарним часом і окремою затримкою. Один список показує витрати, інший повільні користувацькі шляхи. Частий короткий SQL може бути хорошою ціллю, хоча клієнт не назве його повільним. Списки відповідають на різні питання й не зобов'язані збігатися.

У плані дивимося фактичні рядки, проходи й проміжні результати. JOIN для малої вибірки може стати дорогим у великого акаунта. Перевіряємо типові параметри, популярні фільтри та перекоси. Один зручний ID не доводить придатність плану для всієї роботи продукту.

Для блокування шукаємо власника й причину. Запит виглядає дорогим за часом, хоча майже не виконується. Індекс не зніме чужу транзакцію. Розбираємо, чому вона не завершується й чи можна скоротити конкурентну ділянку. Це інший клас проблеми, ніж повільне читання великого обсягу.

Вимірюємо безпечно. Навіть SELECT може сильно навантажити, зміна даних вплине на людей. Починаємо з відповідної копії чи обмеженого експерименту, записуючи відмінності ресурсів і даних. Мета знайти причину без некерованого відтворення аварії для клієнтів.

Оптимізуємо запити перед масштабуванням БД

На сторінці 50 замовлень. Застосунок отримує список, а потім окремо читає клієнта для кожного рядка. Маємо 51 похід у БД, звичайний N+1. Пакетне завантаження, відповідний JOIN та обмеження вибірки часто дадуть більше нового сервера. Виграш залишиться й після зростання навантаження.

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

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

Прибирання N+1 не має створити гігантський JOIN. Сотні позицій на замовлення множать рядки й передачу. Іноді кращі два пакетні запити, іноді обмежений JOIN. Порівнюємо роботу й обсяг відповіді, не ставимо кількість SQL єдиною метою оптимізації.

Складений індекс допомагає певним умовам і порядку. Усім комбінаціям однаково не допоможе. Якщо UI дозволяє десятки довільних сортувань, обираємо важливі або обмежуємо продукт. Індексування кожного поєднання швидко стає дорогим для запису й обслуговування.

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

Транзакцію розбираємо з бізнес-шляхом. Тримати блокування, поки клієнт вводить карту, невдала межа. Коротке утримання зі строком і окрема оплата можуть допомогти. Але потрібні правила підтвердження та закінчення. Прибирання блокування не прибирає вимогу правильного результату.

Дивимося статистику, очищення місця, індекси й зміни схеми. Додавання колонок чи індексів теж конкурує з трафіком. Метод відповідає можливостям обраної БД й тестується за впливом. Швидший кінцевий запит не виправдовує години блокування під час зміни, яка його прискорила.

Для кешу домовляємося про свіжість

Довідник доставки й залишок останнього товару не кешуються за однаковими правилами. Для кожної сутності потрібні допустима затримка, інвалідація та поведінка при cache miss. Якщо клієнт бачить стару ціну, заздалегідь вирішуємо, яка потрапить у підтверджене замовлення.

Рахуємо сценарій без кешу. За hit rate 90% база отримувала лише десяту частину читань. Втрата кешу за того самого потоку збільшить їх приблизно вдесятеро. Допомагають ліміти паралельних оновлень, різні строки закінчення й тимчасові старі дані, де це допустимо. Перевіряти треба й холодний старт.

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

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

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

Hit rate дивимося за важливими класами. Загальні 95% можуть бути дешевим довідником, поки дорогий запит завжди промахується. Цінність у знятій роботі БД та latency людини. Частка знайдених ключів не дає готового висновку про виграш продукту.

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

Репліка змінює те, що бачить користувач

Клієнт зберіг адресу, оновив сторінку й побачив стару. Запис пройшов на primary, читання пішло на відсталу репліку. Зовні це схоже на втрату даних. Для такого шляху читаємо primary або застосовуємо інший механізм видимості власного запису.

Синхронна реплікація має різні режими. Підтвердження отримання запису відрізняється від підтвердження застосування. Суворіша гарантія може додати latency й залежність від доступності репліки. Потрібно розуміти, за якої відмови зупиниться запис.

Read replicas розвантажують читання, але не ділять потік запису. Дивимося lag, швидкість застосування змін і failover. Репліка не замінює бекап: помилкове видалення швидко повториться на всіх копіях, тому відновлення перевіряємо окремо.

Читання маршрутизуємо за операцією. Каталог і давній звіт можуть терпіти lag, підтвердження адреси зазвичай ні. Primary для кількох чутливих шляхів іноді простіша за універсальне очікування репліки. Ці читання лишаються в бюджеті навантаження основної БД.

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

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

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

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

Партиції та шарди: де різниця

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

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

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

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

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

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

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

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

Плануємо перемикання та відновлення

За можливості змінюємо одну річ за раз і порівнюємо latency, throughput, блокування, ціну та запас при збої. Якщо одночасно переписати SQL, змінити сервер і рознести дані, важко зрозуміти, що допомогло, а що створило проблему.

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

Обираємо перевірку повноти до переносу. Однакова кількість рядків не означає однаковий зміст. Порівнюємо ID, суми, зв'язки й вибіркові записи. Бізнес-інваріанти не дозволяють успішній команді копіювання приховати зміну сенсу даних, яка проявиться лише в наступній операції клієнта.

За активного запису переносимо зміни після копії. Потрібні порядок, повтори й виявлення відставання. Два виклики запису не гарантують цілісність: один успішний, інший ні. Розбіжність має виявлятися й виправлятися. Це окремий протокол, а не лише додатковий виклик у функції.

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

Rollback враховує нові записи. Після використання схеми повернення потребує сумісності чи переносу змін назад. Іноді безпечніше вимкнути функцію й виправити новий шлях. Рішення приймаємо завчасно, коли є час оцінити наслідки, а не під тиском аварії.

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

Обираємо наступний крок для бази замовлень

Список замовлень сповільнився. Порівнюємо періоди, очікування й виконання. Знаходимо N+1 та зайві поля, перевіряємо виправлення на малих і великих клієнтах. Далі запис і обслуговування: індекс не повинен з'їсти запас іншого шляху. Критерій корисності включає результат для всієї характерної роботи, а не один швидший тестовий SELECT.

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

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

Перехід готовий за збігу контрольних даних, збереження нових записів, перевіреного продукту й дій при розбіжності. Спостерігаємо до видалення старого шляху. Наступний крок знову може бути локальним SQL. Це нормальний рух межі після покращення. Сам факт попереднього скейлінгу не обґрунтовує ще складнішу розподілену схему.

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

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

До всіх статей