Skip to content

Instantly share code, notes, and snippets.

@marceldarvas
Created April 22, 2026 22:56
Show Gist options
  • Select an option

  • Save marceldarvas/b5e8227fc82ba24233b8701449e69d10 to your computer and use it in GitHub Desktop.

Select an option

Save marceldarvas/b5e8227fc82ba24233b8701449e69d10 to your computer and use it in GitHub Desktop.
Backblaze bzserv memory leak on macOS — diagnosis, remediation, and a daily launchd workaround

Backblaze bzserv Memory Leak — Runbook (macOS)

A workaround and diagnosis guide for a memory leak in Backblaze Personal Backup's bzserv process that quietly fills macOS swap until the system runs out of application memory.

Symptoms

  • Activity Monitor shows Backblaze using tens of GB of memory.
  • sysctl vm.swapusage reports swap climbing into the 20–60 GB range over a few days of uptime.
  • macOS eventually surfaces "Your system has run out of application memory."
  • ps -o rss -p "$(pgrep -x bzserv)" looks fine (~100 MB). The leak is hidden in compressed + swapped-out dirty pages that don't show up in RSS.

Affected version

Observed on Backblaze 10.0.1.1038 on macOS 26 (Apple Silicon). Earlier 9.x had a separate but related problem: Backblaze's frequent .tmp → rename writes under /Library/Backblaze.bzpkg/bzdata/bzreports/ caused fseventsd/Spotlight to bloat. Upgrading to 10.x fixed that one and introduced this MALLOC_SMALL leak in bzserv itself.

Diagnosis

Activity Monitor's "Memory" column shows phys_footprint, not RSS. That's why ps disagrees — you need the Mach APIs:

PID="$(pgrep -x bzserv)"
sudo footprint "$PID" | head -20
sudo vmmap -summary "$PID" | head -30

A leaking bzserv shows a huge MALLOC_SMALL region with nearly 100% of it swapped out, e.g.:

Physical footprint:         49.0G
Writable regions: Total=49.1G written=49.0G(100%) resident=27.6M(0%) swapped_out=49.0G(100%)
MALLOC_SMALL   48.8G   1968K   ...   12495 regions

Observed rate: ~500 MB/hour, ~12 GB/day of small allocations that are never freed. They get compressed/swapped aggressively, so the cost is invisible in most tools but saturates swap after a few days.

Immediate remediation

Graceful restart (only bzserv is recycled; bztransmit and bzfilelist are untouched; launchd respawns it):

sudo launchctl kickstart -k system/com.backblaze.bzserv
sudo purge   # nudges VM to drop clean cached pages; swap reclaims lazily
sysctl vm.swapusage

Verify the new footprint:

sleep 3
NEWPID="$(pgrep -x bzserv)"
sudo footprint "$NEWPID" | head -4

Expect the new bzserv to come up with footprint in the tens of MB. Swap won't shrink immediately — macOS holds swapfiles around and reclaims them lazily — but pressure goes away right away.

If kickstart doesn't recover it for some reason, the hard-kill fallback is plain pkill:

sudo pkill -9 bzserv bztransmit bzfilelist bzbmenu
# launchd will respawn bzserv; the others spin up on demand

Wrapper script

restart-bzserv.sh — graceful bounce with before/after footprint and swap summary. Safe to run any time.

#!/usr/bin/env zsh
set -u

echo "── Restarting bzserv ──"

OLD_PID="$(pgrep -x bzserv || true)"
if [[ -n "${OLD_PID}" ]]; then
    OLD_FP="$(sudo footprint "${OLD_PID}" 2>/dev/null | awk '/Footprint:/ {print $3, $4; exit}')"
    echo "Before: pid=${OLD_PID}  footprint=${OLD_FP:-unknown}"
else
    echo "Before: bzserv not running"
fi

sudo launchctl kickstart -k system/com.backblaze.bzserv

for _ in 1 2 3 4 5; do
    NEW_PID="$(pgrep -x bzserv || true)"
    [[ -n "${NEW_PID}" && "${NEW_PID}" != "${OLD_PID}" ]] && break
    sleep 1
done

if [[ -z "${NEW_PID:-}" ]]; then
    echo "⚠️  bzserv did not respawn within 5s"
    exit 1
fi

sleep 2
NEW_FP="$(sudo footprint "${NEW_PID}" 2>/dev/null | awk '/Footprint:/ {print $3, $4; exit}')"
echo "After:  pid=${NEW_PID}  footprint=${NEW_FP:-unknown}"

sudo purge

echo
echo "── Swap status ──"
sysctl vm.swapusage | sed 's/^/  /'

echo
echo "✓ bzserv restarted"

Scheduled workaround (LaunchDaemon)

A daily launchd job keeps the leak bounded. Pick a label in your own reverse-DNS namespace. This example uses com.example.restart-bzserv; adjust as needed.

/Library/LaunchDaemons/com.example.restart-bzserv.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.restart-bzserv</string>

    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>/path/to/restart-bzserv.sh</string>
    </array>

    <!-- Daily at 03:10 local. Avoid 03:00 sharp so we don't collide with
         all the other on-the-hour scheduled jobs. -->
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key><integer>3</integer>
        <key>Minute</key><integer>10</integer>
    </dict>

    <key>RunAtLoad</key><false/>
    <key>StartCalendarIntervalStartsImmediatelyIfLate</key><false/>

    <key>ProcessType</key><string>Background</string>
    <key>LowPriorityIO</key><true/>
    <key>Nice</key><integer>5</integer>
    <key>AbandonProcessGroup</key><true/>

    <key>StandardOutPath</key><string>/var/log/restart-bzserv.log</string>
    <key>StandardErrorPath</key><string>/var/log/restart-bzserv.log</string>
</dict>
</plist>

Install:

sudo install -o root -g wheel -m 0644 \
    ./com.example.restart-bzserv.plist \
    /Library/LaunchDaemons/com.example.restart-bzserv.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.restart-bzserv.plist

Inspect / trigger / disable:

sudo launchctl print system/com.example.restart-bzserv
sudo launchctl kickstart -k system/com.example.restart-bzserv   # ad-hoc run
sudo launchctl disable   system/com.example.restart-bzserv
sudo launchctl enable    system/com.example.restart-bzserv

Uninstall when Backblaze ships a fix:

sudo launchctl bootout system/com.example.restart-bzserv
sudo rm /Library/LaunchDaemons/com.example.restart-bzserv.plist
sudo rm -f /var/log/restart-bzserv.log

Related: Spotlight exclusion for Backblaze state dir

Harmless on 10.x but useful on 9.x — prevents mds/mdworker from chasing Backblaze's high-frequency .tmp writes under its bundle, which cascades into fseventsd memory growth.

/Library/Backblaze.bzpkg is a bundle (kind BackblazePackage) and cannot be dragged into System Settings → Spotlight → Privacy. Use Apple's documented marker file instead:

sudo touch /Library/Backblaze.bzpkg/.metadata_never_index
sudo touch /Library/Backblaze.bzpkg/bzdata/.metadata_never_index

SIP caveat

On modern macOS, launchctl kickstart on Apple launchd jobs (e.g. com.apple.fseventsd) is blocked by SIP (Operation not permitted). The Backblaze job is third-party and not subject to this restriction, so launchctl kickstart -k system/com.backblaze.bzserv works as root.

If you also need to flush a bloated fseventsd, the workaround is a signal rather than launchctl:

sudo kill -HUP "$(pgrep -x fseventsd)"

Reporting upstream

If the leak persists on a newer Backblaze release, capture state and attach to a support ticket:

sudo vmmap -summary "$(pgrep -x bzserv)" > /tmp/bzserv-vmmap.txt
sudo footprint       "$(pgrep -x bzserv)" > /tmp/bzserv-footprint.txt
sw_vers
defaults read /Applications/Backblaze.app/Contents/Info CFBundleShortVersionString
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment