Updating over a metered link
A component is already installed and a new version is out. The whole archive is 34 MB; the change between the two is 1 MB. This walkthrough sets up the delta server, points a recipe at it, and shows the agent patching instead of downloading.
The numbers here are real, measured on two consecutive Keystone releases while writing this page. Your artifacts will differ ā see what to expect.
What you need
- The two versions of your artifact, uncompressed ā
.tar, not.tar.gz. The publisher keeps publishing.tar.gzexactly as before; the uncompressed form is what the patch is computed over, and it never has to be published. - A host to run the delta server on, reachable from the devices.
- ota-updater ā one implementation
of the delta route the agent expects. Any server exposing
GET /delta/{from}/{to}works.
1. Prepare the two versions
The agent patches the uncompressed archive, so that is what the server needs.
If you publish .tar.gz, decompress each release once:
Keep the digest of the new one. That is what goes in the recipe, and it is what the agent verifies the patched result against.
2. Run the delta server
server.yaml, with the first release declared:
3. Publish the new version
The previous release stays in the store as a source to patch from; the new one becomes the target:
The response echoes the new target_hash and lists the previous one under
history ā that pair is what a patch can be computed between.
4. Point the recipe at it
Only the [artifacts.delta] block is new. Everything else, including the
.tar.gz the artifact is normally downloaded from, stays as it is:
Re-sign the recipe after editing it ā the block is part of the signed bytes:
5. Apply
On a device installing for the first time there is nothing to patch from, so the agent downloads the whole archive and says so:
On a device that already runs 1.0.0 the agent decompresses the copy it has, asks for the patch and reconstructs the release:
That first line is normal and worth understanding: the very first request for a patch that has never been asked for finds nothing cached, so the server answers “not yet” and starts computing it. Generating one took about 10 seconds for a 34 MB artifact. The agent waits and asks again; every device after this one finds it ready.
What to expect
| Size | Share | |
|---|---|---|
| The archive, downloaded whole | 13.4 MB | 100 % |
| The patch, two consecutive releases | 1.0 MB | 8 % |
| The patch, across a Go toolchain change | 6.3 MB | 47 % |
The saving is not a constant. A release that relinks or reorders the binary ā a toolchain bump is the usual cause ā produces a much larger patch, because Go binaries move far more than the size of the source change suggests. Both rows above are the same pair of tools, measured a few releases apart.
Below roughly 1 MB of artifact the machinery is not worth it: the patch approaches the artifact, and you have added a server for nothing.
When it does not work, nothing breaks
Every one of these downloads the whole artifact and continues:
- no previous version on disk;
- the server holds no patch from this device’s version;
- the patch fails to apply, or produces bytes that do not match the recipe’s digest;
- the base is larger than
KEYSTONE_DELTA_MAX_BASE_BYTES; - the server moved to a patch format this agent does not implement.
None of them fails the apply, and the reason is always in the log. A delta is an optimisation; the download is the contract.
Before you enable it on a small device
Patching is done in memory. The base is memory-mapped rather than read onto the
heap, but the reconstructed archive is not, so peak use is roughly the size of
the artifact on top of the agent’s usual footprint. On a 34 MB artifact that is
~34 MB. KEYSTONE_DELTA_MAX_BASE_BYTES (256 MiB by default) is the cap.
The trust model changes too ā the patched bytes are attested by whoever signed the recipe rather than by the artifact’s own signature. That is spelled out in Artifacts; read it before enabling this in a deployment that relies on a separate publisher key.