Дельта-обновление это приём, при котором по сети передаётся не новая версия файла целиком, а компактное описание разницы между тем, что уже стоит на машине, и тем, что должно получиться. Системный диск современной Windows занимает десятки гигабайт, а ежемесячный накопительный пакет для многих сборок укладывается в сотни мегабайт, а экспресс-доставка отдельных исправлений порой в десятки. Эта статья разбирает, как устроена бинарная дельта-компрессия, почему наивный побайтовый дифф на исполняемых файлах бесполезен, что хранится в WinSxS, чем отличаются forward и reverse deltas, зачем нужны PSFX-пакеты и почему экономия трафика оплачивается процессором и диском на стороне клиента.
Бинарная дельта-компрессия против целых файлов
Классический подход к обновлению прост: взять новый файл и положить его поверх старого. Проблема в том, что типичная системная библиотека весит несколько мегабайт, а фактическое исправление затрагивает несколько десятков байт в одной функции. Если обновляется сотня компонентов, пересылка целых файлов превращает маленький фикс в гигабайтный пакет. Дельта-компрессия решает это иначе: на стороне сервера вычисляется разница между старой и новой версией каждого файла, и клиенту передаётся только эта разница плюс инструкции по сборке результата из уже имеющегося у него содержимого.
Ключевой момент состоит в том, что дельта работает не на уровне файлов как непрозрачных blob-объектов, а на уровне их внутренней структуры. Система обновлений знает, какая версия каждого компонента стоит на машине, потому что хранилище компонентов ведёт учёт всех установленных ревизий. Зная точную исходную ревизию, сервер может заранее вычислить дельту именно между парой версий, и эта дельта оказывается крошечной по сравнению даже с хорошо сжатым целым файлом. Клиент применяет её локально, материализует новую версию файла из старой и сверяет криптографический хеш результата с ожидаемым. Если хеш совпал, сборка бит в бит совпадает с эталонной, хотя по сети прошёл лишь маленький патч.
Почему простые диффы на бинарях не работают
Интуитивно хочется взять старый и новый исполняемый файл и посчитать обычный текстовый дифф по байтам. Этот подход разваливается на первом же серьёзном обновлении. Дело в том, что исполняемый файл это не линейный поток независимых байт, а структура, в которой огромное число ссылок зависит от абсолютных и относительных адресов. Если компилятор пересобрал библиотеку и одна функция в начале файла стала на двенадцать байт длиннее, то все функции ниже сдвинулись, и все адреса переходов, таблицы импорта, отладочные ссылки и релокации изменились. Побайтово изменилось почти всё, хотя логически поменялась одна строчка исходника.
Умный патчер поэтому работает на уровне функций. Современные компиляторы Microsoft собирают код в режиме function-level linking, где каждая функция это отдельно адресуемая секция. Дельта-система дизассемблирует обе версии, сопоставляет функции по сигнатурам и по графу вызовов, понимает, какие функции совпадают, какие изменились, какие переехали, и строит патч в терминах этих сущностей: функции с такими-то именами оставить, такие-то заменить новым телом, ссылки между ними переписать по новой карте адресов. Отсутствие такого семантического слоя превращало бы любую пересборку в полную замену файла. Именно поэтому дельта-обновление это не утилита diff над бинарниками, а связка из дизассемблера, структурного сопоставления и генератора инструкций по пересборке образа на клиенте.
WinSxS и пара forward и reverse deltas
Хранилище компонентов WinSxS это база всех версий системных файлов, где лежат сами компоненты и дельты между их ревизиями. Для каждого обновляемого компонента существуют два направления перехода. Forward delta описывает, как из ревизии N получить ревизию N плюс один, и нужна для установки обновления. Reverse delta описывает обратный путь, и нужна для удаления: когда администратор откатывает пакет, система не возвращает файл из резервной копии, а материализует прежнюю ревизию, применяя обратную дельту к текущей. Это изящная экономия: не нужно хранить полные копии обеих версий, достаточно одной базовой ревизии плюс цепочки патчей в обе стороны.
Расплата за такую архитектуру известна каждому, кто обслуживал серверы с долгим аптаймом: хранилище разрастается, потому что для возможности отката хранятся и старые базы, и цепочки дельт. Команда DISM с ключами анализа и очистки хранилища компонентов умеет удалять вытесненные ревизии, с которых уже нет валидных путей отката, и схлопывать цепочки, когда промежуточные состояния больше не нужны. Отсюда же растут ноги у несуразных на первый взгляд цифр в отчётах о занятом месте: часть объёма WinSxS это не дубли файлов, а жёсткие ссылки на одноинстансно хранимые компоненты, плюс дельты, без которых откат был бы невозможен.
DISM, WIM и одноинстансное хранение
Образы развёртывания в формате WIM построены на одноинстансном хранении: каждый уникальный файл хранится в образе один раз, даже если в файловой системе целевой установки он встречается в нескольких местах. Файлы адресуются по хешу содержимого, поэтому идентичные компоненты разных версий одного продукта автоматически дедуплицируются. Для deployment-инженера это значит, что корпоративный образ с несколькими редакциями системы внутри одного WIM весит ненамного больше одной редакции, потому что общие файлы между редакциями хранятся единожды.
DISM это инструмент, который связывает всё вместе: он монтирует WIM, применяет к нему пакеты обновлений, обслуживает хранилище компонентов офлайн-образа и проверяет целостность. При добавлении накопительного пакета в образ DISM фактически прогоняет ту же дельта-сборку, что делает клиент при онлайн-обновлении, только в примонтированном каталоге. Практический вывод для инженера: регулярное обслуживание эталонного образа свежими пакетами с последующей очисткой вытесненных компонентов держит и размер образа, и время раскатки в разумных пределах, а заброшенный образ, на который раз в полугодие наваливают годовой ворох обновлений, применяется мучительно долго именно из-за длинных цепочек дельт.
Экспресс-обновления и кумулятивная модель
В кумулятивной модели каждый ежемесячный пакет содержит все исправления, выпущенные до него, поэтому клиенту достаточно установить последний. Но такой пакет при скачивании целиком был бы огромным, и тут включается экспресс-доставка: служба обновлений вычисляет, какие ревизии компонентов уже стоят на машине, запрашивает у сервера только дельты между локальными ревизиями и целевыми, и собирает результат локально. Алгоритм примерно такой:
- Клиент инвентаризирует хранилище компонентов и сообщает серверу список установленных ревизий.
- Сервер подбирает для каждого компонента дельту от имеющейся ревизии до целевой внутри накопительного пакета.
- Клиент скачивает дельты, применяет их, материализует новые версии файлов и проверяет хеши.
- Установщик подменяет файлы в безопасной транзакции, а reverse deltas остаются для отката.
Эта схема объясняет наблюдение, которое ставит в тупик многих администраторов: скачанный объём маленький, а этап применения при перезагрузке занимает заметное время. Сеть сэкономлена, но счёт предъявлен процессору и диску: клиент декомпрессирует дельты, читает из хранилища базовые версии, собирает из них новые файлы, хеширует и записывает. На машине с медленным диском и слабым процессором дельта-обновление иногда применяется дольше, чем скачался бы и распаковался полный пакет.
MSDelta, PatchAPI и PSFX-пакеты
Технологическая линия бинарных дельт в Microsoft уходит корнями в семейство инструментов MSDelta и более ранний PatchAPI, которые умели строить дельты между произвольными парами файлов. Современная инкарнация этой идеи это PSFX, формат пакетов, с которым работает вся инфраструктура Component-Based Servicing. PSFX-пакет это контейнер, где для каждого компонента лежат forward и reverse дельты относительно известных базовых ревизий плюс манифесты, описывающие зависимости, применимость и порядок установки. Манифесты позволяют установщику рассуждать о системе как о графе компонентов, а не как о куче файлов: он знает, какие пакеты требуют друг друга, в каком порядке материализовать файлы и что делать при откате на любом шаге.
Историческая роль таких API была двойной. Во-первых, они обслуживали собственные обновления операционной системы. Во-вторых, производители приложений могли генерировать дельта-патчи для своих продуктов теми же инструментами, что объясняет, почему идея функционально-ориентированных дельт распространилась далеко за пределы Windows. Современные сборщики дельт внутри Microsoft прошли путь от побайтовых схем к структурным: на больших исполняемых файлах структурное сопоставление даёт патчи в разы меньше, чем лучшие байтовые алгоритмы, ровно потому, что учитывает семантику пересборки, а не слепое совпадение байтовых окон.
Stalled-патчи, сеть против процессора и прагматика переустановки
Дельта-обновление это сделка: меньше по сети в обмен на больше работы на клиенте. Сделка выгодна, когда машины обновляются регулярно: дельта от прошломесячной ревизии крошечная, применение быстрое. Сделка становится проигрышной, когда машина пропустила много циклов: цепочка переходов длинная, материализовать приходится многие компоненты, и пользователь наблюдает stalled-проценты на экране перезагрузки. Парадокс в том, что с хорошим каналом пара гигабайт полного пакета скачивается за минуты, а сборка из дельт на загруженном диске может занять дольше. Поэтому для хронически отставших систем практичный рецепт deployment-инженера звучит грубо: иногда чистовое развёртывание свежего обслуженного образа быстрее и надёжнее, чем догонять систему пятнадцатью поколениями дельт. Не потому что дельты плохи, а потому что амортизация этой технологии рассчитана на регулярный ритм.
Отдельная неснимаемая требование это подпись и целостность. Дельта применяется к подписанному базовому образу и обязана породить результат, чей хеш и подпись проверяются до подмены файлов. Любое расхождение отклоняется, потому что дельта-механизм, принимающий произвольные различия без проверки результата, был бы идеальным вектором подмены системных компонентов.
Тот же принцип у соседей
Идея передавать разницу вместо целого универсальна. Утилита rsync синхронизирует файлы по сети поблочно: получатель считает скользящие слабые чексуммы и сильные хеши блоков своей копии, отправитель находит совпадающие блоки даже со сдвигом и передаёт только недостающие данные. Браузер Chrome применял технологию Courgette, которая делает ровно то, что делает патчер Windows: дизассемблирует старый и новый исполняемые файлы, сопоставляет функции и адреса и строит компактный дифф на семантическом уровне, севодя размер обновления с мегабайт до сотен килобайт. Игровые платформы строят оптимизированные патчи, где пересылаются только изменённые блоки ресурсных контейнеров. Over-the-air обновления прошивок телефонов и автомобильных контроллеров повторяют ту же схему один к одному: известная базовая прошивка, дельта, проверка хеша и подписи результата, атомарное переключение. Везде действует одна и та же физика: канал дорогой и узкий, локальные вычисления дешёвые, а доверие обеспечивается не происхождением дельты, а криптографической проверкой того, что собралось на устройстве.
Журнал применения - не техническая роскошь, а часть контракта дельты. Когда патч применяется к сотне тысяч машин, ошибки «не применилось на семьи процентов парка» видны только в свёрнутой форме агрегированной телеметрии. Поэтому современные платформы обновления пишут подробный журнал каждой транзакции: какая дельта предназначалась, на какой базе собрана, какой хеш получился, какой компонент откатился после сбоя. В разборе инцидентов эти журналы ценнее дампов, потому что позволяют сузить проблему до конкретного ребра между двумя состояниями образа, а не до средней среды на парке.