devops-курс Все курсы

✻ Урок 10.3 · Тема 10: Итоговый практикум

Бэкапы, восстановление и ёмкость

⏱ 4 ч

Зачем это нужно

Бэкап (backup, резервная копия) это сохранённые данные, из которых можно вернуть потерянное, как мы разбирали в уроке 6.5. Бэкап, который ни разу не восстанавливали, это надежда, а не доказанная защита. Дамп (dump) напоминает выгрузку содержимого базы в отдельный файл из того же урока. Бакет (bucket) это именованное место для файлов в объектном хранилище, знакомое по уроку 9.5.

Типичная история: дамп пять месяцев исправно лежит в бакете, а в день аварии выясняется, что он обрезан, зашифрован потерянным ключом или сделан без ролей. Роль (role) задаёт пользователя базы и его права, как в уроке 4.4. Вторая половина той же проблемы: диск заканчивается ночью, хотя направление роста занятого места было видно за неделю. Оба случая ловятся одним подходом: измерить заранее.

В уроке ты подробно разберёшь два ориентира из урока 6.5: RPO (Recovery Point Objective, допустимая потеря данных во времени) и RTO (Recovery Time Objective, допустимое время простоя). Проведёшь restore drill, учебное восстановление с замером времени из того же урока, и посчитаешь запас ёмкости «Заметок»: сколько ещё запросов или данных система выдержит.

Шаг проекта: в ~/notes появляются docs/dr.md (RPO/RTO и порядок восстановления), docs/capacity.md (предел нагрузки и прогноз диска) и scripts/restore-drill.sh. DR (Disaster Recovery, восстановление после катастрофы) это заранее подготовленный путь возвращения сервиса после потери данных или основной площадки. Как запасной комплект документов после пожара, он помогает начать заново, когда привычное место недоступно; без него придётся искать копии и придумывать порядок действий в момент аварии. Подробно разберём ниже.

Что нужно знать

Картина целиком

Представь фотографа, который снимает свадьбу. Умный фотограф не хранит все снимки на одной карте памяти: он копирует их на второй носитель, а один комплект отдаёт домой. И, главное, хотя бы раз проверяет, что фотографии с копии открываются. Иначе про «всё сохранено» он узнает уже после катастрофы.

Эта тема про то же самое для «Заметок»:

  • бэкап (backup, резервная копия) это копия данных, из которой можно всё вернуть;
  • RPO и RTO это два числа: сколько данных мы готовы потерять и сколько готовы простоять;
  • restore drill это проверка, что копия действительно восстанавливается, с секундомером;
  • ёмкость (capacity) это вторая половина: хватит ли системе диска, памяти и мощности завтра, а не только сегодня.
flowchart TD
    A["Данные Заметок<br>PostgreSQL"] -->|по расписанию| B["Бэкап"]
    B --> C["Хранилище<br>вне основной площадки"]
    C --> D["Restore drill<br>восстановить, сверить,<br>замерить время"]
    D --> E["docs/dr.md<br>RPO, RTO, порядок"]
    F["Ёмкость<br>предел, запас, прогноз диска"] --> G["docs/capacity.md"]

Схема читается сверху вниз: копия уходит в хранилище, дрилл доказывает, что она живая, а результат оседает в документах. Ёмкость идёт отдельной веткой.

Аналогия неидеальна: фотографию можно посмотреть глазами, а бэкап базы данных «на глаз» не проверишь, поэтому проверять приходится восстановлением. Каждое понятие ниже разберём подробно.

Теория

Что такое бэкап и от чего он защищает

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

Есть источник (база данных), процесс копирования (запускается по расписанию, например раз в час), хранилище и срок хранения (retention, «храним 14 дней, старше удаляем», иначе место кончится). Восстановление это обратный путь: взять копию, поднять чистую базу, загрузить данные.

Разработчик выполнил DELETE FROM notes без условия WHERE, и все заметки удалены: удаление строк и условие их выбора знакомы по уроку 4.4. В кластере три реплики базы, копии на других серверах, повторяющие изменения главной, как в уроке 9.5. Ты думаешь: «есть три копии, значит, данные живы». Но реплика получает и удаление, и вскоре пусты все три. Реплика защищает от смерти сервера, а от ошибки человека защищает бэкап, сделанный до ошибки.

Прикинь сам: в кластере три реплики базы, и кто-то выполнил DELETE FROM notes без WHERE. Сколько копий данных останется целыми?

Ни одной: реплики повторяют и удаление. Вернуть заметки поможет только бэкап, сделанный до ошибки.

Проверь понимание: сервер с базой сгорел. Есть реплика на другом сервере. Нужен ли бэкап?

Ответ

Нужен. Сейчас реплику можно повысить до главной, и сервис оживёт. Но завтра появится ошибка в коде, которая испортит записи, и реплика повторит испорченные данные. Бэкап это защита от ошибок и злого умысла, а не только от поломок железа.

Осторожно: «реплика это бэкап». Нет. Реплика повторяет и хорошее, и плохое. Отдельно про это ниже, в разделе про HA и DR.

Главное: реплика защищает от смерти сервера, а бэкап, сделанный до ошибки, защищает от ошибок человека и порчи данных.

Копия есть. Теперь договоримся, сколько данных и времени мы готовы потерять.

RPO и RTO: два числа, о которых договариваются заранее

Слова «бэкапы есть» ничего не обещают. Нужно ответить на два конкретных вопроса, и на них ответы бывают разные.

RPO (Recovery Point Objective, допустимая потеря данных) отвечает на вопрос «за сколько времени до аварии мы готовы потерять записи». Аналогия: ты сохраняешь документ раз в час. Если компьютер выключился, потеряешь работу не больше чем за час. Значит, твой RPO равен часу.

RTO (Recovery Time Objective, допустимое время простоя) отвечает на вопрос «за сколько мы обязаны вернуть сервис». Аналогия: сколько времени ты готов сидеть без света, пока электрик едет и чинит.

При ежечасных копиях можно потерять почти 60 минут записей, если авария случилась прямо перед следующей копией. Значит, цель RPO меньше часа такая схема не выполнит; длительность выгрузки и ошибки могут увеличить потерю, как разберём дальше. RTO сравнивают со всем временем простоя: заметить аварию, разбудить дежурного, найти бэкап, расшифровать, восстановить, проверить, переключить трафик. Одной команды pg_restore, восстановления дампа, недостаточно.

Авария в 14:59:50, бэкапы каждый час (в 14:00 и 15:00): потеряно почти 60 минут.

Измеренное время простоя для сравнения с RTO:
 обнаружить 5 мин + дежурный 5 мин + найти бэкап 5 мин
 + восстановить 10 мин + проверить 5 мин + переключить 3 мин = 33 минуты
 (а команда pg_restore в этом списке занимает только 10)

RPO и RTO не выбираются из воздуха: их задаёт бизнес («сколько денег мы теряем за час простоя?»), а инженер показывает цену. Для защиты подтверждённых записей при отказе основного сервера используют синхронную репликацию (synchronous replication): повторение изменений базы на запасном сервере с ожиданием подтверждения от него. Это как передать важную квитанцию помощнику и сказать посетителю «сохранено» только после того, как помощник подтвердил получение. Такая схема уменьшает риск потери последних записей при отказе, но замедляет запись и не защищает от ошибочного удаления; подробнее о репликации и её ограничениях в уроке 10.4.

Для цели RPO 5 минут используют частое архивирование WAL, журнала изменений из урока 9.5, подробнее разберём ниже. Для цели RPO 1 час может подойти pg_dump, программа выгрузки базы, по расписанию. Само расписание ещё не доказывает, что цель выполнена: копирование и доставка в хранилище тоже требуют времени.

Прикинь сам: бэкап раз в 6 часов, авария за минуту до следующей копии. Сколько записей можно потерять?

Почти 6 часов: цель RPO меньше 6 часов такое расписание не выполнит. А время простоя считают отдельно, это RTO.

Проверь понимание: бэкап делается каждые 6 часов, восстановление занимает 20 минут. Чему равны RPO и RTO?

Ответ

При исправных завершённых копиях можно потерять почти 6 часов записей в худшем случае. Если цель RPO меньше 6 часов, такое расписание ей не соответствует. Для сравнения с целью RTO к 20 минутам добавляются обнаружение, сбор людей, поиск и расшифровка бэкапа, проверка и переключение. Полное время измеряют на restore drill.

Осторожно: RTO равен времени восстановления бэкапа. Нет, это цель для всего простоя. На учебном восстановлении измеряют фактическое время и проверяют, укладывается ли оно в цель.

Главное: RPO говорит, сколько данных можно потерять, RTO говорит, сколько можно простоять, и оба числа согласуются с бизнесом, а не берутся с потолка.

Расписание ещё не гарантирует RPO. Проверим, как узнать возраст копии, на которую можно рассчитывать.

Свежесть копии: расписание ещё не обещает RPO

Задача может запускаться каждый час и каждый час завершаться ошибкой. Для восстановления важен возраст последней пригодной копии, которая уже находится в доступном хранилище. Свежесть бэкапа (backup freshness) показывает, насколько далеко эта копия отстоит от текущего времени. Без такой проверки зелёная отметка «запуск состоялся» может скрывать сутки без защиты.

Ты каждый вечер фотографируешь семейный альбом и отправляешь снимки родственнику. Нажать кнопку отправки недостаточно: если интернет пропал, у родственника остались вчерашние фотографии. Оговорка: у базы важен ещё момент, которому соответствуют данные, а не только время доставки файла. Сегодня можно доставить файл, снятый неделю назад, и он всё равно будет старым.

Для каждого бэкапа различай три момента: к какому времени относятся данные, когда закончилась выгрузка и когда копия стала доступна для восстановления. Для логического дампа данные соответствуют согласованному снимку базы в начале выгрузки: изменения после этого момента в него не входят. Для оценки возможной потери отсчитывают время от момента данных последней пригодной копии. Для контроля доставки отдельно смотрят, завершились ли выгрузка и загрузка, можно ли прочитать объект и прошла ли проверка восстановления. Если используется PITR, вместо времени базовой копии учитывают последний момент, до которого можно восстановиться с имеющимся WAL.

Цель RPO равна часу. Дамп запускается в 14:00, содержит данные на 14:00 и становится доступен в хранилище в 14:12. Следующий запуск в 15:00 падает. Авария происходит в 15:20. Последняя пригодная копия соответствует 14:00, поэтому возможная потеря равна 15:20 - 14:00 = 1 час 20 минут. Возраст доставленного файла от 14:12 составляет 68 минут, но даже он недооценивает потерю: записи за первые 12 минут в файле отсутствуют.

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

Прикинь сам: копия на 14:00 доступна с 14:12, запуск в 15:00 упал, авария в 15:20. Какая потеря?

15:20 - 14:00 = 1 час 20 минут: считаем от времени данных копии, а не от времени доставки файла.

Проверь понимание: ежечасная задача успешно запустилась в 16:00, но файл не загрузился. Последняя пригодная копия содержит данные на 14:00. Авария в 16:30. Можно обещать потерю не больше часа?

Ответ

Нет. Доступная точка восстановления отстаёт на 16:30 - 14:00 = 2 часа 30 минут. Запуск в 16:00 не добавил пригодную копию. Нужно исправить доставку, проверить результат и сообщить, что цель RPO пока не выполняется.

Осторожно: RPO это согласованная цель, а возраст последней пригодной копии помогает оценить текущий риск. Если цель час, а копии уже два часа, сама цель не стала «два часа»: защита перестала ей соответствовать.

Главное: для RPO важен момент, к которому относятся данные последней пригодной копии, а не факт запуска задачи.

Теперь о самих видах бэкапа: чем они отличаются по точности и скорости.

Три вида бэкапа PostgreSQL

Нужно вернуть базу ровно на минуту до ошибочного DROP TABLE. Дамп раз в час так не сможет, а журнал изменений сможет.

У них разные скорость, размер и точность. Полезно понимать, что выбираешь.

Логический бэкап (pg_dump, «выгрузка») читает данные из базы и записывает их как набор команд или архив. Аналогия: переписать содержимое книги от руки: медленно, зато можно перенести в другую книгу другого формата или взять одну главу. Плюсы: переносится между версиями PostgreSQL, можно восстановить одну таблицу. Минусы: долго на больших базах, RPO равен интервалу между выгрузками. Формат custom (--format=custom) сжат, содержит оглавление, проверяется командой pg_restore --list и восстанавливается выборочно. Формат plain это просто текстовый SQL без оглавления.

Физический бэкап (pg_basebackup или согласованный снимок диска) копирует сами файлы базы. Аналогия: отксерокопировать книгу целиком. В отличие от книги, база продолжает меняться: произвольное копирование файлов работающей базы может дать непригодную копию, поэтому нужен предусмотренный PostgreSQL способ или согласованная процедура снимка. Такой бэкап привязан к мажорной версии (major version, основному номеру версии): файлы PostgreSQL 17 нельзя напрямую открыть сервером PostgreSQL 18.

PITR (Point-In-Time Recovery, восстановление на момент времени) это физический бэкап плюс непрерывный архив WAL. WAL (write-ahead log, журнал предзаписи) это журнал, в который PostgreSQL сначала записывает каждое изменение, а уже потом меняет файлы данных. Если сохранять этот журнал непрерывно, то можно взять базовую копию и «проиграть» журнал до нужной секунды, например до момента перед ошибочным DELETE. Аналогия: кассовая лента магазина. Если у тебя есть вчерашний остаток и вся лента, ты восстановишь состояние кассы на любую минуту. Именно так работает ScheduledBackup в CloudNativePG из урока 9.5.

Вид Точность (RPO) Скорость восстановления Особенность
pg_dump интервал между дампами медленно на больших базах переносится между версиями
физический бэкап интервал между копиями быстро привязан к версии
PITR (копия + WAL) секунды средне нужно хранить архив WAL

Прикинь сам: нужно вернуть базу на 10:14:30, дамп снят в 10:00. Подойдёт ли дамп?

Нет: он вернёт состояние на 10:00 и потеряет 14 минут работы. Нужен PITR: базовая копия плюс WAL до нужной секунды.

Проверь понимание: тебе надо вернуть базу ровно на 10:14:30, за минуту до ошибочного DROP TABLE. Какой вид бэкапа подойдёт?

Ответ

Только PITR: базовая копия плюс WAL, проигранный до 10:14:30. Дамп, снятый в 10:00, вернёт базу на 10:00 и потеряет 14 минут работы.

Осторожно: PITR отменяет необходимость проверки. Нет: непрерывный архив тоже может тихо ломаться (кончилось место в хранилище, поменялись права), поэтому проверка обязательна для любого вида.

Главное: pg_dump переносим и выборочен, физическая копия быстра, PITR точен до секунды, но все три нужно проверять восстановлением.

Бэкап защищает от потери данных. А как он отличается от отказоустойчивости?

HA и DR: не путай отказоустойчивость с восстановлением

Запасное колесо спасает от прокола, но не от кражи машины. Для сервиса те же две роли у HA и DR.

HA (High Availability, высокая доступность) помогает сервису оставаться работоспособным при отказе отдельного узла. Доступность (availability) это доля времени или запросов, когда сервис выполняет обещанную работу, как в уроке 8.1. Есть запасной сервер, и когда основной отказывает, система переключается на запасной: это failover, знакомый по уроку 9.5. Переключение обычно быстрее полного восстановления, но его время тоже измеряют. DR возвращает данные и сервис после потери данных или целой площадки: пожара в дата-центре, ошибки в коде, вируса.

Аналогия: запасное колесо (HA) спасёт от прокола, но не спасёт от кражи автомобиля. От кражи защищает страховка (DR). Аналогия ломается на цене: запасное колесо стоит копейки, а полноценный DR (вторая площадка) стоит как ещё одна система.

  HA DR
Защищает от смерти узла, диска ошибки человека, потери площадки, вирусов
Способ реплики, failover бэкапы вне кластера, план восстановления
Время реакции секунды минуты и часы

Правило 3-2-1 (из урока 6.5): три копии данных, на двух разных видах носителей, одна из них вне площадки. К нему добавляют: бэкап зашифрован, ключ лежит отдельно от бэкапа (иначе воры получат и данные, и ключ; а если ключ потерян, то и ты не прочитаешь свои копии), а дрилл выполняется по расписанию и падает громко.

Прикинь сам: в облаке раз в день автоматически делается снимок диска в том же аккаунте и регионе. Закрыт ли пункт «вне площадки» в правиле 3-2-1?

Нет: копия на той же площадке и в том же аккаунте. Нужна копия в другом регионе или аккаунте.

Главное: HA переживает смерть узла за секунды, DR возвращает данные после потери площадки или ошибки, а правило 3-2-1 требует три копии, два вида носителей и одну вне площадки.

План есть, но пока он не проверен, это надежда. Проверка называется restore drill.

Restore drill: доказательство, что бэкап живой

Бэкап, который ни разу не восстанавливали, это надежда, а не бэкап. Типичные истории: дамп пять месяцев исправно лежит в бакете, а в день аварии оказывается обрезанным, зашифрованным потерянным ключом или сделанным без ролей (роли пользователей PostgreSQL хранятся отдельно от базы).

Restore drill (учебное восстановление) делают по схеме:

  1. взять последний бэкап;
  2. поднять чистую базу в изолированном месте (не поверх боевой);
  3. восстановить бэкап;
  4. сверить данные с источником: число строк и наличие схемы;
  5. записать время: это первая часть будущего RTO.

Проверка «файл существует и не нулевой» ловит только самые грубые поломки. Проверка «число строк совпало» обнаруживает недостающие строки, но не замечает замену их содержимого. Схема базы (database schema) это устройство таблиц, столбцов и связей, как в уроке 4.4. Ниже разберём, как выбрать эталон для сверки и почему проверка файла не заменяет проверку восстановленных данных.

Реальный дамп твоей базы с семью заметками мы восстановим в чистом контейнере. Скрипт печатает DRILL OK: строк 7. Если снять с дампа хвост (обрезать файл, как при переполненном диске), pg_restore --list выдаст pg_restore: error: could not read from input file: end of file, а скрипт остановится с ошибкой. Ровно эту разницу и показывает дрилл: «файл есть» против «файл читается».

Прикинь сам: скрипт восстановил дамп и не упал, но число строк не сверял. Что он доказал?

Только то, что команда не упала. Вернулись ли нужные данные, без сверки неизвестно.

Осторожно: дрилл делают один раз при настройке. Нет: бэкапы ломаются со временем (сменился пароль, поменялась версия, кончилось место), поэтому дрилл выполняют по расписанию, например раз в месяц, и после каждого изменения БД или бэкапов.

Главное: restore drill поднимает чистую базу, восстанавливает копию, сверяет данные и замеряет время: это первая часть будущего RTO.

Что именно проверять при сверке? Целостность файла и правильность данных это разные вопросы.

Целостность файла и правильность данных: разные проверки

Бэкап может повредиться при передаче, но может быть и совершенно целым файлом с неверным содержимым. Контрольная сумма (checksum) это короткое значение, вычисленное по всем байтам файла, например программой sha256sum. Как отпечаток посылки перед отправкой и после получения, она помогает заметить изменение содержимого при копировании; без неё одинаковый размер двух файлов может создать ложную уверенность. Проверку с SHA-256 мы уже применяли в уроке 6.5.

Запечатанная коробка может приехать без повреждений, но внутри окажется чужой заказ. Контрольная сумма проверяет, что приехало отправленное содержимое. Чтобы узнать, правильный ли заказ отправили, надо открыть коробку и сверить вещи с заказом. Так же проверяют восстановленную базу: целостность архива и правильность данных отвечают на разные вопросы.

Сначала проверяют, что файл сохранился без изменения и его оглавление читается. Оглавление показывает объекты, перечисленные в архиве, но не гарантирует, что каждый участок данных можно восстановить. Затем выполняют полное восстановление в чистую базу и проверяют таблицы, число строк и выбранные значения. Последний шаг выполняет приложение: может ли оно прочитать заметки и сохранить новую? Каждый следующий шаг проверяет то, чего предыдущий ещё не доказал.

Для сверки нужен эталон (reference), заранее записанный ожидаемый результат. Это как список вещей перед отправкой посылки: без списка можно пересчитать приехавшее, но нельзя узнать, всё ли приехало. В учебном опыте эталон это семь заметок в исходной базе. В рабочей системе он должен относиться к тому же моменту, что и копия: текущая база могла уже измениться. Иначе исправный бэкап на 14:00 ошибочно сравнят с базой на 15:00 и объявят неполным.

До копирования в базе семь строк. Восстановление тоже дало семь. При этом в одной строке вместо «Купить молоко» записано пустое значение. Проверка числа строк проходит: 7 = 7. Проверка выбранной заметки не проходит: ожидаемый текст и полученный текст различаются. Совпадение количества не замечает замену текста, а контрольная сумма файла не замечает ошибку, которая уже присутствовала в момент выгрузки.

Для учебного набора заранее запиши количество и содержимое нескольких заметок, особенно самой новой. В результате укажи границы проверки: «семь строк совпали» ещё не означает «все данные исправны».

Прикинь сам: сумма файла совпала, восстановление прошло, строк 7 из 7, но в одной строке пустой текст. Какая проверка это заметит?

Только сверка содержимого выбранных заметок с эталоном: число строк и сумма файла пустой текст не заметят.

Проверь понимание: сумма файла совпала, восстановление прошло, число строк правильное. Доказывает ли это, что до создания копии приложение не испортило текст заметок?

Ответ

Нет. Проверки доказали сохранность файла и часть свойств восстановленной базы. Если неправильный текст уже попал в дамп, восстановление честно вернёт его. Нужны проверки содержимого и, при обнаружении порчи, копия до её возникновения.

Осторожно: Контрольную сумму считают только после аварии и получают длинную строку. Без сохранённого значения для исходного файла сравнивать её не с чем. А если кто-то может подменить и бэкап, и сохранённую сумму, совпадение не доказывает отсутствие подмены.

Главное: контрольная сумма доказывает неизменность файла, число строк ловит потери, а правильность содержимого проверяют сверкой с эталоном на тот же момент.

Проверенную копию нужно где-то хранить. Разберём, где и как долго.

Где хранить бэкап, шифрование и срок хранения

Копию на том же диске, что и база, можно потерять вместе с базой. Хранить её нужно отдельно и так, чтобы чужой не прочитал.

Бэкап нельзя держать на том же диске, что и база: сгорит диск, пропадёт всё сразу. Обычно копии уходят в объектное хранилище (object storage): сервис, куда складывают файлы по имени, как в почтовое облако. Он бывает облачным (S3 в AWS, Object Storage в Yandex Cloud) или локальным (MinIO из урока 9.5, который умеет тот же протокол S3). Файл лежит в бакете (bucket, «корзина»: папка верхнего уровня в хранилище).

Бэкап содержит все данные, поэтому его шифруют: без ключа украденный файл бесполезен. Ключ хранят отдельно от бэкапа (например, в Vault из урока 9.1). Ловушка: если ключ потерян, зашифрованный бэкап навсегда нечитаем. Поэтому ключ тоже нужно уметь восстановить, и дрилл включает расшифровку.

Срок хранения (retention). Копии копятся, и место в хранилище кончается. Задают правило: например, «ежечасные храним двое суток, ежедневные две недели, ежемесячные год». Без правила либо место кончится, либо всё старое будет удалено раньше, чем понадобится (например, ошибку заметят через неделю, а копии хранят только три дня).

Прикинь сам: ошибку в данных заметили через 10 дней, а ежедневные копии хранятся 7. Что это значит?

Копий до ошибки уже нет: их удалили по сроку хранения. Срок должен быть больше времени, за которое вы обычно замечаете проблему.

Осторожно: чем дольше храним, тем лучше. Хранение стоит денег и увеличивает риск утечки старых данных, поэтому срок выбирают осознанно, исходя из того, как быстро вы можете заметить потерю.

Главное: бэкап хранят вне основного диска, шифруют, ключ держат отдельно, а срок хранения выбирают по тому, как быстро вы замечаете потерю.

С данными разобрались. Вторая половина темы: хватит ли системе мощности и места.

Пропускная способность, задержка и предел нагрузки

Сервис не бесконечен: при какой-то нагрузке он начинает отвечать медленно, а потом ошибками. Нужно знать, где эта граница, до того, как её найдут пользователи в день распродажи.

Сначала три слова:

  • rps (requests per second) запросов в секунду, то есть пропускная способность (throughput): сколько работы сервис делает за секунду;
  • задержка (latency): сколько времени занимает один запрос;
  • p95 (95-й перцентиль): такое время, что 95% запросов быстрее, а 5% медленнее. Если из 100 запросов выстроить времена по порядку, p95 это 95-е число. Среднее скрывает медленные запросы, а p95 их показывает (подробнее в уроке 8.3).

Нагрузочный тест (load test) это опыт, в котором программа изображает множество пользователей и измеряет ответы сервиса. Как проверка касс перед распродажей, он помогает заранее увидеть очередь; без него предел найдут настоящие покупатели. Здесь растят число одновременных запросов (параллелизм, concurrency) ступенями и смотрят, как меняются rps и p95; проектирование таких опытов продолжим в уроке 10.8. Пока касс хватает, ожидание небольшое. Когда все кассы заняты, появляется очередь. Оговорка: разные запросы требуют разной работы, поэтому число «покупателей» само по себе не говорит, сколько займёт обслуживание.

Предел это точка «колена», а не среднее по всем ступеням.

Закон Литтла. Есть простая формула, которая связывает три числа: L = λ * W. L это число запросов «внутри» системы (обрабатываются или ждут), λ (лямбда) пропускная способность в запросах в секунду, W среднее время одного запроса в секундах. Из неё получается оценка потолка: если одновременно можно обрабатывать L = 4 запроса (четыре воркера, воркер это исполнитель, который занимается одним запросом за раз), а один запрос занимает W = 0.05 секунды (50 мс), то λ = L / W = 4 / 0.05 = 80 запросов в секунду. Выше этого очередь растёт лавиной.

Запас (headroom) это разница между пиком нагрузки и пределом. Разумная цель: пик не выше 60-70% предела. Причины две: нагрузка бывает всплесками, и при отказе одной реплики предел падает.

Разобранный пример: у приложения три реплики, предел 60 rps на реплику, то есть 180 rps всего, если общая база и сеть выдерживают такую нагрузку. Пик трафика 100 rps, это 100 / 180 * 100 ≈ 56% предела. Отказала одна реплика: предел стал 120 rps, пик уже 100 / 120 * 100 ≈ 83%. Сервис ещё может отвечать, но прежнего запаса нет. Поэтому долю нагрузки проверяют и при отказе, а правило «60-70%» используют как начальную оценку, а не гарантию.

Прикинь сам: пик 40 rps, предел 50 rps, реплик три. Достаточно ли запаса?

Нет: пик 80% предела, а при отказе одной реплики предел падает примерно до 33 rps, и сервис перегружен.

Осторожно: предел это число, которое можно достичь. Нет, в пределе задержка уже неприемлема. Рабочая нагрузка должна оставаться заметно ниже.

Главное: предел это «колено» на графике p95, а не максимум rps, и пик держат не выше 60-70% предела с учётом отказа реплики.

С мощностью понятно. Теперь о диске: как узнать, что он кончится, заранее.

Ёмкость диска и прогноз

Диск заканчивается ночью, хотя тренд был виден за неделю. Причина проста: алерт «занято 90%» срабатывает, когда времени уже мало. Лучше алерт «диск закончится через сутки».

Prometheus умеет предсказывать: функция predict_linear берёт значения метрики за окно (например, за последние 6 часов), проводит через них прямую линию и продлевает её вперёд на заданное время. Если прямая уходит ниже нуля, значит, при сохранении такого роста свободное место закончится раньше. Аналогия: ты едешь со средней скоростью и оцениваешь, когда доедешь до заправки, не учитывая пробки. Оговорка: ночные бэкапы и очистка старых файлов меняют скорость, поэтому прямая может врать. VACUUM, уборка устаревших версий строк внутри PostgreSQL из урока 4.4, обычно позволяет базе повторно использовать место, а не возвращает его операционной системе.

Диск заполнялся 10 ГБ за неделю, свободно 5 ГБ.

скорость = 10 ГБ / 7 суток = 1.43 ГБ в сутки
запас 5 ГБ / 1.43 ГБ в сутки = 3.5 суток

predict_linear делает такой же расчёт, только по реальным точкам на графике. Если результат predict_linear(..., сутки) < 0, то «через сутки диска не будет» и пора действовать: расширить PVC (том с данными, урок 5.5), почистить или найти причину роста.

Прикинь сам: диск рос 10 ГБ за неделю, свободно 5 ГБ. Через сколько суток закончится место?

10 / 7 = 1,43 ГБ в сутки, 5 / 1,43 = 3,5 суток.

Проверь понимание: алерт «занято 90%» и алерт «через 24 часа закончится» сработали в одно и то же время. Какой был полезнее вчера?

Ответ

Прогнозный. Он сработал бы за сутки до проблемы и оставил время починить днём. Алерт «90%» сработает только тогда, когда осталось мало, и разбудит человека ночью.

Осторожно: расширить диск это окончательное решение. Это митигация, временное уменьшение ущерба из урока 10.2: если рост вызван накоплением WAL (архив не уходит в хранилище) или бэкапов без ротации, удаления старых копий по сроку хранения, диск снова кончится. Причину ищут отдельно.

Главное: прогнозный алерт predict_linear даёт время на спокойную починку днём, а расширение диска это митигация, причину роста ищут отдельно.

Диск нужен ещё и для самого восстановления. Посчитаем, сколько.

Ёмкость для восстановления: место нужно ещё до переключения

Свободного места для обычной работы может хватать, а для безопасного восстановления уже нет. Пока ты сохраняешь старую базу для расследования и поднимаешь новую, некоторое время существуют обе. Если этот запас не предусмотрен, правильный план «восстановить отдельно» остановится на создании нового тома.

При переезде старую квартиру нельзя освободить, пока вещи не перевезены в новую. На время нужны обе квартиры и место для коробок. У базы «коробками» будут дамп, журнал WAL и временные файлы, которые PostgreSQL создаёт при работе. Граница аналогии: сжатая коробка с архивом может оказаться намного меньше данных после распаковки, поэтому размер дампа не равен требуемому размеру новой базы.

Оценивают размер восстановленных таблиц и индексов, структур для ускорения поиска из урока 4.4. Добавляют место для WAL, временных файлов, роста и дампа, если он лежит на том же диске. Каждый диск проверяют отдельно: свободное место ноутбука не помогает тому базы в Kubernetes. Для отдельного тома проверяют возможность его выделить.

Старая база занимает 20 ГБ. В учебной оценке новая база после восстановления тоже займёт 20 ГБ, дамп занимает 4 ГБ, WAL и временные файлы потребуют ещё 6 ГБ. Если всё лежит на одном диске, одновременно нужно 20 + 20 + 4 + 6 = 50 ГБ, ещё без дополнительного запаса. На диске 60 ГБ после размещения старой базы свободно 40 ГБ. Для новой базы, дампа и временных файлов нужно 20 + 4 + 6 = 30 ГБ: операция помещается, после неё останется 40 - 30 = 10 ГБ.

На диске 40 ГБ было бы свободно только 20 ГБ. Повседневная работа могла идти нормально, но восстановление потребовало бы ещё 30 ГБ и не поместилось бы. Можно подготовить отдельный том для новой базы, а не удалять старую в спешке. Числа в этом примере иллюстрируют расчёт, а размер WAL и временных файлов для своего проекта измеряют в дрилле.

Время тоже зависит от объёма. Если при восстановлении переносится 20 000 МБ со средней скоростью 100 МБ/с, только перенос занимает 20 000 / 100 = 200 секунд, то есть 3 минуты 20 секунд. Создание индексов, запуск и проверка приложения добавят время. Это оценка для выбранных единиц и измеренной скорости, а не обещание RTO.

Прикинь сам: старая база 30 ГБ, новая тоже 30 ГБ, временные файлы 8 ГБ, свободно 25 ГБ. Хватит ли места?

Нет: нужно 30 + 8 = 38 ГБ, не хватает 13 ГБ. Сначала готовят отдельный том, старую базу не удаляют.

Осторожно: Свободное место на рабочем диске и запас для аварийного восстановления это разные потребности. Прогноз ежедневного роста должен оставлять время подготовить место и для нового экземпляра базы.

Главное: для восстановления в новое место нужно место под новую базу, дамп и временные файлы, пока старая база ещё жива.

Место есть. Теперь письменный порядок действий на случай аварии.

План восстановления: в каком порядке и почему в новое место

В момент аварии люди нервничают и делают лишнее. План (в курсе это docs/dr.md) заранее отвечает: кто решает, что делать первым, куда восстанавливать. Он по смыслу тот же runbook из урока 10.2, только для катастрофы с данными.

Порядок по шагам и причины.

  1. Зафиксировать момент до аварии (по логам и алертам). От него зависит, какой бэкап или какую секунду PITR брать.
  2. Остановить запись (например, kubectl scale rollout/notes --replicas=0, для Deployment deploy/notes; под Flux сначала flux suspend helmrelease notes -n notes, иначе helm-controller вернёт реплики). Пока приложение пишет в испорченную базу, оно ухудшает положение и создаёт записи, которые потом придётся разбирать.
  3. Восстановить в новое место, а не поверх старого. Старая база, даже испорченная, это улика: по ней разберутся в причине, из неё можно вытащить то, что успело записаться. Если восстановить поверх и ошибиться, назад пути нет. Аналогия: перед ремонтом ты фотографируешь сломанное.
  4. Сверить число строк и схему (тот же дрилл).
  5. Переключить Service на новую базу и вернуть реплики приложения.
  6. Разобрать причину: постмортем, обновление плана.

RPO и RTO в документе не придумываются инженером: их согласует владелец сервиса с бизнесом, а инженер приносит измеренные значения дрилла и цену. Поэтому в dr.md рядом с целью стоит «обоснование».

Прикинь сам: можно ли после потери данных сразу удалить испорченную базу и восстановить на её место?

Нельзя: испорченная база нужна для расследования, и если восстановление не удастся, вернуться будет не к чему.

Осторожно: DR-план это документ, который лежит и ждёт. Он устаревает первым (сменились имена, ключи, версии), поэтому его проверяют дриллом и обновляют по его итогам.

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

Вернёмся к мощности: что именно упирается в потолок при нагрузочном тесте.

Что ограничивает потолок: клиент, сеть или приложение

Кассиры скучают, а очередь стоит: узкое место может быть не там, где его ищут.

Найденный на тесте предел это предел всей цепочки, а не только приложения: клиент, который создаёт нагрузку, сеть, шлюз (Envoy Gateway из урока 5.4), поды приложения, база. Узкое место (bottleneck) это звено, которое ограничивает работу всей цепочки. Как единственная открытая касса в большом магазине, оно создаёт очередь, даже если остальные отделы свободны; его поиск помогает не покупать мощность там, где её и так хватает. Если rps перестал расти, смотришь загрузку и ожидание каждого звена; ограничения общей базы продолжим разбирать в уроке 10.4.

CPU (Central Processing Unit) это процессор, который выполняет инструкции программы, знакомый по уроку 1.5. В схеме ниже процент CPU означает его загрузку.

flowchart TD
    A["Клиент<br>твой ноутбук"] --> B["Шлюз<br>Envoy Gateway"]
    B --> C["Поды notes"]
    C --> D["PostgreSQL"]

Предел цепочки задаёт самое загруженное звено: смотри CPU, диск и ожидание в каждом из четырёх.

Высокая загрузка это подсказка, но узкое место не обязано показывать 100% CPU. Запросы могут ждать диск, свободное соединение с базой или снятие блокировки, запрета одновременно менять одни и те же данные из урока 4.4. Если ограничителем оказался клиент, тест ещё не достиг предела приложения. Тест по одному адресу тоже не повторяет реальную смесь запросов: найденный предел может оказаться выше или ниже рабочего. Запас учитывает неопределённость, но не заменяет проверку нужных адресов.

Прикинь сам: rps перестал расти, CPU подов 20%, CPU твоего ноутбука 100%. Где узкое место?

В клиенте: ноутбук не успевает создавать нагрузку. Нужно запустить тест мощнее, иначе измерен предел клиента.

Осторожно: предел найден один раз, и на этом всё. Нет: предел меняется с новой версией приложения, размером базы и настройками, поэтому тест повторяют перед крупными релизами и записывают результат в docs/capacity.md.

Главное: найденный предел это предел всей цепочки от клиента до базы, и его повторяют перед крупными релизами.

Остался вопрос, что считать пределом: все ответы или только полезные.

Рабочий предел: считать нужно полезные ответы

Максимальное число ответов в секунду ещё не говорит, что сервис пригоден для работы. Он может очень быстро возвращать ошибки. Рабочий предел (usable capacity) это нагрузка, при которой выполняются выбранные требования к задержке и доле ошибок. Как касса, успевающая правильно оформить покупки до закрытия магазина, система должна делать полезную работу вовремя; иначе высокий счётчик обслуженных запросов вводит в заблуждение.

До опыта выбирают адреса, длительность ступени, допустимую задержку и долю ошибок. На каждой ступени записывают параллелизм, rps, p95 и ошибки. Проверяют все условия. После изменения версии или состава запросов измеряют заново. Граница аналогии с кассой: адреса требуют разной работы, проверка здоровья /healthz не заменяет чтение заметок /notes.

Для учебной оценки выбрали p95 не больше 200 мс и долю ошибок меньше 1%. Условия демонстрационные: для рабочего проекта их берут из согласованных требований. Получены такие результаты:

Одновременных запросов Ответов в секунду p95 Доля ошибок Выполняются условия?
2 40 90 мс 0% да
7 65 170 мс 0% да
64 68 1500 мс 4% нет

Как читать таблицу: параллелизм вырос с 7 до 64, более чем в девять раз, но ответов стало больше лишь на 68 - 65 = 3 в секунду. p95 вырос с 170 до 1500 мс, ошибки превысили условие. Числа согласованы с законом Литтла: средняя задержка равна параллелизму, делённому на rps (7 / 65 ≈ 108 мс, что меньше p95). Самая высокая прошедшая ступень это 65 rps. Точная граница требует промежуточных измерений; 68 rps нельзя считать рабочим пределом.

Для начальной рабочей доли 60% ориентир пика равен 65 * 0.60 = 39 rps, запас равен 65 - 39 = 26 rps. При пике 35 rps доля составит 35 / 65 * 100 ≈ 54%. Отдельно проверяют отказ реплики. Умножение числа реплик на прежний предел остаётся оценкой до нового опыта.

Прикинь сам: тест показал 120 rps с 10% ошибок и 90 rps без ошибок. Какое число брать для планирования при условии «ошибок меньше 1%»?

90 rps: только он выполняет условия. Из него вычитают ещё запас и проверяют отказ реплики.

Проверь понимание: тест показал 120 rps с 10% ошибок и 90 rps без ошибок, оба результата уложились в ограничение p95. Какой из них использовать для планирования, если ошибки должны быть меньше 1%?

Ответ

Из этих двух результатов подходит 90 rps: второй выполняет оба условия. При 120 rps доля ошибок слишком велика. Для рабочей нагрузки нужен ещё запас ниже 90 rps и проверка отказа, если обещана работа при потере реплики.

Осторожно: Увеличить параллелизм клиента означает добавить ожидающих запросов, а не добавить исполнителей в приложении. В реальной работе новые пользователи также могут приходить независимо от завершения старых запросов. Поэтому короткий тест с фиксированным числом одновременных запросов даёт ориентир для выбранного опыта, а не гарантию поведения при любом всплеске.

Главное: рабочий предел это нагрузка, при которой выполняются условия по p95 и доле ошибок, а запас считают от неё.

Теории достаточно. В практике ты сделаешь бэкап, восстановишь его, найдёшь предел и оформишь docs/dr.md.

Практика

Задание 1. Логический бэкап и проверка целостности

Цель: сделать pg_dump из кластера CloudNativePG в формате custom и убедиться, что файл читаем.

Предскажи: сколько таблиц выведет pg_restore --list для базы notes и почему дамп надо снимать в формате custom, а не plain? Ответ в <details>.

Ответ

Одна таблица notes плюс её последовательность notes_id_seq. Формат custom сжат, содержит оглавление, проверяется командой pg_restore --list и позволяет восстанавливать выборочно и параллельно. Plain это текстовый SQL без оглавления.

Шаги:

  1. Убедись, что кластер работает и в нём есть данные. Разбор: kubectl -n notes exec pod -c postgres -- команда выполняет команду внутри контейнера postgres пода (-- отделяет флаги kubectl от самой команды). psql -U postgres -d notes -Atc "..." запускает SQL: -U пользователь, -d база, -A вывод без выравнивания, -t без заголовков, -c выполнить запрос. SELECT count(*) FROM notes считает строки в таблице.
# Под primary называется notes-db-1 (в режиме 8 ГБ он один)
kubectl -n notes get pod notes-db-1
kubectl -n notes exec notes-db-1 -c postgres -- psql -U postgres -d notes -Atc "SELECT count(*) FROM notes"
  1. Сними дамп. Разбор: pg_dump --format=custom выгружает базу в сжатом формате с оглавлением, а > файл направляет вывод команды в файл на твоём компьютере (перенаправление, урок 1.2). \ в конце строки переносит команду. Дамп снимается внутри пода, потому что там уже есть pg_dump нужной версии и доступ к базе по локальному сокету, а с ноутбука пришлось бы ставить клиент и открывать порт. Локальная папка бэкапов внутри репозитория не создаётся, работай в домашнем каталоге:
mkdir -p ~/notes-backups
# Дамп идёт по локальному сокету от имени postgres, пароль не нужен
kubectl -n notes exec notes-db-1 -c postgres -- \
  pg_dump -U postgres -d notes --format=custom > ~/notes-backups/notes-manual.dump
ls -l ~/notes-backups/notes-manual.dump
  1. Проверь, что архив читается. Разбор: pg_restore --list печатает оглавление архива, не восстанавливая его; -i в kubectl exec пробрасывает вход, а < файл подаёт ему твой дамп; | head -20 берёт первые 20 строк.
kubectl -n notes exec -i notes-db-1 -c postgres -- pg_restore --list < ~/notes-backups/notes-manual.dump | head -20

Что должно получиться:

;
; Archive created at 2026-09-29 10:15:02 UTC
;     dbname: notes
;     TOC Entries: 11
;     Compression: gzip
;     Dump Version: 1.16-0
;     Format: CUSTOM
;     Integer: 4 bytes
;     Offset: 8 bytes
;     Dumped from database version: 18.6
;     Dumped by pg_dump version: 18.6
;
;
; Selected TOC Entries:
;
220; 1259 16387 TABLE public notes notes
219; 1259 16386 SEQUENCE public notes_id_seq notes
...

Как читать вывод: строки с ; это шапка архива: время создания, база, Format: CUSTOM (то, что мы просили) и версии PostgreSQL. TOC Entries это число объектов в оглавлении (у тебя оно может отличаться). Ниже список объектов: TABLE public notes это твоя таблица, SEQUENCE notes_id_seq счётчик для номеров заметок. Первая команда выше печатает число заметок, оно совпадает с тем, сколько заметок создано в прошлых уроках.

Объясни себе:

  • Почему дамп снимается через exec в поде, а не с ноутбука?
  • Что покажет pg_restore --list для обрезанного файла?
  • Чем этот бэкап хуже PITR по RPO?

Типичные ошибки:

  • Error from server (NotFound): pods "notes-db-1" not found: у тебя другое имя primary. Найди его: kubectl -n notes get pod -l cnpg.io/instanceRole=primary.
  • pg_dump: error: connection to server on socket "/controller/run/.s.PGSQL.5432" failed: FATAL: role "root" does not exist: psql и pg_dump берут имя пользователя из ОС, и если процесс запущен не от postgres, а, например, от root, подключение идёт под этим именем: укажи -U postgres.
  • Файл notes-manual.dump весит 0 байт: команда упала до вывода, смотри stderr. Всегда проверяй ls -l и --list.

Задание 2. Restore drill в чистом контейнере

Цель: написать scripts/restore-drill.sh, который поднимает чистую БД, восстанавливает бэкап, сверяет число строк и печатает время (это и есть измеренное RTO).

Предскажи: что произойдёт, если скрипт восстановит дамп, но не сверит число строк с источником? Ответ в <details>.

Ответ

Скрипт покажет «успех» и на пустой или неполной БД: pg_restore может завершиться без ошибки, восстановив только часть объектов. Без сверки с эталоном дрилл проверяет лишь то, что команда не упала.

Шаги:

  1. Создай файл ~/notes/scripts/restore-drill.sh (каталог scripts создай, если его нет: mkdir -p ~/notes/scripts). Читай скрипт по частям. set -euo pipefail останавливает скрипт при любой ошибке (урок 1.7). ${1:?текст} берёт первый аргумент и печатает текст с остановкой, если его нет. $$ это номер процесса скрипта, он делает имя контейнера уникальным. trap cleanup EXIT вызывает функцию cleanup при любом завершении скрипта, чтобы контейнер не оставался. Цикл for _ in $(seq 1 30) ждёт готовности базы по TCP (-h 127.0.0.1) не больше 30 секунд. --no-owner пропускает установку владельцев, --exit-on-error останавливает восстановление при первой ошибке. $(( ... )) считает арифметику, date +%s даёт секунды с 1970 года, поэтому разность это длительность.
#!/usr/bin/env bash
# Restore drill: восстановить бэкап в чистую БД, сверить данные, замерить время (RTO)
# Использование: scripts/restore-drill.sh <файл-дампа> <ожидаемое-число-строк>
set -euo pipefail

DUMP="${1:?укажи файл дампа}"
EXPECTED="${2:?укажи ожидаемое число строк в таблице notes}"
NAME="notes-drill-$$"
START=$(date +%s)

# Контейнер удаляется при любом выходе: успешном и аварийном
cleanup() { docker rm -f "$NAME" >/dev/null 2>&1 || true; }
trap cleanup EXIT

# 1. Чистый PostgreSQL той же мажорной версии, что и в кластере
docker run -d --name "$NAME" -e POSTGRES_PASSWORD=CHANGE_ME postgres:18 >/dev/null

# 2. Ждём готовности по TCP, но не бесконечно: при первом старте образ поднимает
# временный сервер только на сокете, проверка по сокету прошла бы слишком рано
for _ in $(seq 1 30); do
  docker exec "$NAME" pg_isready -h 127.0.0.1 -U postgres >/dev/null 2>&1 && break
  sleep 1
done
docker exec "$NAME" pg_isready -h 127.0.0.1 -U postgres >/dev/null

# 3. Восстанавливаем: роль notes и база notes создаются заранее
docker exec "$NAME" psql -U postgres -c "CREATE ROLE notes LOGIN" >/dev/null
docker exec "$NAME" createdb -U postgres -O notes notes
docker exec -i "$NAME" pg_restore -U postgres -d notes --no-owner --exit-on-error < "$DUMP"

# 4. Сверка: число строк должно совпасть с источником
ACTUAL=$(docker exec "$NAME" psql -U postgres -d notes -Atc "SELECT count(*) FROM notes")
if [ "$ACTUAL" != "$EXPECTED" ]; then
  echo "DRILL FAILED: ожидали $EXPECTED строк, получили $ACTUAL" >&2
  exit 1
fi

echo "DRILL OK: строк $ACTUAL, время восстановления $(( $(date +%s) - START )) с"
  1. Сделай исполняемым и запусти с числом строк из задания 1 (пусть их 7). chmod +x даёт право на запуск, shellcheck проверяет скрипт на типичные ошибки (урок 1.7). Нужен Docker на твоём компьютере (урок 4.1) и образ postgres:18, который скачается при первом запуске:
chmod +x ~/notes/scripts/restore-drill.sh
shellcheck ~/notes/scripts/restore-drill.sh
~/notes/scripts/restore-drill.sh ~/notes-backups/notes-manual.dump 7
  1. Проверь, что дрилл умеет падать: передай заведомо неверное число.
~/notes/scripts/restore-drill.sh ~/notes-backups/notes-manual.dump 999; echo "код выхода: $?"

Что должно получиться:

DRILL OK: строк 7, время восстановления 2 с
DRILL FAILED: ожидали 999 строк, получили 7
код выхода: 1

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

Как читать вывод: DRILL OK значит «дамп восстановился и число строк совпало», в конце время восстановления: это измеренная часть RTO. DRILL FAILED с кодом выхода 1 значит, что скрипт умеет падать: это важно, иначе дрилл в расписании никогда не сообщил бы о проблеме. Код выхода 0 означает успех, любой другой ошибку.

Объясни себе:

  • Зачем --exit-on-error и trap cleanup EXIT?
  • Чем 2 секунды отличаются от реального RTO?
  • Почему образ закреплён как postgres:18, а не «любой свежий»?

Типичные ошибки:

  • pg_restore: error: could not execute query: ERROR: role "notes" does not exist: роль не создана до восстановления. Роль нужна из-за прав (GRANT/ACL) в дампе: даже с --no-owner права на объекты ссылаются на роли, а роли живут вне базы и в pg_dump одной базы не попадают.
  • pg_restore: error: unsupported version (1.16) in file header: клиент старее, чем версия, которой сделан дамп. Используй образ той же мажорной версии, что и сервер.
  • docker: Error response from daemon: Conflict. The container name "/notes-drill-123" is already in use: остался контейнер после прерванного дрилла. Удали: docker rm -f notes-drill-123.

Задание 3. Найти предел нагрузки

Цель: найти число одновременных запросов, при котором p95 задержки /readyz превышает 300 мс, и сверить результат с законом Литтла.

Предскажи: при росте параллелизма с 1 до 64 растёт ли p95 линейно? Ответ в <details>.

Ответ

Нет. Пока есть свободные воркеры, p95 почти не растёт (плато). После насыщения запросы встают в очередь, и задержка резко растёт: это «колено» графика. Предел это точка колена, а не средняя картина по всем уровням.

Шаги:

  1. Убедись, что сервис доступен (notes.lab в /etc/hosts из урока 5.4):
curl -ks -o /dev/null -w "%{http_code}\n" https://notes.lab/readyz
  1. Запусти ступенчатую нагрузку. Разбор скрипта: ThreadPoolExecutor(workers) запускает столько потоков (параллельных исполнителей), сколько задано параллелизмом; функция one делает один запрос и возвращает его время в миллисекундах или None при ошибке; sorted(...) выстраивает времена по порядку, и элемент на позиции 95% списка это p95; rps это число успешных запросов, делённое на длительность ступени. ctx.verify_mode = ssl.CERT_NONE отключает проверку сертификата, потому что он самоподписанный. Скрипт одноразовый, на стандартной библиотеке Python, в репозиторий не кладём:
python3 - <<'EOF'
import ssl, time, statistics, urllib.request
from concurrent.futures import ThreadPoolExecutor

URL = "https://notes.lab/readyz"
ctx = ssl.create_default_context()
ctx.check_hostname = False            # учебный самоподписанный сертификат
ctx.verify_mode = ssl.CERT_NONE

def one(_):
    t = time.perf_counter()
    try:
        urllib.request.urlopen(URL, context=ctx, timeout=5).read()
    except Exception:
        return None
    return (time.perf_counter() - t) * 1000

for workers in (1, 4, 16, 64):
    n = workers * 40
    t0 = time.perf_counter()
    with ThreadPoolExecutor(workers) as pool:
        res = list(pool.map(one, range(n)))
    dur = time.perf_counter() - t0
    ok = sorted(r for r in res if r is not None)
    p95 = ok[int(len(ok) * 0.95) - 1]
    print(f"параллелизм {workers:>2}: {len(ok)/dur:6.0f} rps, p95 {p95:6.1f} мс, ошибок {n-len(ok)}")
EOF
  1. Прикинь по Литтлу: при p95 около W секунд и параллелизме L получаешь λ = L / W. Сравни с измеренным rps.

Что должно получиться: пример вывода (числа зависят от твоей машины, важна форма):

параллелизм  1:    210 rps, p95    6.2 мс, ошибок 0
параллелизм  4:    640 rps, p95    9.8 мс, ошибок 0
параллелизм 16:    780 rps, p95   38.5 мс, ошибок 0
параллелизм 64:    790 rps, p95  151.0 мс, ошибок 3

Пропускная способность вышла на потолок около 780-790 rps, дальше растёт только задержка: это и есть предел.

Как читать вывод: каждая строка это ступень. Столбец rps пропускная способность, p95 задержка, ошибок число неудавшихся запросов. Пока параллелизм растёт с 1 до 16, rps растёт почти пропорционально, а p95 держится низким (плато). Между 16 и 64 rps почти не растёт, зато p95 вырастает в четыре раза, а ошибки появляются: это «колено». Проверка по Литтлу: при параллелизме 64 и p95 0.151 с получаем 64 / 0.151 = 424 rps, что меньше измеренных 790, потому что p95 это верхний край, а среднее время короче. Оценка грубая, важна форма.

Объясни себе:

  • Где на твоём выводе «колено» и при каком параллелизме?
  • Что ограничивает потолок: сам клиент, ingress или приложение? Как отличить (подсказка: CPU пода и клиента)?
  • Почему пик надо держать заметно ниже найденного предела?

Типичные ошибки:

  • ssl.SSLCertVerificationError: certificate verify failed: в контексте забыли verify_mode = ssl.CERT_NONE при самоподписанном сертификате.
  • urllib.error.URLError: <urlopen error [Errno -2] Name or service not known>: нет строки 127.0.0.1 notes.lab в /etc/hosts.
  • Все запросы дают ошибок N, а curl работает: упёрся в лимит открытых файлов клиента, проверь ulimit -n.

Задание 4. Прогноз диска через predict_linear

Цель: посчитать, когда закончится том с данными PostgreSQL, и оформить это как правило.

Предскажи: если диск заполнялся 10 ГБ за неделю, а свободно 5 ГБ, через сколько дней он закончится? Ответ в <details>.

Ответ

Скорость примерно 1.4 ГБ в сутки, запас 5 ГБ, значит около 3.5 суток. Именно такой расчёт делает predict_linear, только по реальному окну наблюдений, а не по круглым числам.

Шаги:

  1. Открой Prometheus из урока 8.9. Имя сервиса зависит от имени релиза, найди его:
kubectl -n monitoring get svc | grep -i 'prometheus ' 
  1. Пробрось порт (подставь найденное имя):
kubectl -n monitoring port-forward svc/<имя-сервиса-prometheus> 9090:9090
  1. В браузере http://localhost:9090/graph выполни запрос: сколько байт останется на томах в notes через 4 суток. Разбор: kubelet_volume_stats_available_bytes это свободные байты тома; {namespace="notes"} оставляет том нашего namespace; [6h] берёт данные за 6 часов; 4 * 24 * 3600 это 4 суток в секундах.
predict_linear(kubelet_volume_stats_available_bytes{namespace="notes"}[6h], 4 * 24 * 3600)
  1. Отрицательное значение означает «том закончится раньше». Правило-алерт, которое стоит завести (по образцу PrometheusRule из 8.9):
predict_linear(kubelet_volume_stats_available_bytes{namespace="notes"}[6h], 24 * 3600) < 0

Что должно получиться: по каждому PVC (data-notes-db-1 и другим) одна строка с числом байт.

{namespace="notes", persistentvolumeclaim="data-notes-db-1"}  6.2e+08

Как читать вывод: каждая строка это один том (PVC), число это прогноз свободных байт через 4 суток (6.2e+08 это 620 МБ). Положительное число: диск переживёт это время; отрицательное: закончится раньше. Второе выражение из шага 4 использует < 0, поэтому оно возвращает только те тома, которые закончатся в течение суток, и именно оно годится для алерта. Положительное число: диск переживёт сутки. Если у тебя мало истории, окно [6h] даст шумный результат: это нормально на свежем кластере.

Объясни себе:

  • Почему окно 6 часов, а не 5 минут, и когда линейный прогноз врёт (подсказка: ночные бэкапы, VACUUM)?
  • Чем алерт «через сутки закончится» лучше «занято 90%»?
  • Что делать, когда алерт сработал: расширить PVC, чистить или искать причину роста?

Типичные ошибки:

  • Empty query result: метрики kubelet_volume_stats_* нет для томов, которые никто не монтирует, или в kind её не отдаёт выбранный провайдер. Проверь kubelet_volume_stats_capacity_bytes без фильтра.
  • bad_data: 1:60: parse error: ranges only allowed for vector selectors: окно [6h] поставлено после функции, а не после селектора. Диапазон стоит сразу после метрики.

Задание 5. Шаг проекта: docs/dr.md и docs/capacity.md

Цель: зафиксировать RPO/RTO, порядок восстановления и результаты замеров в репозитории ~/notes.

Предскажи: какое RTO ты запишешь: время дрилла или больше? Ответ в <details>.

Ответ

Больше. К нескольким секундам восстановления добавь обнаружение аварии, сбор дежурного, поиск и выдачу дампа, проверку и переключение сервиса. Записывай измеренное время дрилла отдельной строкой, а RTO целевое и достижимое: с запасом.

Шаги:

  1. Создай ~/notes/docs/dr.md:
# План восстановления «Заметок» (DR)

## Цели

| Показатель | Значение | Обоснование |
|---|---|---|
| RPO | 1 час | логический дамп раз в час, PITR через ScheduledBackup в MinIO (урок 9.5) |
| RTO | 30 минут | измеренный дрилл 2 с + обнаружение, дежурный, проверка, запас |

## Где лежат бэкапы

- Физические бэкапы и WAL: MinIO (ns `minio`), `ScheduledBackup` кластера `notes-db`.
- Логический дамп: `pg_dump --format=custom`, хранится вне кластера, зашифрован, ключ отдельно.

## Порядок восстановления

1. Определить момент до аварии (по логам и алертам) и зафиксировать в тикете.
2. Остановить запись: `flux suspend helmrelease notes -n notes`, затем `kubectl -n notes scale rollout/notes --replicas=0` (без Flux и Rollout: `scale deploy/notes`).
3. Восстановить БД (PITR или последний дамп) в новый кластер, не поверх старого.
4. Сверить число строк и схему: `scripts/restore-drill.sh <дамп> <строк>`.
5. Переключить Service на новую БД, вернуть реплики приложения.
6. Написать постмортем (урок 10.2), обновить этот документ.

## Дрилл

| Дата | Кто | Дамп | Строк | Время | Итог |
|---|---|---|---|---|---|
| 2026-09-29 | ученик | notes-manual.dump | 7 | 2 с | OK |

Повторять раз в месяц и после любого изменения БД или бэкапов.
  1. Создай ~/notes/docs/capacity.md, подставив свои измерения из заданий 3 и 4:
# Ёмкость «Заметок»

## Предел нагрузки

Нагрузочный тест `/readyz`, ступени параллелизма 1, 4, 16, 64.

| Параллелизм | rps | p95, мс |
|---|---|---|
| 16 | 780 | 38 |
| 64 | 790 | 151 |

Предел: около 780 rps (на этой машине, kind, 3 реплики). Колено между 16 и 64.

## Запас

Рабочая цель: пик не выше 60% предела при отказе одной реплики (около 520 rps из трёх), то есть около 310 rps.

## Диск

Прогноз: `predict_linear(kubelet_volume_stats_available_bytes{namespace="notes"}[6h], 86400) < 0`.
Действие при срабатывании: расширить PVC, проверить размер WAL и retention бэкапов.
  1. Проверь, что все три файла на месте (ls печатает имена, а если файла нет, сообщает об ошибке):
cd ~/notes && ls docs/dr.md docs/capacity.md scripts/restore-drill.sh

Что должно получиться:

docs/capacity.md
docs/dr.md
scripts/restore-drill.sh

Объясни себе:

  • Почему восстанавливать надо в новый кластер, а не поверх старого?
  • Откуда взялись цифры RPO и RTO и кто их утверждает?
  • Что в этих документах устареет первым?

Типичные ошибки:

  • ls: cannot access 'docs/dr.md': No such file or directory: не создан каталог docs. Выполни mkdir -p ~/notes/docs.
  • bash: scripts/restore-drill.sh: Permission denied: нет права на исполнение, chmod +x.

Сломай и почини

Скачай сценарий и не читай скрипт: он подменяет состояние, ты диагностируешь. Скрипт работает от твоего пользователя, без sudo.

curl -fsSL -o /tmp/break-10.3.sh https://raw.githubusercontent.com/distinguished-sre/learning/main/devops/project/notes/break/10.3/break.sh
bash /tmp/break-10.3.sh 1

Симптом

Сценарий 1: ночной бэкап ~/notes-backups/notes-nightly.dump «зелёный», размер файла ненулевой, но scripts/restore-drill.sh ~/notes-backups/notes-nightly.dump 7 падает. Починка: bash /tmp/break-10.3.sh fix.

Сценарий 2 (запусти bash /tmp/break-10.3.sh 2 после починки первого): утром алерт «том с данными заполнен», а вчера всё было хорошо.

Гипотезы

Гипотеза (hypothesis) это возможное объяснение, которое ещё нужно проверить. Как предположение «лампа не горит из-за перегоревшей лампочки», оно подсказывает следующий опыт, но не становится фактом до проверки; иначе легко чинить исправную деталь. Запиши минимум три гипотезы на каждый сценарий до проверок. Например для первого: дамп обрезан, дамп сделан другой версией клиента, не хватает роли. Порядок «наблюдение, гипотеза, проверка» подробно отработаем в уроке 10.7.

Проверки

Сценарий 1:

ls -l ~/notes-backups/
BACKUP=~/notes-backups/notes.dump   # подставь имя файла из вывода ls
pg_restore --list "$BACKUP" | tail -3

Сценарий 2:

kubectl -n notes exec notes-db-1 -c postgres -- df -h /var/lib/postgresql/data
kubectl -n notes exec notes-db-1 -c postgres -- sh -c 'du -sh /var/lib/postgresql/data/*'

Исправление

Разбор сценариев

Сценарий 1. pg_restore --list на плохом файле выдаёт pg_restore: error: could not read from input file: end of file: дамп оборвался (например, диск переполнился или процесс убит), а скрипт бэкапа не проверил код выхода и не сделал pipefail. Починка: set -euo pipefail, снимать дамп во временный файл, проверять pg_restore --list, только потом переименовывать в итоговое имя. Профилактика: restore drill по расписанию с алертом на падение.

Сценарий 2. df показывает почти полный том, du -sh /var/lib/postgresql/data/* показывает виновника: в учебном сценарии это каталог old-wal-archive. В жизни так же накапливаются WAL (архив не уходит в MinIO) или бэкапы без ротации. Тренд был виден в predict_linear за 6-12 часов, но алерта на него не было, только на «занято 90%». Починка: bash /tmp/break-10.3.sh fix (в жизни: освободить место, восстановить архивирование или ретеншен, расширить PVC), завести алерт на прогноз (задание 4). Итог для постмортема: причина в отсутствии предупреждающего сигнала, а не только в переполнении.

ИИ в помощь

Нейросеть хорошо проверяет документы по чек-листу и считает по шагам, но не видит твой стенд: она не знает, что лежит в бакете и сколько реально свободно места. Общие правила: ИИ-помощник.

Задача: найти дыры в плане восстановления.

Вот мой docs/dr.md для учебного проекта «Заметки»: <вставь текст без секретов>.
Проверь по пунктам: заданы ли RPO и RTO с обоснованием; есть ли порядок шагов с остановкой записи
и восстановлением в новое место; где лежит ключ шифрования; когда проходит дрилл.
Выведи таблицу «пункт, есть ли, чего не хватает». Ничего не придумывай за меня.

Проверь ответ: сверь каждый «чего не хватает» со своим файлом. Типичная ошибка: нейросеть добавляет в план команды и имена ресурсов, которых у тебя нет.

Задача: перепроверить расчёт RPO и запаса диска.

Бэкап раз в <N> часов, последняя пригодная копия соответствует <время>, авария в <время>.
Посчитай возможную потерю данных. Диск занят на <X> ГБ, за неделю вырос на <Y> ГБ, всего <Z> ГБ.
Через сколько суток закончится место? Покажи вычисления по шагам.

Проверь ответ: пересчитай сам: потеря считается от времени данных последней пригодной копии, а не от времени запуска задачи. Типичная ошибка: нейросеть берёт время старта задачи, а не момент, к которому относятся данные.

Задача: разобрать результаты нагрузочного теста.

Условия: p95 не больше <мс>, ошибок меньше <процент>. Результаты ступеней: <таблица параллелизм, rps, p95, ошибки>.
Какая ступень последняя проходит условия? Где «колено»? Какой запас взять от пика?

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

Словарик урока

Термин Простыми словами
Бэкап (backup) копия данных, из которой можно всё вернуть
RPO сколько данных готовы потерять, измеряется временем
RTO сколько готовы простоять, включает обнаружение и людей
Реплика копия базы на другом сервере, повторяет изменения в реальном времени
Failover автоматическое переключение на запасной сервер
HA отказоустойчивость: защита от смерти узла
DR восстановление после катастрофы: защита данных и площадки
Логический бэкап (pg_dump) выгрузка данных как набора команд или архива
Физический бэкап копия файлов базы
WAL журнал предзаписи: каждое изменение сначала пишется в него
PITR восстановление на момент времени: копия плюс журнал WAL
Правило 3-2-1 три копии, два вида носителей, одна вне площадки
Retention срок хранения копий
Бакет (bucket) «папка верхнего уровня» в объектном хранилище
Restore drill учебное восстановление с проверкой и замером времени
rps запросов в секунду
Задержка (latency), p95 время запроса; p95: 95% запросов быстрее этого значения
Закон Литтла L = λ * W: связь одновременных запросов, пропускной способности и времени запроса
Headroom запас между пиком и пределом
predict_linear функция Prometheus, продлевающая тренд метрики вперёд по прямой
Свежесть бэкапа (backup freshness) насколько от текущего времени отстают данные последней пригодной копии
Синхронная репликация (synchronous replication) подтверждение записи клиенту после подтверждения от запасного экземпляра базы
Схема базы (database schema) устройство таблиц, столбцов и связей
Контрольная сумма (checksum) вычисленный отпечаток содержимого для проверки, что оно не изменилось
Эталон (reference) заранее сохранённый ожидаемый результат для сравнения
Мажорная версия (major version) основной номер версии, от которого зависит совместимость файлов базы
Нагрузочный тест (load test) опыт с множеством запросов для измерения скорости, задержки и ошибок
Параллелизм (concurrency) сколько запросов одновременно выполняются или ждут
Воркер (worker) исполнитель, который обрабатывает запрос
Узкое место (bottleneck) звено, ограничивающее работу всей цепочки
Рабочий предел (usable capacity) нагрузка, при которой соблюдаются требования к задержке и ошибкам
Ёмкость (capacity) сколько данных или работы может вместить система
Ротация (rotation) удаление старых копий по принятому правилу хранения
Гипотеза (hypothesis) возможное объяснение проблемы, которое ещё нужно проверить
CPU процессор, выполняющий инструкции программы
Роль (role) пользователь базы или группа с назначенными правами
SLO согласованная цель качества сервиса
Доступность (availability) доля времени или запросов, когда сервис выполняет обещанную работу

Вопросы с собеседований

Раздел для повторения: ответь вслух, потом открой ответ. Короткие вопросы с пометкой [на скорость] тренируй на время: ответ за 30 секунд.

1. [junior] [часто] Чем RPO отличается от RTO и как их узнать для своего сервиса?

Ответ

RPO это сколько данных мы готовы потерять, RTO сколько можем простоять. RPO выводится из частоты бэкапов и наличия WAL. RTO я измеряю на restore drill и добавляю время на обнаружение и людей.

Что хотят услышать: цифры выбирает бизнес, инженер показывает стоимость; RTO измеряется, а не угадывается.

Красный флаг: «RTO это время работы pg_restore».

2. [middle] [часто] У вас бэкап делается каждую ночь. Как убедиться, что он рабочий?

Ответ

Регулярный автоматический restore drill: поднять чистую БД, восстановить последний бэкап, сверить число строк и схему, записать время и ронять задачу, если что-то не так. Плюс алерт «бэкап не делался N часов».

Что хотят услышать: восстановление, а не проверка размера файла; сверка данных; алерт на отсутствие успеха.

Красный флаг: «файл есть и весит нормально».

3. [junior] [часто] Что такое правило 3-2-1?

Ответ

Три копии данных, на двух типах носителей, одна вне площадки. Дополнение: бэкап зашифрован, ключ хранится отдельно, восстановление проверено.

Что хотят услышать: не только цифры, но и шифрование и проверка.

Красный флаг: «три копии на одном диске».

4. [junior] [на скорость] Прод удалил таблицу по ошибке. В кластере три реплики. Что делаешь?

Ответ

Реплики не спасут, удаление уже реплицировалось. Останавливаю запись, определяю момент до ошибки, восстанавливаю в новый экземпляр из бэкапа или PITR на секунду до удаления, сверяю и переключаю сервис.

Что хотят услышать: HA не равно бэкап; PITR; восстановление рядом, не поверх; фиксация времени.

Красный флаг: «переключимся на реплику».

5. [middle] Диск заполнен, а du показывает мало. Почему?

Ответ

Файл удалён, но процесс держит его открытым: место не освобождается до закрытия. Ищу lsof +L1. Также бывают исчерпанные inode (df -i), или du смотрит не тот mount, или место занято под точкой монтирования.

Что хотят услышать: lsof +L1, df -i, разные mount, перезапуск процесса.

Красный флаг: «удалю ещё что-нибудь».

6. [middle] Ночью закончился диск на БД, утром всё лежит. Как предотвращать?

Ответ

Алерт на прогноз, а не на процент: predict_linear на сутки вперёд. Ретеншен бэкапов и WAL, мониторинг роста, расширяемый PVC. В постмортеме пишу не «диск закончился», а «не было предупреждающего сигнала».

Что хотят услышать: прогноз, ретеншен, запас, расширение тома.

Красный флаг: «увеличим диск в два раза».

7. [middle] Как найти предел нагрузки сервиса?

Ответ

Ступенчатый нагрузочный тест: растить параллелизм, смотреть пропускную способность и p95. Предел там, где rps выходит на плато, а задержка растёт лавиной. Затем сверяюсь с законом Литтла и с CPU по слоям.

Что хотят услышать: ступени, колено, p95 а не среднее, узкое место (клиент, ingress, приложение, БД).

Красный флаг: «запущу тест и посмотрю на среднее время».

8. [middle] Трафик вырос втрое перед распродажей. Что делаешь?

Ответ

Смотрю текущий предел и headroom. Если пик втрое выше 60% предела, планирую: горизонтальное масштабирование (реплики, HPA), кэш, проверка пула соединений БД. До события гоняю нагрузочный тест и готовлю откат.

Что хотят услышать: headroom, узкое место (обычно БД), тест заранее, план отката.

Красный флаг: «добавим серверов».

9. [middle] pg_dump против физического бэкапа и PITR: что выберешь?

Ответ

Для небольшой базы и переносов между версиями: pg_dump. Для быстрого восстановления всего кластера: физический бэкап. Когда нужна потеря в минуты и откат на момент: физический бэкап плюс WAL (PITR). Часто комбинирую: PITR как основа, дамп как страховка.

Что хотят услышать: зависимость от размера, версии и RPO; комбинация.

Красный флаг: «всегда pg_dump, он проще».

10. [middle] Бэкап есть, pg_restore падает с role does not exist. Что произошло?

Ответ

Роли живут в кластере, а не в базе, и pg_dump одной базы их не выгружает. Перед восстановлением создаю роли (или использую pg_dumpall --globals-only). Это ещё одна причина, почему дрилл нужен: без него это узнают в аварию.

Что хотят услышать: глобальные объекты, --globals-only, дрилл ловит такое заранее.

Красный флаг: «уберу --exit-on-error и пойдёт».

11. [middle] Разница между HA и DR?

Ответ

HA переживает отказ узла за секунды: реплика, failover. DR переживает потерю данных или площадки за часы: бэкапы, вторая площадка. HA не защищает от логических ошибок и удаления.

Что хотят услышать: разные цели и времена; примеры отказов для каждого.

Красный флаг: «у нас реплика, значит бэкап не нужен».

12. [junior] [на скорость] Утром выяснилось, что ночью пропали данные. Твои первые шаги?

Ответ

Останавливаю дальнейший вред (запись и автоматические задачи), фиксирую время и наблюдаемые признаки проблемы, смотрю логи, журнал действий и алерты. Определяю момент потери, решаю: восстановление на момент до или ручное вытаскивание из бэкапа. Потом постмортем.

Что хотят услышать: сначала остановить ущерб, потом расследовать, восстановление в стороне.

Красный флаг: «сразу восстановлю поверх боевой БД».

13. [middle] Что такое PITR и что нужно, чтобы он работал в PostgreSQL?

Ответ

PITR (point-in-time recovery) - восстановление на конкретный момент, например за минуту до ошибочного DROP. Нужны два компонента: базовая физическая копия и непрерывный архив WAL после неё. При восстановлении поднимаю базовую копию и проигрываю WAL до нужного времени. RPO тогда определяется тем, как часто архивируется WAL. Восстановление обязательно тренирую, иначе первый раз оно запустится в инциденте.

Что хотят услышать: базовая копия плюс архив WAL, восстановление до заданного времени, связь с RPO, тренировка.

Красный флаг: говорит, что достаточно pg_dump раз в сутки.

14. [junior] Чем полный, инкрементальный и дифференциальный бэкапы отличаются?

Ответ

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

Что хотят услышать: что копируется в каждом, цепочка при восстановлении, компромисс размера и времени.

Красный флаг: выбирает только по размеру, не думая о восстановлении.

15. [middle] Как защитить бэкапы от шифровальщика или злоумышленника с админским доступом?

Ответ

Делаю копию, которую нельзя изменить или удалить: объектное хранилище с иммутабельностью (например, S3 Object Lock или аналог) на заданный срок. Отдельный аккаунт и отдельные учётные данные для бэкапов, не те, что у основной платформы. У пишущей стороны права только на запись. Часть копий держу в другом месте или офлайн. Восстановление из такой копии проверяю, а ключи шифрования храню отдельно.

Что хотят услышать: неизменяемое хранилище, раздельные доступы, минимум прав на запись, другая локация, проверка восстановления.

Красный флаг: бэкап лежит на том же диске или под теми же правами, что и основная база.

Проверено на версиях

  • PostgreSQL: 18.6 (образ postgres:18, Docker на macOS): pg_dump --format=custom, pg_restore --list (вывод в задании 1 и ошибка could not read from input file: end of file для обрезанного файла) и scripts/restore-drill.sh (DRILL OK за 2 с, DRILL FAILED, код 1) прогнаны; shellcheck без замечаний
  • CloudNativePG: v1.30.1 (кластер не запускался: команды с kubectl exec, predict_linear и нагрузочный тест приведены по документации, числа условные)
  • kube-prometheus-stack: chart 91.8.2
  • Python: скрипт нагрузки использует только стандартную библиотеку, не прогонялся; break.sh проверен shellcheck, на кластере не запускался
  • Ubuntu: 26.04 LTS и 24.04 LTS
  • ShellCheck и Docker Engine: версия не закреплена, проверь актуальную версию на странице проекта

Итог урока: ты умеешь

  • умею объяснить RPO и RTO и назвать, что входит в реальное RTO
  • умею снять pg_dump в формате custom и проверить его pg_restore --list
  • умею провести restore drill скриптом со сверкой числа строк и замером времени
  • умею отличить HA от DR и назвать, что защищает от DROP TABLE
  • умею найти предел нагрузки ступенчатым тестом и оценить его по закону Литтла
  • умею настроить прогноз диска через predict_linear вместо алерта на процент
  • умею вести docs/dr.md и docs/capacity.md как живые документы

Дальше: Урок 10.4: Кирпичики распределённых систем

Проверь себя

Короткий тест по уроку: 5 вопросов из банка в 30. Засчитывается только полностью правильный ответ, порог 60%. Каждая новая попытка даёт другие вопросы, пока банк не закончится. Ответы видны после проверки.

Тест работает с включённым JavaScript.

тема 10 урок 10.3 4 ч курс 0/0 ← → уроки