-
-
Notifications
You must be signed in to change notification settings - Fork 5
Load‐balancer
Note
This is an internal doc about how the load-balancer is implemented. Please read Downloads first.
Our ZIM load-balancer is based on Mirrorbrain. We are still using mod_mirrorbrain and the MB database.
All the tooling has been replaced though.
A request is processed as follows:
-
https://lb.download.kiwix.org/ is first handled by the k8s ingress which adds CORS headers and handles
/redirection. -
lb-webdeployment is aminibrain-serverimage that is mostly latest apache withmod_mirrorbrainand its dependencies (dbd,mod_form,mod_maxminddb). -
mod_mirrorbrainchecks if the file exists on disk.lb-webhas access to fake data (see below). - It then checks if there's a match for this file on the DB.
- If there is, it build a sorted list of online mirrors holding the file while capable of serving the file to this user (based on IP and IP location).
- If it's a MB endpoint (digest, meta4, etc), it handles it with DB info.
- If it's a standard request to the file, it redirects to the first mirror in the list.
# prevents scanning non-zim folder, especially on mirrors which have (now obsolete) release and other folders
scan_top_include = zim
# size of chunks for SHA1. Smaller chunks means larger torrent/metalkink files
chunk_size = 4194304MirrorBrainEngine On
# very useful when debugging but very verbose
MirrorBrainDebug Off
# very important as it ensures mb never attempts to send the file itself (required for us without real files)
MirrorBrainFallback "eu" "fr" "https://mirror.download.kiwix.org/"
# means that any file larger than this must be handled by mirrors (required for us without real files)
MirrorBrainMinSize 0
# Not required as we have the metadata but saves us some resources
MirrorBrainHandleHEADRequestLocally Offlb-db-sts- Runs on
SRV-S - Latest postgres (
18.4) - Started from a clean DB (using new
copydbscript) - New
mirr_add_bypath_noinsPLSQL function to record a file on a mirror
There are 3 important tables:
| Server | |
|---|---|
id |
2 |
identifier |
mirror.download.kiwix.org |
enabled |
true |
status_baseurl |
true |
baseurl |
https://mirror.download.kiwix.org |
country |
eu |
country |
fr |
only_region |
false |
only_country |
true |
other_countries |
it,gb,pt |
| Filearr | |
|---|---|
id |
6590 |
path |
zim/wikipedia/wikipedia_fr_all_maxi_2026-05.zim |
mirrors |
[2, 4, 7, 3] |
| Hash | |
|---|---|
file_id |
6590 |
size |
55438794004 |
mtime |
1779688887 |
md5 |
b83b918bb2d0fb2cd2f5778219fa67b9 |
sha1 |
b25638806dc0f39682270822836a203e3e181094 |
sha256 |
61401b1b3618afe736450a868c7205f572f590db310e044a9c72b51fbfcfb431 |
btih |
a8e6e34f430c09af02e5ab852b6afc255b863a73 |
Every day (cronjob) we clean-up the database by removing all files that are not on our master mirror and triggering a VACUUM.
We probe mirrors to check their responsiveness.
-
mod_mirrorbrainonly considers mirrors that havestatus_baseurl=True. - we thus test frequently that mirrors are online and change
status_baseurlbased on response. - this probe fails if it timesout after 10s (configurable)
- it's a simple
GETrequest (we only read headers) - we run this every minute
We scan mirrors to verify what files they hold.
- We connect via
rsyncto get file listing - Wikimedia mirror is only one not giving us rsync access. We scan via HTTP 😕.
- We update DB to remove the mirror ID from
Filearr.mirrorsfor each file which is not present anymore and add it to all those present. ⚠️ We differ from MirrorBrain which createsFilearrentries for every file found on mirrors. We don't. We ignore files not already in DB. Not only does this prevent DB from being polluted, it also allows us to control when a file is exposed in the LB.- We run this every mn, if the mirror is online.
- When scanning, we don't consider mtime or size. That's MB behavior, possibly because HTTP scanning is not precise enough. We could maybe improve that if we get rid of HTTP.
Because both probe and scan are run as frequently as every minute and because scan depends on probe, we:
- have 1 cronjob per mirror (every minute, no concurrency). Allows customizing/suspending per mirror in the cluster.
- run a
foreverloop script. That's to not have 10+ jobs created and deleted every mn. - run a
probe-then-scanscript that first probes and if successful runs the scan. Prevents scanning and log-pollution on probe failures - this takes 2-15s depending on mirrors.
- Runs on
SRV-S
Hashes are used by mirrorbrain to respond to metadata requests such as digests. When querying the DB, mod_mirrobrain queries it with the mtime and size of the file on disk, ensuring that there's no discrepency between what it has access to and what it responds.
Important
For regular requests, the mtime and size are not considered.
When redirecting to a mirror, MB only ensures that this path was scanned on said mirror. The file could have disapeared on mirror or changed on mirror without the LB knowing.
Because we want to be torrent-first, we want hashes to be computed before offering a ZIM for download.
That's why we are only creating Filearr entry when we have hashes ready. That differs from MB.
-
lb-makehashescronjob (every minute, no concurrency). - Runs a forever script that triggers every mn.
- Runs on
SRV-Bso it has access to actual ZIM files. - Filters only on
*.zimto prevent adding non-ZIM files to DB. - Computes at about 100MiB/s. That's ~35mn for a 200GiB ZIM.
makehashes script works off two file trees: the actual ZIM files tree which will be used to compute hashes from and a fake hashes tree which will contain a copy of this file tree but composed entirely of sparse files: empty files faking actual size and mirrored mtime.
This hashes tree is used to figure which files have not had their hashes computed without hamering the DB: it's easy and fast to diff files on filesystem with their path, mtime and size.
A matching (path, mtime, size) file means it's up to date in DB.
# zim/wikipedia/wikipedia_el_all_nopic_2026-06.zim has its hashes computed and up-to-date
❯ ls -l /data/lb/zim/wikipedia/wikipedia_el_all_nopic_2026-06.zim
-rw-r--r-- 1 root root 1861146927 Jun 25 05:47 /data/lb/zim/wikipedia/wikipedia_el_all_nopic_2026-06.zim
❯ ls -l /data/hashes/zim/wikipedia/wikipedia_el_all_nopic_2026-06.zim
-rw-r--r-- 1 root root 1861146927 Jun 25 05:47 /data/hashes/zim/wikipedia/wikipedia_el_all_nopic_2026-06.zim
# ghana-bece-past-questions_en_all_2026-06.zim has been updated and needs new hashes
❯ ls -l /data/lb/zim/zimit/ghana-bece-past-questions_en_all_2026-06.zim
-rw-r--r-- 1 root root 8340798 Jun 16 04:32 /data/lb/zim/zimit/ghana-bece-past-questions_en_all_2026-06.zim
❯ ls -l /data/hashes/zim/zimit/ghana-bece-past-questions_en_all_2026-06.zim
-rw-r--r-- 1 root root 18340798 Jun 22 12:16 /data/hashes/zim/zimit/ghana-bece-past-questions_en_all_2026-06.zimmod_mirrorbrain needs a working GeoIP system to retrieve country/region from IP.
We use the free City DB from Maxmind and update it daily in a cronjob running our geoipupdate image.
We generate daily via a cronjob a mirrors.json file that replaces the old MB's mirrors.html.
It is used by operations workflow tests to gather list of enabled mirrors.
mod_mirrorbrain needs access to served files on the filesystem to ensure they exist and retrieve both mtime and size when querying DB for hashes.
Because we don't want to rely on actual ZIM files (8+TiB), we maintain a copy of the entire repository but composed only of sparse files mirroring size and mtime. This lives in memory and is maintained in a fakesync side container that refreshes every 30s.
We also need to generate permalinks on the apache server (to ZIM and all MB endpoints). We do this in a side containers that stores it in-memory. It runs every minute.
_ _ _ _
(_) (_) | (_)
_ __ ___ _ _ __ _| |__ _ __ __ _ _ _ __
| '_ ` _ \| | '_ \| | '_ \| '__/ _` | | '_ \
| | | | | | | | | | | |_) | | | (_| | | | | |
|_| |_| |_|_|_| |_|_|_.__/|_| \__,_|_|_| |_|
MB conf at /etc/mirrobrain.conf
Available tools:
$ status quick overview of mirror statuses
$ probe check if a mirror is online or not
$ scan scan a mirror's filetree to update DB with list of files on mirror
$ probe-then-scan probe then scan if alive (shortcut for prod jobs)
$ makehashes compute hashes from source files and update DB
$ test-hash hash a single file and print its results
$ gen-zim-map create an apache map of version-less ZIM filename to last path
$ cleandb allows vacuuming the DB or removing gone (from master) files
$ genlist generate list of mirrors (JSON format)
$ fileinfo get all details about a specific file
$ latest get list of latest added files on the DB
For monitoring and maintenance, we have two deployments: lb-tools and lb-tools-with-files.
The first one runs on SRV-S and only has access to the DB while the second one runs on SRV-B and also has access to actual ZIM files (/data/lb) and hashes (/data/hashes).
Those pods run minibrain-tools which should cover most needs.
Any advanced operation or query to the DB will require opening a port-forward.
- Add IP to whitelist in
rsyncd-configsand restartmaster-rsyncd - Manually add
Serverentry. - Create a new probe-then-scan job
- ZIM journey starts in the CMS
- CMS moves files on
SRV-Bto the correct location - ZIM is added and visible in the catalog
- then:
-
makehashessees it and computes hashes. - rsync job from
SRV-Mcopies it to master mirror
-
- LB is able to serve it
- mirrors syncs it at their own schedule
ATM, this works because all of this happens fairly quickly but ideally https://github.com/openzim/cms/issues/103 should ensure that we publish only once it's on x mirrors.
- Connect to lb-tools-with-files
- Remove hashes tree version of file
- Wait for makehashes to recreate it (<1mn)
rm -f /data/hashes/zim/wikipedia/wikipedia_en_bad_maxi-2026-01.zimxx