MAATRIX / Блог / Copy-on-write в qcow2: почему снапшот сначала бесплатный, а потом тормозит

Copy-on-write в qcow2: почему снапшот сначала бесплатный, а потом тормозит

MAATRIX

Снапшот виртуалки в KVM создаётся за долю секунды, и это подкупает — кажется, что можно наплодить их сколько угодно перед каждым рискованным обновлением. А через месяц та же виртуалка начинает еле шевелиться на дисковых операциях, хотя вы вроде бы ничего не меняли. Дело не в деградации диска и не в утечке памяти — дело в том, как устроен copy-on-write в qcow2, и в том, что снапшот не бесплатен, просто плата за него откладывается на потом.

Что вообще происходит при создании снапшота qcow2

Когда вы делаете virsh snapshot-create-as или qemu-img snapshot -c, гипервизор не копирует ни байта данных диска. Он берёт текущий qcow2-файл, замораживает его как есть и создаёт новый пустой qcow2-файл поверх — с указателем на старый файл как на backing-файл. Новый файл пуст, в нём нет ни одного блока данных, поэтому операция занимает миллисекунды независимо от того, 20 гигабайт у вас диск или 2 терабайта.

Структура становится такой:

disk.qcow2          <- backing file (заморожен, только на чтение)
   └── disk-snap1.qcow2   <- активный файл, куда сейчас пишет ВМ

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

Механика copy-on-write: почему запись после снапшота дороже

До снапшота запись блока — это одна операция: гипервизор находит нужный кластер в qcow2-файле (или выделяет новый, если это первая запись туда) и пишет данные. После снапшота, если вы хотите изменить блок, который физически лежит в backing-файле, происходит другое:

  1. Гипервизор проверяет L2-таблицу активного qcow2-файла — есть ли уже такой кластер в новом файле.
  2. Если кластера ещё нет (первая запись в это место после снапшота) — он должен прочитать соответствующий кластер из backing-файла целиком.
  3. Записать этот кластер в новый (активный) qcow2-файл — то есть выделить новое место и физически скопировать данные.
  4. И только после этого применить вашу фактическую запись поверх скопированного кластера.
  5. Обновить метаданные (L1/L2 таблицы), чтобы дальнейшие обращения к этому блоку шли уже в новый файл.

Это и есть copy-on-write в буквальном смысле: копирование происходит в момент записи, а не в момент снапшота. Один логический write превращается в read + write + write метаданных. По размеру кластера qcow2 (по умолчанию 64 КБ) это означает, что даже изменение одного 4КБ-сектора внутри гостевой файловой системы может потребовать прочитать и переписать целый 64КБ-кластер, если он ещё не был скопирован в этой сессии.

Важная деталь: это разовая плата за каждый уникальный кластер, а не за каждую запись. Как только кластер скопирован в активный файл один раз, все последующие записи в него идут напрямую, без повторного copy-on-write — до тех пор, пока вы не создадите новый снапшот поверх. Поэтому свежесозданный снапшот бьёт по производительности сильнее всего в первые часы или дни, пока идёт «прогрев»: активно используемые блоки постепенно расходятся с backing-файлом и на них перестаёт срабатывать copy-on-write. А редко изменяемые данные (старые логи, архивы, статика) так и остаются в backing-файле — на них штраф не заканчивается никогда, потому что копия делается один раз при первой записи, и это нормально: вопрос не «когда закончится», а в том, что этот штраф разово случится хотя бы один раз для каждого изменяемого блока.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Почему цепочка снапшотов замедляет чтение, а не только запись

С записью всё более-менее понятно — штраф разовый на кластер. Но есть вторая проблема, которая нарастает с количеством снапшотов и не исчезает со временем: чтение данных, которые остались в старых backing-файлах.

Если у вас накопилась цепочка из нескольких снапшотов:

base.qcow2 ← snap1.qcow2 ← snap2.qcow2 ← snap3.qcow2 (активный)

то чтение блока, который не менялся с момента base-диска, требует пройти по всей цепочке: гипервизор смотрит в snap3 — блока нет, идёт в snap2 — нет, в snap1 — нет, и только в base находит данные. Каждый «промах» — это отдельный лукап по метаданным (L1/L2 таблицам) того файла в цепочке. Чем длиннее цепочка, тем больше таких проверок на каждое чтение старых данных.

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

Наглядно: во что превращается диск через месяц снапшотов

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

МоментФайлов в цепочкеЧто происходит при записиЧто происходит при чтении старых данных
Сразу после установки, без снапшотов1Прямая записьПрямое чтение
После 1-го снапшота2CoW на первую запись в каждый кластерОдин лишний лукап при промахе
После 5 снапшотов подряд (без удаления)6CoW может накапливаться на каждом уровне отдельноДо 5 лишних лукапов на промах
После месяца ежедневных снапшотов30+Резко растёт использование диска, I/O непредсказуемЧтение «холодных» данных заметно медленнее

Плюс к замедлению добавляется рост занятого места: каждый скопированный при CoW кластер физически лежит и в старом, и в новом файле, пока вы не удалите снапшот. Так виртуалка с диском 40 ГБ данных может неожиданно занимать 150+ ГБ на хранилище — не потому что данных стало больше, а потому что несколько версий одних и тех же измененных блоков живут одновременно в разных файлах цепочки. Если у вас в принципе тесно с местом, полезно заранее прикинуть сколько дискового пространства закладывать с запасом — снапшоты входят в этот запас незаметно и быстро.

Как правильно схлопывать (commit) старые снапшоты

Решение — не «не делать снапшоты», а не давать им копиться. Снапшот нужен как временная точка отката на время рискованной операции (обновление, миграция, эксперимент), а не как постоянный слой.

Через virsh (актуально для конца августа 2026 — команды стабильны много лет, но проверяйте синтаксис в своей версии libvirt):

# посмотреть цепочку снапшотов
virsh snapshot-list mydomain --tree

# схлопнуть (commit) снапшот в backing-файл
virsh blockcommit mydomain vda --base disk.qcow2 --top snap1.qcow2 --wait --verbose

# для активного (top) снапшота обычно проще pivot
virsh blockcommit mydomain vda --active --pivot --wait

Через голый qemu-img, если ВМ выключена (для работающей ВМ так делать нельзя — файл может быть повреждён):

qemu-img commit snap1.qcow2

После commit данные из снапшота вливаются в backing-файл, цепочка укорачивается, а лишний файл можно удалить. Практическое правило, которое стоит взять за привычку: если снапшот сделан перед обновлением и обновление прошло успешно — схлопните его в течение суток-двух, не оставляйте «на всякий случай» на неделю. Если что-то пошло не так и вы откатились назад через снапшот — тем более нет смысла его хранить дальше, он уже сослужил свою службу.

Отдельно стоит проверять состояние диска до и после операций commit — если гипервизор и так испытывает нагрузку, blockcommit на боевой машине лучше делать в окно с низкой нагрузкой. Разобраться, где реальное узкое место, поможет статья про то, как проверить, что диск на VPS тормозит.

Когда снапшоты qcow2 — не лучший инструмент вообще

Copy-on-write снапшоты qcow2 хороши как краткосрочная страховка, но плохо подходят для:

  • Долгосрочного бэкапа. Снапшот живёт на том же физическом диске, что и сама ВМ — если диск умрёт, вы потеряете и данные, и все снапшоты разом. Это не замена резервному копированию на другой носитель.
  • Множества параллельных версий. Если вам нужно держать 10+ точек отката одновременно (например, для тестирования разных версий приложения), цепочка станет слишком длинной, и чтение по всей цепочке начнёт заметно тормозить именно старые, «глубокие» слои.
  • Продакшн-баз данных под высокой нагрузкой записи. CoW-штраф на первую запись в каждый блок особенно болезненный для СУБД с активной перезаписью страниц — там и так критична скорость диска, а тут добавляется read-modify-write оверхед. Если тема дисковой производительности под базами для вас актуальна, стоит отдельно посмотреть, почему важна скорость диска для баз данных.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Снапшот занимает 0 байт сразу после создания — это нормально?

Да, это ожидаемое поведение copy-on-write: новый qcow2-файл создаётся пустым, данные в него попадают только по мере записи поверх backing-файла.

Можно ли просто удалить старый snap-файл, если он мешает?

Нет, если на него ссылается активная цепочка — это разрушит целостность диска. Сначала нужно blockcommit (или qemu-img commit при выключенной ВМ), и только затем удалять освободившийся файл.

CoW одинаково влияет на HDD и SSD/NVMe?

Механика одна и та же, но абсолютный эффект на SSD/NVMe заметен меньше за счёт кратно более высокой скорости случайного I/O — на вращающихся дисках лишние read+write операции при CoW ощущаются сильнее.

Снапшот замедляет саму гостевую ОС или только дисковые операции?

Только те операции, которые упираются в диск — сеть, CPU-нагрузка и память не затрагиваются. Но если приложение активно пишет на диск (логи, БД, кэш на диске), общая отзывчивость системы просядет ощутимо.

Есть ли способ вообще избежать штрафа CoW?

Полностью — нет, это плата за саму идею снапшота без предварительного копирования. Но можно минимизировать: реже плодить снапшоты, быстрее их схлопывать и использовать более крупные кластеры qcow2 для рабочих нагрузок с крупными последовательными записями (компромисс — больше «overhead» на мелкие случайные записи).

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →