ariadne.space/content/blog/why-apk-tools-is-different-...

33 lines
3.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

---
title: "Why apk-tools is different than other package managers"
date: "2021-04-25"
---
Alpine as you may know uses the [apk-tools package manager](https://gitlab.alpinelinux.org/alpine/apk-tools), which we built because pre-existing package managers did not meet the design requirements needed to build Alpine.  But what makes it different, and why does that matter?
## `apk add` and `apk del` manipulate the desired state
In traditional package managers like `dnf` and `apt`, requesting the installation or removal of packages causes those packages to be directly installed or removed, after a consistency check.
In `apk`, when you do `apk add foo` or `apk del bar`, it adds `foo` or `bar` as a dependency constraint in `/etc/apk/world` which describes the desired system state.  Package installation or removal is done as a side effect of modifying this system state.  It is also possible to edit `/etc/apk/world` with the text editor of your choice and then use `apk fix` to synchronize the installed packages with the desired system state.
Because of this design, you can also add conflicts to the desired system state.  For example, we [recently had a bug in Alpine where `pipewire-pulse` was preferred over `pulseaudio` due to having a simpler dependency graph](https://gitlab.alpinelinux.org/alpine/apk-tools/-/issues/10742).  This was not a problem though, because users could simply add a conflict against `pipewire-pulse` by doing `apk add !pipewire-pulse`.
Another result of this design is that `apk` will never commit a change to the system that leaves it unbootable.  If it cannot verify the correctness of the requested change, it will back out adding the constraint before attempting to change what packages are actually installed on the system.  This allows our dependency solver to be rigid: there is no way to override or defeat the solver other than providing a scenario that results in a valid solution.
## Verification and unpacking is done in parallel to package fetching
Unlike other package managers, when installing or upgrading packages, `apk` is completely driven by the package fetching I/O.  When the package data is fetched, it is verified and unpacked on the fly.  This allows package installations and upgrades to be extremely fast.
To make this safe, package contents are initially unpacked to temporary files and then atomically renamed once the verification steps are complete and the package is ready to be committed to disk.
## `apk` does not use a particularly advanced solver
Lately, traditional package managers have bragged about having advanced SAT solvers for resolving complicated constraint issues automatically.  For example, [aptitude is capable of solving sudoku puzzles](https://web.archive.org/web/20080823224640/http://algebraicthunk.net/~dburrows/blog/entry/package-management-sudoku/).  `apk` is definitely not capable of that, and I consider that a feature.
While it is true that `apk` does have a deductive dependency solver, it does not perform backtracking.  The solver is also constrained: it is not allowed to make changes to the `/etc/apk/world` file.  This ensures that the solver cannot propose a solution that will leave your system in an inconsistent state.
Personally, I think that trying to make a smart solver instead of appropriately constraining the problem is a poor design choice.  I believe the fact that `apt`, `aptitude` and `dnf` have all written code to constrain their SAT solvers in various ways proves this point.
To conclude, package managers can be made to go fast, and be safe while doing it, but require a careful design that is well-constrained.  `apk` makes its own tradeoffs: a less powerful but easy to audit solver, trickier parallel execution instead of phase-based execution.  These were the right decisions for us, but may not be the right decisions for other distributions.