Обзор виртуального A/B

Android имеет два механизма обновления: обновления A/B (бесшовные) и обновления без A/B. Чтобы уменьшить сложность кода и улучшить процесс обновления, в Android 11 эти два механизма объединены с помощью виртуального A/B, что обеспечивает плавное обновление всех устройств с минимальной стоимостью хранилища. Android 12 предлагает возможность сжатия Virtual A/B для сжатия разделов моментальных снимков. И в Android 11, и в Android 12 применяются следующие правила:

  • Виртуальные обновления A/B выполняются так же, как и обновления A/B. Виртуальные обновления A/B минимизируют время, в течение которого устройство находится в автономном режиме и становится непригодным для использования.
  • Виртуальные обновления A/B можно откатить . Если новая ОС не загружается, устройства автоматически откатываются к предыдущей версии.
  • Виртуальные обновления A/B используют минимум дополнительного пространства , дублируя только те разделы, которые используются загрузчиком. Другие обновляемые разделы создаются в виде снимков .

Предыстория и терминология

В этом разделе определяется терминология и описывается технология, поддерживающая виртуальный A/B.

Устройство-сопоставитель

Device-mapper — это уровень виртуальных блоков Linux, часто используемый в Android. При использовании динамических разделов такие разделы, как /system , представляют собой стек многоуровневых устройств:

  • В нижней части стека находится физический суперраздел (например, /dev/block/by-name/super ).
  • В середине находится dm-linear устройство, определяющее, какие блоки суперраздела образуют данный раздел. Это выглядит как /dev/block/mapper/system_[a|b] на устройстве A/B или /dev/block/mapper/system на устройстве, отличном от A/B.
  • Вверху находится устройство dm-verity , созданное для проверенных разделов. Это устройство проверяет правильность подписи блоков на устройстве dm-linear . Он выглядит как /dev/block/mapper/system-verity и является источником точки монтирования /system .

На рис. 1 показано, как выглядит стек под точкой монтирования /system .

Partition stacking underneath system

Рис. 1. Стек под точкой монтирования /system

dm-снимок

Virtual A/B использует dm-snapshot , модуль устройства отображения для создания моментальных снимков состояния устройства хранения. При использовании dm-snapshot в игре задействованы четыре устройства:

  • Базовое устройство — это устройство, для которого сделан снимок. На этой странице базовым устройством всегда является динамический раздел, например системный или поставщика.
  • Устройство копирования при записи (COW) для регистрации изменений на базовом устройстве. Он может быть любого размера, но он должен быть достаточно большим, чтобы вместить все изменения базового устройства.
  • Устройство моментального снимка создается с использованием цели snapshot . Записи на устройство моментальных снимков записываются на устройство COW. Чтение с устройства моментального снимка. Чтение либо с базового устройства, либо с устройства COW, в зависимости от того, были ли данные, к которым осуществляется доступ, изменены снимком.
  • Исходное устройство создается с использованием цели snapshot-origin . Считывает на исходное устройство, считывает непосредственно с базового устройства. Запись на исходное устройство записывается непосредственно на базовое устройство, но исходные данные резервируются путем записи на устройство COW.

Device mapping for dm-snapshot

Рисунок 2. Сопоставление устройств для dm-snapshot

Сжатые снимки

В Android 12 и более поздних версиях, поскольку требования к пространству в разделе /data могут быть высокими, вы можете включить сжатые снимки в своей сборке, чтобы удовлетворить более высокие требования к пространству раздела /data .

Снимки со сжатием Virtual A/B создаются на основе следующих компонентов, доступных в Android 12 и более поздних версиях:

  • dm-user — модуль ядра, аналогичный FUSE, который позволяет пользовательскому пространству реализовывать блочные устройства.
  • snapuserd — демон пользовательского пространства для реализации нового формата моментальных снимков.

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

Формат COW для сжатых снимков

В Android 12 и более поздних версиях сжатые снимки используют формат COW. Подобно встроенному формату ядра, используемому для несжатых снимков, формат COW для сжатых снимков имеет чередующиеся разделы метаданных и данных. Метаданные исходного формата допускали только операции замены : заменить блок X в базовом изображении содержимым блока Y в снимке. Формат сжатых снимков COW более выразительный и поддерживает следующие операции:

  • Копировать : Блок X в базовом устройстве должен быть заменен блоком Y в базовом устройстве.
  • Заменить : блок X в базовом устройстве должен быть заменен содержимым блока Y в снимке. Каждый из этих блоков сжат gz.
  • Ноль : блок X в базовом устройстве должен быть заменен всеми нулями.
  • XOR : Устройство COW сохраняет байты, сжатые XOR, между блоком X и блоком Y. (Доступно в Android 13 и более поздних версиях.)

Полные OTA-обновления состоят только из операций замены и нуля . Дополнительные обновления OTA могут дополнительно содержать операции копирования .

dm-пользователь в Android 12

Модуль ядра dm-user позволяет userspace реализовывать блочные устройства устройства-сопоставителя. Запись таблицы dm-user создает другое устройство в /dev/dm-user/<control-name> . Процесс userspace может опрашивать устройство для получения запросов на чтение и запись от ядра. Каждый запрос имеет связанный буфер для пользовательского пространства, который может либо заполняться (для чтения), либо распространяться (для записи).

Модуль ядра dm-user предоставляет новый видимый пользователю интерфейс к ядру, который не является частью исходной базы кода kernel.org. До тех пор, пока это не произойдет, Google оставляет за собой право изменять dm-user в Android.

Snapuserd

Компонент пользовательского пространства snapuserd для dm-user реализует виртуальное A/B-сжатие.

В несжатой версии Virtual A/B (либо в Android 11 и более ранних версиях, либо в Android 12 без опции сжатого снимка) устройство COW представляет собой необработанный файл. Когда сжатие включено, COW вместо этого функционирует как устройство dm-user , которое подключено к экземпляру демона snapuserd .

Ядро не использует новый формат COW. Таким образом, компонент snapuserd преобразует запросы между форматом Android COW и встроенным форматом ядра:

Snapuserd component translating requests between Android COW format and kernel built-in format

Рисунок 3. Блок-схема snapuserd в качестве переводчика между форматами Android и Kernel COW.

Эта трансляция и распаковка никогда не происходит на диске. Компонент snapuserd перехватывает операции чтения и записи COW, происходящие в ядре, и реализует их с использованием формата Android COW.

XOR-сжатие

Для устройств, запускаемых с Android 13 и более поздних версий, функция сжатия XOR, которая включена по умолчанию, позволяет снимкам пользовательского пространства хранить байты, сжатые XOR, между старыми блоками и новыми блоками. Когда при обновлении Virtual A/B изменяются только несколько байтов в блоке, схема хранения со сжатием XOR использует меньше места, чем схема хранения по умолчанию, поскольку моментальные снимки не хранят полные 4 КБ. Такое уменьшение размера моментального снимка возможно, поскольку данные XOR содержат много нулей и их легче сжимать, чем необработанные блочные данные. На устройствах Pixel сжатие XOR уменьшает размер снимка на 25–40 %.

Для устройств, обновляющихся до Android 13 и выше, необходимо включить сжатие XOR. Подробности см. в разделе Сжатие XOR .

Процессы виртуального сжатия A/B

В этом разделе представлена ​​подробная информация о процессе сжатия Virtual A/B, используемом в Android 13 и Android 12.

Чтение метаданных (Android 12)

Метаданные создаются демоном snapuserd . Метаданные представляют собой в основном сопоставление двух идентификаторов по 8 байт каждый, которые представляют сектора, подлежащие объединению. В dm-snapshot это называется disk_exception .

struct disk_exception {
    uint64_t old_chunk;
    uint64_t new_chunk;
};

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

Демон snapuserd считывает внутренний файл COW через библиотеку COW и создает метаданные для каждой операции COW, присутствующей в файле COW.

Чтение метаданных инициируется из dm-snapshot в ядре при создании устройства dm- snapshot .

На рисунке ниже представлена ​​диаграмма последовательности пути ввода-вывода для создания метаданных.

Sequence diagram, IO path for metadata construction

Рисунок 4. Последовательность операций для пути ввода-вывода при построении метаданных

Объединение (Android 12)

После завершения процесса загрузки механизм обновления помечает слот как успешную загрузку и инициирует слияние, переключая цель dm-snapshot на цель dm-snapshot-merge .

dm-snapshot просматривает метаданные и инициирует ввод-вывод слияния для каждого исключения диска. Ниже показан общий обзор пути ввода-вывода слияния.

Merge IO path

Рисунок 5. Обзор пути объединения ввода-вывода

Если устройство перезагружается во время процесса слияния, слияние возобновляется при следующей перезагрузке и завершается.

Слои устройства сопоставления

Для устройств, запускаемых с Android 13 и более поздних версий, процессы слияния и слияния снимков при виртуальном A/B-сжатии выполняются компонентом пользовательского пространства snapuserd . Для устройств, обновляющихся до Android 13 и выше, эта функция должна быть включена. Подробности см. в разделе Слияние пользовательского пространства .

Ниже описан процесс сжатия Virtual A/B:

  1. Платформа монтирует раздел /system устройства dm-verity , которое устанавливается поверх устройства dm-user . Это означает, что каждый ввод-вывод из корневой файловой системы направляется на dm-user .
  2. dm-user направляет ввод-вывод демону snapuserd пользовательского пространства, который обрабатывает запрос ввода-вывода.
  3. Когда операция слияния завершена, платформа сворачивает dm-verity поверх dm-linear ( system_base ) и удаляет dm-user .

Процесс виртуального сжатия A/B

Рисунок 6. Процесс виртуального A/B-сжатия

Процесс слияния снимков можно прервать. Если устройство перезагружается во время процесса слияния, процесс слияния возобновляется после перезагрузки.

Инициализация переходов

При загрузке со сжатыми снимками программа инициализации первого этапа должна запустить snapuserd для монтирования разделов. Это создает проблему: когда sepolicy загружается и применяется, snapuserd помещается в неправильный контекст, и его запросы на чтение завершаются неудачей с отказом selinux.

Чтобы решить эту проблему, переходы snapuserd синхронизируются с init следующим образом:

  1. Первый этап init запускает snapuserd с виртуального диска и сохраняет к нему открытый файловый дескриптор в переменной среды.
  2. Первый этап init переключает корневую файловую систему на системный раздел, затем выполняет системную копию init .
  3. Системная копия init считывает объединенную политику sepolicy в строку.
  4. Init вызывает mlock() на всех страницах, поддерживаемых ext4. Затем он деактивирует все таблицы сопоставителей устройств для устройств моментальных снимков и останавливает snapuserd . После этого чтение из разделов запрещено, так как это приводит к взаимоблокировке.
  5. Используя открытый дескриптор копии snapuserd на виртуальном диске, init перезапускает демон с правильным контекстом selinux. Таблицы сопоставления устройств для устройств моментальных снимков повторно активируются.
  6. Init вызывает munlockall() — можно безопасно снова выполнить ввод-вывод.

Использование пространства

В следующей таблице представлено сравнение использования пространства для различных механизмов OTA с использованием ОС Pixel и размеров OTA.

Влияние размера не А/Б А/Б Виртуальный А/Б Виртуальный A/B (сжатый)
Исходное заводское изображение 4,5 ГБ супер (3,8 ГБ образа + 700 МБ зарезервировано) 1 9ГБ супер (3,8Г + 700М зарезервировано, на два слота) 4,5 ГБ супер (изображение 3,8 ГБ + зарезервировано 700 МБ) 4,5 ГБ супер (изображение 3,8 ГБ + зарезервировано 700 МБ)
Другие статические разделы /кэш Никто Никто Никто
Дополнительное хранилище во время OTA (пространство возвращается после применения OTA) 1,4 ГБ в /data 0 3,8 ГБ 2 в /data 2,1 ГБ 2 в /data
Общий объем хранилища, необходимый для применения OTA 5,9 ГБ 3 (супер и данные) 9 ГБ (супер) 8,3 ГБ 3 (супер и данные) 6,6 ГБ 3 (супер и данные)

1 Указывает предполагаемую компоновку, основанную на сопоставлении пикселей.

2 Предполагается, что новый образ системы имеет тот же размер, что и исходный.

3 Требование к пространству является временным до перезагрузки.

Чтобы реализовать Virtual A/B или использовать возможности сжатых снимков, см. раздел «Реализация Virtual A/B».

,

Android имеет два механизма обновления: обновления A/B (бесшовные) и обновления без A/B. Чтобы уменьшить сложность кода и улучшить процесс обновления, в Android 11 эти два механизма объединены с помощью виртуального A/B, что обеспечивает плавное обновление всех устройств с минимальной стоимостью хранилища. Android 12 предлагает возможность сжатия Virtual A/B для сжатия разделов моментальных снимков. И в Android 11, и в Android 12 применяются следующие правила:

  • Виртуальные обновления A/B выполняются так же, как и обновления A/B. Виртуальные обновления A/B минимизируют время, в течение которого устройство находится в автономном режиме и становится непригодным для использования.
  • Виртуальные обновления A/B можно откатить . Если новая ОС не загружается, устройства автоматически откатываются к предыдущей версии.
  • Виртуальные обновления A/B используют минимум дополнительного пространства , дублируя только те разделы, которые используются загрузчиком. Другие обновляемые разделы создаются в виде снимков .

Предыстория и терминология

В этом разделе определяется терминология и описывается технология, поддерживающая виртуальный A/B.

Устройство-сопоставитель

Device-mapper — это уровень виртуальных блоков Linux, часто используемый в Android. При использовании динамических разделов такие разделы, как /system , представляют собой стек многоуровневых устройств:

  • В нижней части стека находится физический суперраздел (например, /dev/block/by-name/super ).
  • В середине находится dm-linear устройство, определяющее, какие блоки суперраздела образуют данный раздел. Это выглядит как /dev/block/mapper/system_[a|b] на устройстве A/B или /dev/block/mapper/system на устройстве, отличном от A/B.
  • Вверху находится устройство dm-verity , созданное для проверенных разделов. Это устройство проверяет правильность подписи блоков на устройстве dm-linear . Он выглядит как /dev/block/mapper/system-verity и является источником точки монтирования /system .

На рис. 1 показано, как выглядит стек под точкой монтирования /system .

Partition stacking underneath system

Рис. 1. Стек под точкой монтирования /system

dm-снимок

Virtual A/B использует dm-snapshot , модуль устройства отображения для создания моментальных снимков состояния устройства хранения. При использовании dm-snapshot в игре задействованы четыре устройства:

  • Базовое устройство — это устройство, для которого сделан снимок. На этой странице базовым устройством всегда является динамический раздел, например системный или поставщика.
  • Устройство копирования при записи (COW) для регистрации изменений на базовом устройстве. Он может быть любого размера, но он должен быть достаточно большим, чтобы вместить все изменения базового устройства.
  • Устройство моментального снимка создается с использованием цели snapshot . Записи на устройство моментальных снимков записываются на устройство COW. Чтение с устройства моментального снимка. Чтение либо с базового устройства, либо с устройства COW, в зависимости от того, были ли данные, к которым осуществляется доступ, изменены снимком.
  • Исходное устройство создается с использованием цели snapshot-origin . Считывает на исходное устройство, считывает непосредственно с базового устройства. Запись на исходное устройство записывается непосредственно на базовое устройство, но исходные данные резервируются путем записи на устройство COW.

Device mapping for dm-snapshot

Рисунок 2. Сопоставление устройств для dm-snapshot

Сжатые снимки

В Android 12 и более поздних версиях, поскольку требования к пространству в разделе /data могут быть высокими, вы можете включить сжатые снимки в своей сборке, чтобы удовлетворить более высокие требования к пространству раздела /data .

Снимки со сжатием Virtual A/B создаются на основе следующих компонентов, доступных в Android 12 и более поздних версиях:

  • dm-user — модуль ядра, аналогичный FUSE, который позволяет пользовательскому пространству реализовывать блочные устройства.
  • snapuserd — демон пользовательского пространства для реализации нового формата моментальных снимков.

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

Формат COW для сжатых снимков

В Android 12 и более поздних версиях сжатые снимки используют формат COW. Подобно встроенному формату ядра, используемому для несжатых снимков, формат COW для сжатых снимков имеет чередующиеся разделы метаданных и данных. Метаданные исходного формата допускали только операции замены : заменить блок X в базовом изображении содержимым блока Y в снимке. Формат сжатых снимков COW более выразительный и поддерживает следующие операции:

  • Копировать : Блок X в базовом устройстве должен быть заменен блоком Y в базовом устройстве.
  • Заменить : блок X в базовом устройстве должен быть заменен содержимым блока Y в снимке. Каждый из этих блоков сжат gz.
  • Ноль : блок X в базовом устройстве должен быть заменен всеми нулями.
  • XOR : Устройство COW сохраняет байты, сжатые XOR, между блоком X и блоком Y. (Доступно в Android 13 и более поздних версиях.)

Полные OTA-обновления состоят только из операций замены и нуля . Дополнительные обновления OTA могут дополнительно содержать операции копирования .

dm-пользователь в Android 12

Модуль ядра dm-user позволяет userspace реализовывать блочные устройства устройства-сопоставителя. Запись таблицы dm-user создает другое устройство в /dev/dm-user/<control-name> . Процесс userspace может опрашивать устройство для получения запросов на чтение и запись от ядра. Каждый запрос имеет связанный буфер для пользовательского пространства, который может либо заполняться (для чтения), либо распространяться (для записи).

Модуль ядра dm-user предоставляет новый видимый пользователю интерфейс к ядру, который не является частью исходной базы кода kernel.org. До тех пор, пока это не произойдет, Google оставляет за собой право изменять dm-user в Android.

Snapuserd

Компонент пользовательского пространства snapuserd для dm-user реализует виртуальное A/B-сжатие.

В несжатой версии Virtual A/B (либо в Android 11 и более ранних версиях, либо в Android 12 без опции сжатого снимка) устройство COW представляет собой необработанный файл. Когда сжатие включено, COW вместо этого функционирует как устройство dm-user , которое подключено к экземпляру демона snapuserd .

Ядро не использует новый формат COW. Таким образом, компонент snapuserd преобразует запросы между форматом Android COW и встроенным форматом ядра:

Snapuserd component translating requests between Android COW format and kernel built-in format

Рисунок 3. Блок-схема snapuserd в качестве переводчика между форматами Android и Kernel COW.

Эта трансляция и распаковка никогда не происходит на диске. Компонент snapuserd перехватывает операции чтения и записи COW, происходящие в ядре, и реализует их с использованием формата Android COW.

XOR-сжатие

Для устройств, запускаемых с Android 13 и более поздних версий, функция сжатия XOR, которая включена по умолчанию, позволяет снимкам пользовательского пространства хранить байты, сжатые XOR, между старыми блоками и новыми блоками. Когда при обновлении Virtual A/B изменяются только несколько байтов в блоке, схема хранения со сжатием XOR использует меньше места, чем схема хранения по умолчанию, поскольку моментальные снимки не хранят полные 4 КБ. Такое уменьшение размера моментального снимка возможно, поскольку данные XOR содержат много нулей и их легче сжимать, чем необработанные блочные данные. На устройствах Pixel сжатие XOR уменьшает размер снимка на 25–40 %.

Для устройств, обновляющихся до Android 13 и выше, необходимо включить сжатие XOR. Подробности см. в разделе Сжатие XOR .

Процессы виртуального сжатия A/B

В этом разделе представлена ​​подробная информация о процессе сжатия Virtual A/B, используемом в Android 13 и Android 12.

Чтение метаданных (Android 12)

Метаданные создаются демоном snapuserd . Метаданные представляют собой в основном сопоставление двух идентификаторов по 8 байт каждый, которые представляют сектора, подлежащие объединению. В dm-snapshot это называется disk_exception .

struct disk_exception {
    uint64_t old_chunk;
    uint64_t new_chunk;
};

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

Демон snapuserd считывает внутренний файл COW через библиотеку COW и создает метаданные для каждой операции COW, присутствующей в файле COW.

Чтение метаданных инициируется из dm-snapshot в ядре при создании устройства dm- snapshot .

На рисунке ниже представлена ​​диаграмма последовательности пути ввода-вывода для создания метаданных.

Sequence diagram, IO path for metadata construction

Рисунок 4. Последовательность операций для пути ввода-вывода при построении метаданных

Объединение (Android 12)

После завершения процесса загрузки механизм обновления помечает слот как успешную загрузку и инициирует слияние, переключая цель dm-snapshot на цель dm-snapshot-merge .

dm-snapshot просматривает метаданные и инициирует ввод-вывод слияния для каждого исключения диска. Ниже показан общий обзор пути ввода-вывода слияния.

Merge IO path

Рисунок 5. Обзор пути объединения ввода-вывода

Если устройство перезагружается во время процесса слияния, слияние возобновляется при следующей перезагрузке и завершается.

Слои устройства сопоставления

Для устройств, запускаемых с Android 13 и более поздних версий, процессы слияния и слияния снимков при виртуальном A/B-сжатии выполняются компонентом пользовательского пространства snapuserd . Для устройств, обновляющихся до Android 13 и выше, эта функция должна быть включена. Подробности см. в разделе Слияние пользовательского пространства .

Ниже описан процесс сжатия Virtual A/B:

  1. Платформа монтирует раздел /system устройства dm-verity , которое устанавливается поверх устройства dm-user . Это означает, что каждый ввод-вывод из корневой файловой системы направляется на dm-user .
  2. dm-user направляет ввод-вывод демону snapuserd пользовательского пространства, который обрабатывает запрос ввода-вывода.
  3. Когда операция слияния завершена, платформа сворачивает dm-verity поверх dm-linear ( system_base ) и удаляет dm-user .

Процесс виртуального сжатия A/B

Рисунок 6. Процесс виртуального A/B-сжатия

Процесс слияния снимков можно прервать. Если устройство перезагружается во время процесса слияния, процесс слияния возобновляется после перезагрузки.

Инициализация переходов

При загрузке со сжатыми снимками программа инициализации первого этапа должна запустить snapuserd для монтирования разделов. Это создает проблему: когда sepolicy загружается и применяется, snapuserd помещается в неправильный контекст, и его запросы на чтение завершаются неудачей с отказом selinux.

Чтобы решить эту проблему, переходы snapuserd синхронизируются с init следующим образом:

  1. Первый этап init запускает snapuserd с виртуального диска и сохраняет к нему открытый файловый дескриптор в переменной среды.
  2. Первый этап init переключает корневую файловую систему на системный раздел, затем выполняет системную копию init .
  3. Системная копия init считывает объединенную политику sepolicy в строку.
  4. Init вызывает mlock() на всех страницах, поддерживаемых ext4. Затем он деактивирует все таблицы сопоставителей устройств для устройств моментальных снимков и останавливает snapuserd . После этого чтение из разделов запрещено, так как это приводит к взаимоблокировке.
  5. Используя открытый дескриптор копии snapuserd на виртуальном диске, init перезапускает демон с правильным контекстом selinux. Таблицы сопоставления устройств для устройств моментальных снимков повторно активируются.
  6. Init вызывает munlockall() — можно безопасно снова выполнить ввод-вывод.

Использование пространства

В следующей таблице представлено сравнение использования пространства для различных механизмов OTA с использованием ОС Pixel и размеров OTA.

Влияние размера не А/Б А/Б Виртуальный А/Б Виртуальный A/B (сжатый)
Исходное заводское изображение 4,5 ГБ супер (3,8 ГБ образа + 700 МБ зарезервировано) 1 9ГБ супер (3,8Г + 700М зарезервировано, на два слота) 4,5 ГБ супер (изображение 3,8 ГБ + зарезервировано 700 МБ) 4,5 ГБ супер (изображение 3,8 ГБ + зарезервировано 700 МБ)
Другие статические разделы /кэш Никто Никто Никто
Дополнительное хранилище во время OTA (пространство возвращается после применения OTA) 1,4 ГБ в /data 0 3,8 ГБ 2 в /data 2,1 ГБ 2 в /data
Общий объем хранилища, необходимый для применения OTA 5,9 ГБ 3 (супер и данные) 9 ГБ (супер) 8,3 ГБ 3 (супер и данные) 6,6 ГБ 3 (супер и данные)

1 Указывает предполагаемую компоновку, основанную на сопоставлении пикселей.

2 Предполагается, что новый образ системы имеет тот же размер, что и исходный.

3 Требование к пространству является временным до перезагрузки.

Чтобы реализовать Virtual A/B или использовать возможности сжатых снимков, см. раздел «Реализация Virtual A/B».