
Jede App, die Sie mit der WebCatalog-Desktop-App erstellten, belegte unter macOS bisher rund 320 MB Speicherplatz. Für eine Electron-basierte App ist das nicht ungewöhnlich. WebCatalog ist jedoch für Menschen gedacht, die viele Web-Apps als Desktop-Apps nutzen. Wer zehn davon installierte, konnte damit mehr als 3 GB belegen – obwohl die Daten in diesen Apps größtenteils identisch waren.
Wir haben kürzlich geändert, wie die WebCatalog-Desktop-App Apps unter macOS erstellt. Die erste App benötigt jetzt rund 340 MB physischen Speicherplatz. Darin enthalten ist eine lokal vorgehaltene, vorbereitete Kopie der App-Engine. Danach belegt jede zusätzliche App in der Regel nur 1–2 MB zusätzlichen physischen Speicherplatz. In unseren Tests sank der Platzbedarf von zehn Apps von mehr als 3 GB auf rund 360 MB.
Dafür nutzen wir eine Funktion, die macOS bereits mitbringt: APFS-Klone. Die Herausforderung bestand nicht einfach darin, Dateien zu klonen. Entscheidend war, dass das Klonen funktioniert und dabei jede App unabhängig, korrekt codesigniert und funktional gleichwertig mit einer nach dem bisherigen Verfahren erstellten App bleibt.
Warum jede App weitere 320 MB belegte
Die WebCatalog-Desktop-App verwandelt Websites in eigenständige Desktop-Apps. Jede erstellte App läuft mit Photon, unserer auf Electron basierenden App-Engine. Unter macOS ist jede davon ein normales .app-Bundle, das Electron (Chromium und Node.js), Photon und die Dateien für die jeweilige App enthält.
Nehmen wir zum Beispiel zwei Apps: Slack und Discord. Ihre Namen, Symbole, Bundle-IDs und Konfigurationen unterscheiden sich, aber das große Electron-Framework und der Großteil von Photon sind identisch.
Bisher wurde jede App als vollständig separate Kopie erstellt.
Slack.app
Electron
Photon
Slack-spezifische Dateien
Discord.app
Electron
Photon
Discord-spezifische Dateien
Das Electron-Framework und Photon konnten in beiden Apps Byte für Byte identisch sein, trotzdem speicherte macOS für jede App eine weitere physische Kopie. Zehn Apps bedeuteten somit ungefähr zehn Kopien eines nahezu identischen, 320 MB großen App-Bundles.
Diese Architektur hatte allerdings eine Eigenschaft, die wir erhalten wollten: Jede App war vollständig unabhängig. Das Löschen einer App konnte sich nicht auf eine andere auswirken, und installierte Apps waren nicht darauf angewiesen, dass die WebCatalog-Desktop-App weiterhin auf dem System vorhanden war.
Beseitigen wollten wir nur die unnötige Duplizierung der zugrunde liegenden Daten.
APFS bot bereits die Funktion, die wir brauchten
Moderne Macs verwenden Apples Dateisystem APFS. APFS unterstützt Klonen: einen Copy-on-Write-Mechanismus, durch den zwei unabhängige Dateien dieselben physischen Daten gemeinsam nutzen können, bis sich eine von ihnen ändert.
Stellen Sie sich vor, Sie kopieren eine 300 MB große Datei. Bei einer normalen Kopie schreibt macOS weitere 300 MB auf den Datenträger. Zusammen belegen die beiden Dateien also rund 600 MB.
Bei einem APFS-Klon sieht die neue Datei weiterhin wie eine vollständige 300-MB-Datei aus und verhält sich auch so. Anfangs verweist sie jedoch auf dieselben physischen Blöcke wie das Original.
Datei A ────┐
├── gemeinsam genutzte physische Blöcke
Datei B ────┘
Wenn sich später ein Teil von Datei B ändert, schreibt APFS nur für die geänderten Daten neue Blöcke. Alles, was identisch bleibt, kann die ursprünglichen Blöcke weiterhin gemeinsam nutzen.
Datei A ─────── gemeinsam genutzte Blöcke
Datei B ─────── gemeinsam genutzte Blöcke
└────── geänderte Blöcke
Aus Sicht der Anwendung sind Datei A und Datei B getrennte Dateien. Aus Sicht der SSD müssen identische Daten nur einmal gespeichert werden.
Genau dieses Verhalten brauchten wir.
Apps aus einer gemeinsamen Basis erstellen
Die WebCatalog-Desktop-App bereitet jetzt für jede Kombination aus Electron- und Photon-Version eine Basis vor. Diese Photon-Basis-App enthält die Teile, die bei allen Apps identisch sind: Electron, Photon, die Framework-Struktur und die gemeinsamen Signaturen.
Wenn die Desktop-App eine weitere App mit denselben Versionen erstellt, entpackt und erstellt sie nicht mehr von Grund auf eine weitere vollständige Kopie. Stattdessen erstellt sie einen APFS-Klon der Photon-Basis-App und passt nur die Teile an, die sich unterscheiden müssen.
Dazu gehören der App-Name, das Symbol, die Bundle-ID, Einstellungen, Namen ausführbarer Dateien, die Build-ID und weitere app-spezifische Metadaten.
Da der Großteil des Bundles unverändert bleibt, nutzt APFS die physischen Blöcke mit Electron und Photon weiterhin gemeinsam. Nur die vergleichsweise geringe Menge app-spezifischer Daten benötigt zusätzlichen Speicherplatz.
Warum nicht einfach Electron gemeinsam nutzen?
Ein naheliegenderer Ansatz wäre, Electron einmal zu installieren und jede App darauf verweisen zu lassen.
Wir haben das erwogen, doch es würde die Zuverlässigkeit grundlegend verändern. Wenn die gemeinsam genutzte Electron-Installation verschwindet, könnten alle davon abhängigen Apps nicht mehr funktionieren. Ein Bereinigungsprogramm könnte sie entfernen, bei der Deinstallation der WebCatalog-Desktop-App könnte sie gelöscht werden, und das Verschieben oder Wiederherstellen einer einzelnen App würde komplizierter.
Außerdem müsste nachverfolgt werden, welche installierten Apps von welchen Runtime-Versionen abhängen, damit alte Versionen gefahrlos entfernt werden können.
Die Codesignierung unter macOS bringt ein weiteres Problem mit sich: Die strenge Signaturprüfung lehnt symbolische Links ab, die auf Ziele außerhalb des App-Bundles verweisen.
APFS-Klone bieten uns den Vorteil gemeinsam genutzter Daten, ohne eine solche Abhängigkeit einzuführen. Installierte Apps können sich physische Speicherblöcke teilen und bleiben dennoch vollständige, unabhängige App-Bundles.
Das Löschen der Photon-Basis-App beeinträchtigt keine Apps, die aus ihr erstellt wurden. Das Löschen einer installierten App wirkt sich nicht auf andere aus. Das Dateisystem übernimmt die gemeinsame Nutzung, während die Apps architektonisch unabhängig bleiben.
Die Codesignierung hätte die Einsparungen beinahe zunichtegemacht
Das Klonen der App war nur ein Teil der Aufgabe. macOS-Apps müssen außerdem codesigniert sein.
Unsere bisherige Build-Pipeline signierte nach der Anpassung jedes App-Bundle einschließlich aller enthaltenen Komponenten neu. Bei einer normalen Kopie ist das unproblematisch. Bei Copy-on-Write-Speicherung kann die Änderung einer großen Binärdatei jedoch dazu führen, dass APFS neue physische Blöcke dafür zuweist.
Während der Entwicklung haben wir gemessen, dass die erneute Signierung des Electron-Frameworks pro App rund 194 MB zusätzlichen physischen Speicherplatz benötigte. Wir hatten das Kopieren von Electron beim Erstellen der App erfolgreich vermieden, nur um einen großen Teil davon beim Signieren erneut zu duplizieren.
Deshalb mussten wir auch den Signierprozess ändern.
Das große, gemeinsame Framework wird jetzt als Teil der Photon-Basis-App signiert. Wenn die Desktop-App diese Basis klont, verändern wir die großen, gemeinsam genutzten Binärdateien nicht und signieren nur die kleineren Teile neu, die sich zwischen den Apps tatsächlich unterscheiden müssen.
Sobald die App fertig ist, führen wir für das fertige Bundle eine strenge Signaturprüfung durch, um sicherzustellen, dass alles gültig bleibt.
So bleiben sowohl die Garantien der macOS-Codesignierung als auch die Speicherersparnis durch APFS erhalten.
Wenn Node.js klonen sollte, aber nicht wirklich klonte
Ein weiteres unerwartetes Problem war das Klonen selbst.
Node.js bietet Flags für Kopiervorgänge, mit denen Copy-on-Write angefordert werden soll, darunter COPYFILE_FICLONE und COPYFILE_FICLONE_FORCE. Auf dem Papier klangen sie nach genau den APIs, die wir brauchten.
In unseren Tests mit Node.js 24 kopierten wir dieselbe 288 MB große Electron-App mit verschiedenen Methoden und maßen, wie viel zusätzlichen physischen Speicherplatz jeder Vorgang benötigte:
fs.cpSync +288 MB
fs.cpSync + COPYFILE_FICLONE +288 MB
fs.cpSync + COPYFILE_FICLONE_FORCE +288 MB
/bin/cp -c -R ~0 MB
Beide Klonmodi von Node.js erzeugten in unserem Test weiterhin eine vollständige physische Kopie. Selbst die FORCE-Variante meldete keinen Fehler, obwohl das erwartete Klonen ausblieb.
Der native macOS-Befehl cp -c verhielt sich wie erwartet. Deshalb nutzt die WebCatalog-Desktop-App diesen Mechanismus direkt.
Das war auch eine Erinnerung daran, Optimierungen des Dateisystems durch Messungen am Dateisystem selbst zu überprüfen. Eine API aufzurufen, die Copy-on-Write anfordert, bedeutet nicht zwangsläufig, dass die entstandenen Dateien tatsächlich physischen Speicher gemeinsam nutzen.
Die Optimierung darf scheitern – die Installation nicht
Speicherplatz zu sparen ist eine Optimierung. Eine App erfolgreich zu installieren ist dagegen unverzichtbar.
Wir haben die neue Pipeline so gestaltet, dass ein fehlgeschlagener Klon die App-Installation nicht verhindert. Wenn die Vorbereitung der Photon-Basis-App scheitert, die zwischengespeicherte Basis beschädigt ist, das Klonen fehlschlägt oder die Signaturprüfung nicht bestanden wird, greift die Desktop-App auf den bisherigen Build-Prozess mit vollständigem Entpacken und vollständiger Signierung zurück.
Im ungünstigsten Fall ist der Speicherverbrauch also so hoch wie zuvor – die Installation schlägt deshalb nicht fehl.
Auch gleichzeitige Vorgänge mussten wir berücksichtigen. Eine Photon-Basis-App wird zunächst in einem temporären Verzeichnis vorbereitet und erst an ihren endgültigen Ort verschoben, wenn sie vollständig ist. So können zwei gleichzeitig laufende App-Builds nicht versehentlich eine nur teilweise erstellte Basis verwenden.
Ältere Basis-Apps lassen sich ebenfalls gefahrlos entfernen. Ein installierter APFS-Klon ist nicht darauf angewiesen, dass die ursprüngliche Basis weiterhin existiert. Wird die Basis gelöscht, behält der Klon die physischen Blöcke, die er noch verwendet.
Das funktioniert auf APFS, dem Standarddateisystem jedes modernen Macs. Wenn Ihre Apps auf einem Datenträger liegen, auf dem Klonen nicht möglich ist – etwa einem externen HFS+- oder exFAT-Laufwerk –, greift die WebCatalog-Desktop-App auf eine normale Kopie zurück. Die Apps funktionieren dann wie bisher, sparen aber keinen Speicherplatz. Unter Windows und Linux ändert sich vorerst nichts.
Logische und physische Größe sind nicht dasselbe
Ein etwas verwirrender Nebeneffekt ist, dass der Finder für jede App möglicherweise weiterhin eine Größe von rund 320 MB anzeigt.
Diese Zahl beschreibt die logische Größe der App. Die App enthält tatsächlich Dateien im Umfang von ungefähr 320 MB. Würden diese Dateien ohne Klonen an einen anderen Ort kopiert, müsste ungefähr diese Datenmenge geschrieben werden.
Geändert hat sich die physische Größe: die Anzahl der eigenständigen Blöcke, die diese Dateien tatsächlich auf dem Datenträger belegen.
Wenn zwei Apps dasselbe 300 MB große Framework enthalten und APFS ihnen erlaubt, dieselben zugrunde liegenden Blöcke gemeinsam zu nutzen, kann jede App logisch 300 MB enthalten, während die zweite fast keinen zusätzlichen physischen Speicherplatz belegt.
Die Finder-Ansicht „Informationen“ und Werkzeuge wie du machen diese gemeinsame Nutzung nicht unbedingt sichtbar. Die Einsparung lässt sich am besten am tatsächlich verbleibenden freien Speicherplatz erkennen.
Wir haben das mit sieben echten Apps überprüft: Discord, Facebook, Instagram, Messenger, TikTok und zwei benutzerdefinierten Apps. Auf diese Weise erstellt, benötigten sie gegenüber dem bisherigen Build-Prozess etwa 1,9 GB weniger Speicherplatz.
Außerdem haben wir die zugrunde liegenden physischen Positionen auf dem Datenträger geprüft und bestätigt, dass die Apps ihre gemeinsamen Electron- und Photon-Daten auf Blockebene teilten. Eine mit dem bisherigen Build-Prozess erstellte App teilte keinen dieser Blöcke – ein hilfreicher Vergleich.
Das Ergebnis
Wer mit der WebCatalog-Desktop-App nur eine einzige App erstellt, wird kaum einen Unterschied bemerken. Die erste App benötigt sogar etwas mehr Speicherplatz als zuvor, weil die Desktop-App zusätzlich die Photon-Basis-App für spätere Klone vorhält.
Danach wächst der Vorteil schnell.
Vorher
1 App ~320 MB
10 Apps >3 GB
Mit APFS-Klonen:
Nachher
1 App ~340 MB
10 Apps ~360 MB
Sobald die erste Basis erstellt ist, belegt jede zusätzliche App in der Regel nur 1–2 MB zusätzlichen physischen Speicherplatz.
Wichtig ist, dass wir dies ohne eine Abhängigkeit von einer gemeinsam genutzten Runtime erreicht haben. Jede App bleibt eine normale, eigenständige macOS-App, während APFS die identischen Electron- und Photon-Daten nur einmal speichert. Bei etwa zehn installierten Apps kann das den tatsächlichen Speicherverbrauch auf weniger als ein Achtel reduzieren.
Diese Neuerung ist in der neuesten Version der WebCatalog-Desktop-App für macOS verfügbar. Sie müssen nichts aktivieren: Neue Apps nutzen sie automatisch, und bereits vorhandene Apps benötigen nach ihrer nächsten Aktualisierung weniger Speicherplatz.