Остановка боевой базы данных на время резервного копирования редко проходит безболезненно: клиенты видят ошибки, очередь запросов растёт, а окно обслуживания приходится согласовывать заранее и держать максимально коротким. Снимки LVM (snapshot) дают способ обойти эту дилемму, создавая практически мгновенную точку отсчёта состояния диска, с которой можно спокойно снимать резервную копию, пока сама база данных продолжает принимать запросы и писать новые данные.
Как устроен механизм снимка на уровне блочного устройства
Снимок LVM работает по принципу copy-on-write, то есть копирования при записи. В момент создания снимка новое устройство физически не содержит копии данных, оно лишь ссылается на те же блоки, что и исходный логический том, и занимает почти нулевой объём. Как только приложение изменяет какой либо блок на исходном томе, менеджер логических томов сначала сохраняет оригинальное, ещё не изменённое содержимое этого блока в область снимка, и только затем позволяет записи пройти на исходный том. Именно поэтому размер тома снимка не должен совпадать с размером исходного тома целиком: снимок обязан вместить лишь те изменения, которые накопятся за время его жизни, пока идёт резервное копирование, а не полный объём данных.
Практическое следствие этой механики: снимок, оставленный активным надолго, начинает разрастаться пропорционально интенсивности записи на исходный том, и если область, выделенная под снимок, заполнится полностью, LVM переводит снимок в недоступное состояние и он перестаёт быть пригодным для восстановления. Отсюда правило: снимок создаётся непосредственно перед операцией резервного копирования, используется предельно короткое время и удаляется сразу после того, как копия данных снята, а не оставляется висеть в системе на длительный срок.
Почему простой снимок диска ещё не гарантирует целостность базы данных
Блочный снимок сам по себе фиксирует состояние диска в конкретный момент, но для базы данных это состояние по умолчанию является лишь crash-consistent, то есть эквивалентным внезапному отключению питания сервера, а не application-consistent, то есть состоянием, которое приложение считает корректно завершённым. Файловая система и движок хранения данных в момент снимка вполне могут иметь незавершённые операции записи, буферы, ещё не сброшенные на диск, и частично применённые транзакции. Восстановление из такого снимка для многих СУБД пройдёт успешно за счёт встроенных механизмов журналирования и восстановления после сбоя, но рассчитывать на это как на гарантию не стоит, а для части конфигураций разница между "восстановилось само" и "потребовался восстановительный запуск с потерей последних операций" может стоить нескольких минут актуальных транзакций.
Чтобы превратить снимок из просто согласованного по состоянию диска в действительно надёжный для восстановления, движок базы данных перед созданием снимка нужно на короткое время "заморозить": сбросить буферы на диск и приостановить запись, сохранив при этом состояние, из которого можно однозначно продолжить работу после восстановления.
Подготовка MySQL к согласованному снимку через блокировку на чтение
Для баз данных на MySQL классический способ подготовки к снимку это команда FLUSH TABLES WITH READ LOCK, выполненная в отдельном, специально открытом сеансе подключения к серверу. Команда закрывает все открытые таблицы, сбрасывает кеш таблиц и переводит сервер в состояние, где чтение продолжает работать в обычном режиме, а любая запись блокируется до снятия блокировки:
mysql -u backup_user -p
FLUSH TABLES WITH READ LOCK;
Важная особенность этой команды в том, что она ждёт завершения не только текущих операций записи, но и всех выполняющихся в данный момент запросов на чтение, включая долгие SELECT-запросы. Если на сервере в момент выполнения FLUSH TABLES WITH READ LOCK крутится тяжёлый аналитический запрос, команда блокировки будет ждать его завершения, и это ожидание способно растянуться на заметное время, поэтому перед плановым резервным копированием разумно проверить отсутствие длительных запросов заранее.
Сессия, в которой была выполнена блокировка, обязана оставаться открытой на всё время создания снимка: как только соединение закрывается, блокировка автоматически снимается, и целостность снимка перестаёт гарантироваться. Именно поэтому создание самого снимка LVM выполняется в параллельном терминале или отдельным процессом, не закрывая сессию с блокировкой:
lvcreate -L 1G -s -n mysql-backup /dev/vg0/mysql-data
После того как снимок успешно создан, блокировку немедленно снимают командой UNLOCK TABLES в той же сессии, где она была установлена, возвращая сервер к обычной работе с записью:
UNLOCK TABLES;
Весь период недоступности записи для клиентов сокращается до времени выполнения самой команды lvcreate, которая на современном оборудовании занимает доли секунды, а не до продолжительности полноценного резервного копирования, которое может растянуться на десятки минут для крупной базы. Это и есть главный практический выигрыш связки FLUSH TABLES WITH READ LOCK плюс снимок LVM по сравнению с обычным mysqldump, который блокирует таблицы на всё время выгрузки данных.
Почему LOCK INSTANCE FOR BACKUP не подходит для снимка LVM
В экосистеме MySQL 8.0 существует команда LOCK INSTANCE FOR BACKUP, которая на первый взгляд выглядит более щадящей альтернативой полной блокировке чтения и записи. Официальная документация описывает её именно так: она удерживает блокировку на уровне всего экземпляра, но при этом прямо разрешает выполнение операций изменения данных (DML), запрещая лишь операции, способные привести к рассинхронизации файлов, например создание, переименование или удаление файлов. Транзакции продолжают фиксироваться, грязные страницы буферного пула по прежнему пишутся на диск в обычном режиме, а значит состояние файлов данных в момент такого "замороженного" состояния всё равно продолжает изменяться.
Эта команда была спроектирована не для мгновенных блочных снимков, а для инструментов потокового копирования вроде Percona XtraBackup, которые параллельно копируют файлы данных и одновременно читают журнал повторного выполнения транзакций (redo log), чтобы затем математически докатить недостающие изменения поверх скопированных файлов и получить согласованное состояние без остановки записи. LVM-снимок работает принципиально иначе: он фиксирует мгновенный слепок состояния блочного устройства в один конкретный момент времени и не умеет ничего докатывать поверх уже сделанного среза. Если создать LVM-снимок, пока экземпляр находится под LOCK INSTANCE FOR BACKUP, а не под полной блокировкой чтения и записи, полученный снимок останется лишь crash-consistent, то есть ровно тем небезопасным состоянием, о котором шла речь в начале статьи, а зафиксировать точную позицию бинарного журнала в момент снимка окажется невозможно, поскольку запись в этот момент не останавливалась и позиция журнала оказывается "размазанной" по времени создания снимка.
Отсюда практический вывод: для получения согласованного на уровне приложения (application-consistent) LVM-снимка с точной фиксацией позиции бинарного журнала необходима именно полная блокировка FLUSH TABLES WITH READ LOCK, независимо от того, использует база данных InnoDB, MyISAM или их сочетание. LOCK INSTANCE FOR BACKUP имеет смысл применять только в связке с инструментами вроде XtraBackup, которые сами умеют читать и докатывать журнал транзакций, но не с механизмом моментальных блочных снимков файловой системы.
Практический порядок создания резервной копии через снимок LVM
Полная последовательность действий для снятия согласованной резервной копии работающей базы данных через снимок LVM выглядит так:
- Открыть отдельный сеанс подключения к серверу базы данных и выполнить в нём FLUSH TABLES WITH READ LOCK, дождавшись подтверждения выполнения команды, не закрывая при этом сам сеанс;
- В параллельном терминале записать текущую позицию бинарного журнала, если резервная копия впоследствии будет использоваться для настройки репликации или восстановления на конкретный момент времени. В MySQL версий до 8.2 для этого служит команда SHOW MASTER STATUS, а начиная с версии 8.2 та же информация выводится командой SHOW BINARY LOG STATUS, поскольку прежний синтаксис был объявлен устаревшим и полностью удалён в релизе 8.4;
- Создать снимок логического тома, где физически размещены файлы данных, командой lvcreate с ключом -s, указав разумный размер тома снимка исходя из ожидаемой интенсивности записи за время резервного копирования;
- Немедленно вернуться в первый сеанс и выполнить UNLOCK TABLES, снимая блокировку и возвращая сервер к полноценной работе с записью, поскольку снимок уже зафиксировал согласованное состояние диска;
- Смонтировать полученный снимок в отдельную точку монтирования только для чтения и скопировать оттуда файлы данных на внешнее хранилище любым удобным способом, будь то простое копирование, архивация или потоковая передача по сети;
- После завершения копирования размонтировать снимок и удалить его командой lvremove, освобождая занятое им пространство в группе томов и прекращая дальнейший рост области снимка.
Ценность такого подхода в том, что снимок смонтированный отдельно можно даже запустить как полноценный экземпляр сервера базы данных для проверки: выполнить проверку таблиц, восстановление после сбоя или любые другие диагностические операции прямо на копии, не рискуя повредить рабочие данные и не мешая основному серверу обслуживать реальных клиентов.
Определение размера тома под снимок
Ошибка в расчёте размера снимка это одна из самых частых практических проблем при использовании данного метода. Размер тома снимка не регламентирован жёстко, но обязан быть достаточным для сохранения всех изменений, которые произойдут на исходном томе за весь период существования снимка, от создания до удаления. Если резервное копирование крупной базы растягивается на час активной работы сервера с интенсивной записью, снимок должен вместить объём данных, который сервер способен изменить за этот час, а не какую то произвольно выбранную небольшую величину.
Заниженный размер снимка приводит к тому, что при интенсивной записи область под изменения заполняется до того, как резервное копирование завершится, и LVM автоматически переводит снимок в состояние invalid, после чего восстановление данных из такого снимка становится невозможным. Завышенный размер снимка, напротив, просто напрасно резервирует свободное пространство группы томов, которое временно недоступно для других задач, но не создаёт риска для целостности данных. Для тестовой среды и небольших баз данных типичным ориентиром служит размер снимка в диапазоне от нескольких процентов до трети от объёма исходного тома, а для боевых серверов с высокой нагрузкой на запись размер снимка стоит масштабировать пропорционально фактическому объёму трафика записи, замеренному заранее за аналогичный интервал времени.
Ограничения и подводные камни метода
Снимки LVM для операционной системы, а не только для отдельной базы данных, работают жёстче: снятие снимка равносильно резкому отключению питания для файловой системы в целом, и хотя большинство современных журналируемых файловых систем переживают такую ситуацию штатно, гарантии сохранности зависят от конкретной файловой системы и её настроек журналирования. Для файлов базы данных, лежащих на отдельном логическом томе, риск ниже, поскольку сама СУБД уже позаботилась о согласованном состоянии перед снимком через описанную выше процедуру блокировки.
Если данные распределены по нескольким логическим томам, например когда журналы транзакций и основной массив данных физически размещены на разных дисках через отдельные RAID-группы для повышения производительности, снимки этих томов будут сделаны в разные моменты времени последовательными вызовами lvcreate, и совместная согласованность между ними уже не гарантируется автоматически: для полностью надёжного результата в такой конфигурации необходимо держать блокировку записи активной, пока не будут созданы снимки всех задействованных томов, а не только одного из них.
Ещё один практический нюанс касается модуля ядра dm-snapshot, отвечающего за реализацию снимков в device mapper: на некоторых минимальных или облегчённых сборках дистрибутивов Linux этот модуль не загружен по умолчанию, и попытка выполнить lvcreate с ключом -s завершается ошибкой об отсутствии нужной цели в ядре. Решается это explicit загрузкой модуля командой modprobe dm-snapshot перед первой попыткой создания снимка, после чего операция проходит без дополнительных препятствий.
Наконец, снимок не заменяет полноценную стратегию резервного копирования сам по себе: он лишь даёт короткое, безопасное окно для снятия согласованной копии данных, будь то простое копирование файлов, потоковая передача на внешний сервер или запуск отдельного экземпляра базы для более глубокой проверки целостности. Сам снимок остаётся физически на том же дисковом массиве, что и оригинальные данные, поэтому при полном отказе диска или сервера снимок погибнет вместе с исходным томом, и обязательным дополнением к этой технике остаётся вынос итоговой резервной копии за пределы исходной системы хранения.