
Преди всяко приложение, което създавахте с настолното приложение WebCatalog, заемаше около 320 MB дисково пространство в macOS. Това не е необичайно за приложение, базирано на Electron, но WebCatalog е предназначен за хора, които използват много уеб приложения като настолни приложения. Инсталирането на десет такива приложения можеше да заеме над 3 GB, въпреки че по-голямата част от данните в тях бяха напълно еднакви.
Наскоро променихме начина, по който настолното приложение WebCatalog създава приложения в macOS. Първото приложение вече използва около 340 MB физическо дисково пространство, включително подготвено копие на приложния енджин, което се съхранява локално. След това всяко следващо приложение обикновено добавя само 1–2 MB реално използвано дисково пространство. При нашите тестове десет приложения преминаха от над 3 GB към около 360 MB.
Постигнахме това с функция, която вече е вградена в macOS: клониране в APFS. Интересното не беше просто клонирането на файлове, а как то да работи, като всяко приложение остане независимо, с валиден кодов подпис и функционално равностойно на приложение, създадено по стария начин.
Защо всяко приложение заемаше още 320 MB
Настолното приложение WebCatalog превръща уебсайтове в самостоятелни настолни приложения. Всяко създадено приложение работи с Photon — нашия приложен енджин, изграден върху Electron. В macOS всяко от тях е обикновен пакет .app, който съдържа Electron (Chromium и Node.js), Photon и файловете за конкретното приложение.
Да вземем например две приложения — Slack и Discord. Имената, иконите, идентификаторите на пакетите и конфигурациите им са различни, но голямата рамка Electron и по-голямата част от Photon са идентични.
Досега всяко приложение се създаваше като напълно отделно копие.
Slack.app
Electron
Photon
Файлове, специфични за Slack
Discord.app
Electron
Photon
Файлове, специфични за Discord
Рамката Electron и Photon можеше да са идентични до последния байт и в двете приложения, но macOS въпреки това съхраняваше още едно физическо копие за всяко приложение. Така десет приложения означаваха приблизително десет копия на почти един и същ пакет с размер 320 MB.
Тази архитектура имаше едно свойство, което искахме да запазим: всяко приложение беше напълно независимо. Изтриването на едно не можеше да засегне друго, а инсталираните приложения не зависеха от това настолното приложение WebCatalog да продължи да съществува в системата.
Искахме да премахнем ненужното дублиране, без да губим тази независимост.
APFS вече предлагаше необходимия механизъм
Съвременните Mac компютри използват файловата система APFS на Apple. APFS поддържа клониране — механизъм за копиране при запис, който позволява два независими файла да споделят едни и същи физически данни, докато единият не се промени.
Представете си, че копирате файл с размер 300 MB. При обикновено копиране macOS записва още 300 MB на диска, така че двата файла заемат общо около 600 MB.
При клониране в APFS новият файл изглежда и се държи като пълен файл от 300 MB, но първоначално сочи към същите физически блокове като оригинала.
Файл A ─────┐
├── споделени физически блокове
Файл B ─────┘
Ако по-късно част от файл B се промени, APFS записва нови блокове само за променените данни. Всичко, което остава еднакво, може да продължи да споделя първоначалните блокове.
Файл A ───────── споделени блокове
Файл B ───────── споделени блокове
└──────── променени блокове
От гледна точка на приложението файл A и файл B са отделни файлове. От гледна точка на SSD устройството идентичните данни трябва да се съхраняват само веднъж.
Това е почти точно поведението, от което се нуждаехме.
Създаване на приложения от обща основа
Настолното приложение WebCatalog вече подготвя основа за всяка комбинация от версии на Electron и Photon. Базовото приложение Photon съдържа частите, които са еднакви във всички приложения, включително Electron, Photon, структурата на рамката и общите подписи.
Когато настолното приложение създава друго приложение със същите версии, то вече не извлича и изгражда от нулата още едно пълно копие. Вместо това създава APFS клонинг на базовото приложение Photon и променя само частите, които трябва да се различават.
Те включват името на приложението, иконата, идентификатора на пакета, настройките, имената на изпълнимите файлове, идентификатора на компилацията и други метаданни, специфични за приложението.
Тъй като по-голямата част от пакета остава непроменена, APFS продължава да споделя физическите блокове, съдържащи Electron и Photon. Ново пространство заема само сравнително малкото количество данни, специфични за приложението.
Защо просто не споделяме Electron?
По-очевиден подход би бил да инсталираме Electron веднъж и всяко приложение да сочи към него.
Обмислихме това, но то би променило из основи модела на надеждност. Ако споделената инсталация на Electron изчезне, всяко приложение, което зависи от нея, може да спре да работи. Програма за почистване на кеша би могла да я премахне, деинсталирането на настолното приложение WebCatalog също би могло да я премахне, а преместването или възстановяването на отделно приложение би станало по-сложно.
Освен това би се наложило да следим кои инсталирани приложения зависят от кои версии на средата за изпълнение, за да можем безопасно да премахваме старите версии.
Подписването на код в macOS създава и друг проблем. Строгата проверка на подписа отхвърля символни връзки, които сочат извън пакета на приложението.
APFS клонингите ни дават ползите от споделянето, без да въвеждат такава зависимост. Инсталираните приложения могат да споделят физически дискови блокове, като същевременно остават пълни, независими пакети.
Изтриването на базовото приложение Photon не поврежда приложенията, създадени от него. Изтриването на едно инсталирано приложение не засяга друго. Файловата система управлява споделянето, докато архитектурата на приложенията остава независима.
Подписването на кода едва не заличи спестеното пространство
Клонирането на приложението беше само част от задачата. Приложенията за macOS трябва също да имат кодов подпис.
При стария ни процес след персонализирането подписвахме наново целия пакет на всяко приложение, включително вложените му компоненти. При обикновено копие това не е проблем. При съхранение с копиране при запис обаче промяната на голям двоичен файл може да накара APFS да задели нови физически блокове за него.
По време на разработката установихме, че повторното подписване на рамката Electron добавя приблизително 194 MB физически заето пространство на приложение. Бяхме избегнали копирането на Electron при създаването на приложението, само за да дублираме голяма част от него отново при подписването.
Затова трябваше да променим и процеса на подписване.
Голямата обща рамка вече се подписва като част от базовото приложение Photon. Когато настолното приложение клонира тази основа, избягваме да променяме големите споделени двоични файлове и подписваме наново само по-малките части, които действително трябва да се различават между приложенията.
След като приложението е готово, извършваме строга проверка на подписа на завършения пакет, за да се уверим, че всичко остава валидно.
Така запазваме едновременно гаранциите на macOS за подписването на кода и спестяването на пространство чрез APFS.
Когато заявката към Node.js за клониране всъщност не клонираше
Появи се и друг неочакван проблем: самото клониране.
Node.js предоставя флагове за копиране, предназначени да заявят поведение с копиране при запис, включително COPYFILE_FICLONE и COPYFILE_FICLONE_FORCE. На теория изглеждаха като точно необходимите ни API интерфейси.
При тестовете ни с Node.js 24 копирахме едно и също приложение Electron с размер 288 MB по няколко начина и измерихме колко допълнително физическо дисково пространство заема всяка операция:
fs.cpSync +288 MB
fs.cpSync + COPYFILE_FICLONE +288 MB
fs.cpSync + COPYFILE_FICLONE_FORCE +288 MB
/bin/cp -c -R ~0 MB
И двата режима за клониране на Node.js създадоха пълно физическо копие в нашия тест, а дори вариантът FORCE не съобщи за грешка, когато очакваното клониране не се случи.
Вградената в macOS операция cp -c се държеше според очакванията, затова настолното приложение WebCatalog използва директно този механизъм.
Това беше и полезно напомняне, че оптимизациите на файловата система трябва да се проверяват чрез измерване на самата файловата система. Извикването на API, което заявява копиране при запис, не означава непременно, че получените файлове действително споделят физическо дисково пространство.
Как направихме оптимизацията безопасна при неуспех
Спестяването на дисково пространство е оптимизация. Успешното инсталиране на приложение не е.
Проектирахме новия процес така, че неуспешното клониране да не пречи на инсталирането. Ако подготовката на базовото приложение Photon е неуспешна, ако кешираната основа е повредена, ако клонирането се провали или ако проверката на подписа не мине, настолното приложение се връща към предишния процес на създаване с пълно извличане и пълно подписване.
Следователно в най-лошия случай използваното дисково пространство е същото като преди, а инсталирането не се проваля.
Трябваше да отчетем и едновременната работа. Базовото приложение Photon първо се подготвя във временна директория и се премества на окончателното си място едва когато е завършено. Така две приложения, които се създават едновременно, не могат случайно да използват недовършена основа.
По-старите основи също могат да бъдат премахнати безопасно. Инсталираният APFS клонинг не зависи от това оригиналната основа да продължи да съществува. Ако тя бъде изтрита, клонингът запазва физическите блокове, които все още използва.
Това работи на APFS — файловата система по подразбиране на всеки съвременен Mac. Ако приложенията ви са на диск, който не поддържа клониране, например външно устройство с HFS+ или exFAT, настолното приложение WebCatalog преминава към обикновено копиране. Така приложенията работят както преди, но не пестят пространство. Засега няма промяна за Windows и Linux.
Логическият и физическият размер не са едно и също
Една леко объркваща последица е, че Finder все още може да показва размер около 320 MB за всяко приложение.
Това число е логическият размер на приложението. То наистина съдържа файлове с общ размер около 320 MB и ако тези файлове бъдат копирани някъде без клониране, приблизително толкова данни ще трябва да се запишат.
Променил се е физическият размер — броят на уникалните блокове, които тези файлове действително заемат на диска.
Ако две приложения съдържат една и съща рамка с размер 300 MB и APFS им позволява да споделят едни и същи физически блокове, всяко приложение може логически да съдържа 300 MB, докато второто добавя почти никакви нови физически данни.
Изгледът Get Info във Finder и инструменти като du невинаги показват това споделяне. Спестеното пространство се забелязва най-добре по действително оставащото свободно място на диска.
Проверихме това със седем реални приложения: Discord, Facebook, Instagram, Messenger, TikTok и две персонализирани приложения. Създадени по този начин, те спестиха около 1,9 GB дисково пространство в сравнение със стария процес.
Проверихме и физическите отмествания на данните върху диска и потвърдихме, че приложенията споделят общите си данни от Electron и Photon на ниво блокове. Приложение, създадено по стария начин, не споделяше нито един от тези блокове, което ни даде полезна основа за сравнение.
Резултатът
За човек, който създава само едно приложение с настолното приложение WebCatalog, разликата е малка. Първото приложение всъщност използва малко повече дисково пространство от преди, защото настолното приложение съхранява и базовото приложение Photon, от което създава следващите клонинги.
След това ползата нараства бързо.
Преди
1 приложение ~320 MB
10 приложения >3 GB
С клониране в APFS:
Сега
1 приложение ~340 MB
10 приложения ~360 MB
След създаването на първоначалната основа всяко допълнително приложение обикновено добавя само 1–2 MB физически заето дисково пространство.
Важното е, че постигнахме това, без да въвеждаме зависимост от споделена среда за изпълнение. Всяко приложение остава нормално, самостоятелно приложение за macOS, а APFS съхранява идентичните данни от Electron и Photon само веднъж. При около десет инсталирани приложения това може да намали действително използваното дисково пространство над осем пъти.
Това е налично в най-новата версия на настолното приложение WebCatalog за macOS. Не е нужно да включвате нищо: новите приложения го използват автоматично, а приложенията, които вече имате, ще започнат да заемат по-малко място при следващото си обновяване.