
เดิมทีแต่ละแอปที่คุณสร้างด้วยแอป WebCatalog บนเดสก์ท็อปจะใช้พื้นที่ดิสก์บน macOS ประมาณ 320 MB ซึ่งไม่ใช่เรื่องแปลกสำหรับแอปที่ใช้ Electron แต่ WebCatalog ออกแบบมาสำหรับผู้ที่ใช้เว็บแอปหลายตัวในรูปแบบแอปบนเดสก์ท็อป หากติดตั้งสิบแอป แอปเหล่านั้นอาจใช้พื้นที่มากกว่า 3 GB ทั้งที่ข้อมูลส่วนใหญ่ภายในแอปเหมือนกันทุกประการ
เมื่อไม่นานมานี้ เราเปลี่ยนวิธีที่แอป WebCatalog บนเดสก์ท็อปใช้สร้างแอปบน macOS ตอนนี้แอปแรกใช้พื้นที่ดิสก์จริงประมาณ 340 MB ซึ่งรวมสำเนาของเอนจินแอปที่เตรียมและเก็บไว้ในเครื่องแล้ว หลังจากนั้น โดยทั่วไปแอปแต่ละตัวที่เพิ่มเข้ามาจะใช้พื้นที่ดิสก์จริงเพิ่มเพียง 1–2 MB ในการทดสอบของเรา แอปสิบตัวลดการใช้พื้นที่จากมากกว่า 3 GB เหลือประมาณ 360 MB
เราทำเช่นนี้โดยใช้คุณสมบัติที่มีอยู่แล้วใน macOS นั่นคือ APFS clones ส่วนที่น่าสนใจไม่ใช่แค่การโคลนไฟล์ แต่เป็นการทำให้การโคลนใช้งานได้โดยที่แต่ละแอปยังเป็นอิสระจากกัน มีลายเซ็นโค้ดที่ถูกต้อง และทำงานได้เทียบเท่ากับแอปที่สร้างด้วยกระบวนการเดิม
ทำไมแต่ละแอปจึงใช้พื้นที่เพิ่มอีก 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 ซึ่งรองรับ การโคลน กลไกแบบคัดลอกเมื่อมีการเขียน (copy-on-write) ที่ทำให้ไฟล์อิสระสองไฟล์ใช้ข้อมูลจริงชุดเดียวกันร่วมกันได้ จนกว่าไฟล์ใดไฟล์หนึ่งจะเปลี่ยนแปลง
ลองนึกภาพการคัดลอกไฟล์ขนาด 300 MB หากคัดลอกตามปกติ macOS จะเขียนข้อมูลอีก 300 MB ลงดิสก์ ทำให้ไฟล์ทั้งสองใช้พื้นที่รวมประมาณ 600 MB
แต่ถ้าใช้ APFS clone ไฟล์ใหม่จะยังดูเหมือนและทำงานเหมือนไฟล์ขนาด 300 MB ที่ครบถ้วน ทว่าในตอนแรกจะอ้างอิงบล็อกข้อมูลจริงชุดเดียวกับไฟล์ต้นฉบับ
ไฟล์ A ─────┐
├── บล็อกข้อมูลจริงที่ใช้ร่วมกัน
ไฟล์ B ─────┘
หากส่วนหนึ่งของไฟล์ B เปลี่ยนไปในภายหลัง APFS จะเขียนบล็อกใหม่เฉพาะข้อมูลที่เปลี่ยน ส่วนที่ยังเหมือนเดิมทั้งหมดสามารถใช้บล็อกต้นฉบับร่วมกันต่อไปได้
ไฟล์ A ───────── บล็อกที่ใช้ร่วมกัน
ไฟล์ B ───────── บล็อกที่ใช้ร่วมกัน
└─────── บล็อกที่เปลี่ยนแปลง
ในมุมมองของแอปพลิเคชัน ไฟล์ A และไฟล์ B เป็นคนละไฟล์กัน แต่ในมุมมองของ SSD ข้อมูลที่เหมือนกันต้องจัดเก็บเพียงครั้งเดียว
นี่แทบจะตรงกับสิ่งที่เราต้องการทุกประการ
สร้างแอปจากฐานร่วมกัน
ตอนนี้แอป WebCatalog บนเดสก์ท็อปเตรียมแอปฐานสำหรับแต่ละคู่เวอร์ชันของ Electron และ Photon แอปฐาน Photon ประกอบด้วยส่วนที่เหมือนกันในทุกแอป รวมถึง Electron, Photon, โครงสร้างเฟรมเวิร์ก และลายเซ็นร่วม
เมื่อแอปเดสก์ท็อปสร้างแอปอีกตัวโดยใช้เวอร์ชันเดียวกัน ก็ไม่จำเป็นต้องแตกไฟล์และสร้างสำเนาทั้งชุดขึ้นใหม่ตั้งแต่ต้น แต่จะสร้าง APFS clone ของแอปฐาน Photon แล้วปรับแต่งเฉพาะส่วนที่ต้องแตกต่างกัน
ส่วนเหล่านั้นได้แก่ ชื่อแอป ไอคอน ตัวระบุบันเดิล การตั้งค่า ชื่อไฟล์ปฏิบัติการ ตัวระบุบิลด์ และข้อมูลเมตาอื่น ๆ ที่เฉพาะเจาะจงกับแอป
เนื่องจากเนื้อหาส่วนใหญ่ของบันเดิลไม่เปลี่ยนแปลง APFS จึงยังคงใช้บล็อกข้อมูลจริงที่เก็บ Electron และ Photon ร่วมกัน มีเพียงข้อมูลเฉพาะของแอปซึ่งมีขนาดค่อนข้างเล็กเท่านั้นที่ใช้พื้นที่ใหม่
ทำไมไม่ใช้ Electron ร่วมกันไปเลย?
แนวทางที่ดูชัดเจนกว่าคือการติดตั้ง Electron เพียงครั้งเดียว แล้วให้ทุกแอปอ้างอิงไปยังชุดที่ติดตั้งนั้น
เราพิจารณาวิธีนี้แล้ว แต่มันจะเปลี่ยนรูปแบบความน่าเชื่อถือของแอปไปโดยพื้นฐาน หาก Electron ที่ใช้ร่วมกันหายไป ทุกแอปที่พึ่งพามันอาจหยุดทำงาน โปรแกรมล้างแคชอาจลบมัน การถอนการติดตั้งแอป WebCatalog บนเดสก์ท็อปอาจลบมัน และการย้ายหรือกู้คืนแอปแต่ละตัวก็จะซับซ้อนขึ้น
นอกจากนี้ยังต้องติดตามด้วยว่าแอปที่ติดตั้งแต่ละตัวพึ่งพารันไทม์เวอร์ชันใด จึงจะล้างเวอร์ชันเก่าทิ้งได้อย่างปลอดภัย
การเซ็นโค้ดของ macOS เป็นอีกปัญหาหนึ่ง การตรวจสอบลายเซ็นแบบเข้มงวดจะปฏิเสธ symlink ที่ชี้ไปภายนอกบันเดิลแอป
APFS clones ทำให้เราได้ประโยชน์จากการใช้พื้นที่ร่วมกันโดยไม่ต้องเพิ่มการพึ่งพาแบบนั้น แอปที่ติดตั้งแล้วสามารถใช้บล็อกข้อมูลจริงบนดิสก์ร่วมกัน ขณะเดียวกันก็ยังเป็นบันเดิลแอปที่ครบถ้วนและเป็นอิสระจากกัน
การลบแอปฐาน Photon ไม่ทำให้แอปที่สร้างจากฐานนั้นเสีย การลบแอปที่ติดตั้งไว้ตัวหนึ่งก็ไม่ส่งผลต่ออีกตัว ระบบไฟล์จัดการเรื่องการใช้พื้นที่ร่วมกัน ขณะที่สถาปัตยกรรมของแอปยังคงเป็นอิสระ
การเซ็นโค้ดเกือบทำให้พื้นที่ที่ประหยัดได้หายไป
การโคลนแอปเป็นเพียงส่วนหนึ่งของปัญหา แอปพลิเคชันบน macOS ยังต้องได้รับการเซ็นโค้ดด้วย
กระบวนการสร้างแบบเดิมของเราเซ็นลายเซ็นใหม่ให้กับบันเดิลแอปทั้งหมด รวมถึงส่วนที่อยู่ภายใน หลังจากปรับแต่งแอปแล้ว สำหรับการคัดลอกทั่วไป วิธีนี้ไม่มีปัญหา แต่เมื่อใช้การจัดเก็บแบบ copy-on-write การแก้ไขไฟล์ไบนารีขนาดใหญ่อาจทำให้ APFS ต้องจัดสรรบล็อกข้อมูลจริงชุดใหม่ให้ไฟล์นั้น
ระหว่างการพัฒนา เราวัดพบว่าการเซ็นเฟรมเวิร์ก Electron ใหม่ทำให้แต่ละแอปใช้พื้นที่จัดเก็บจริงเพิ่มขึ้นประมาณ 194 MB เราหลีกเลี่ยงการคัดลอก Electron ตอนสร้างแอปได้สำเร็จ แต่กลับมาทำข้อมูลส่วนใหญ่ซ้ำอีกครั้งตอนเซ็นลายเซ็น
ดังนั้นกระบวนการเซ็นลายเซ็นจึงต้องเปลี่ยนด้วย
ตอนนี้เฟรมเวิร์กส่วนกลางขนาดใหญ่จะถูกเซ็นเป็นส่วนหนึ่งของแอปฐาน Photon เมื่อแอปเดสก์ท็อปโคลนแอปฐานนั้น เราจะไม่แก้ไขไฟล์ไบนารีขนาดใหญ่ที่ใช้ร่วมกัน และจะเซ็นใหม่เฉพาะส่วนที่มีขนาดเล็กกว่าซึ่งจำเป็นต้องแตกต่างกันระหว่างแอป
เมื่อแอปเสร็จสมบูรณ์ เราจะตรวจสอบลายเซ็นของบันเดิลที่สร้างเสร็จแล้วแบบเข้มงวด เพื่อให้แน่ใจว่าทุกอย่างยังถูกต้อง
วิธีนี้ทำให้เราคงทั้งการรับประกันด้านการเซ็นโค้ดของ macOS และประโยชน์ด้านพื้นที่จัดเก็บจาก APFS ไว้ได้
เมื่อสั่งให้ Node.js โคลน แต่กลับไม่ได้โคลนจริง
ยังมีปัญหาที่ไม่คาดคิดอีกอย่าง นั่นคือขั้นตอนการโคลนเอง
Node.js มีแฟล็กสำหรับการคัดลอกที่ออกแบบมาเพื่อขอให้ใช้การทำงานแบบ copy-on-write รวมถึง 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 ก็ไม่รายงานข้อผิดพลาดเมื่อการโคลนตามที่คาดหวังไม่เกิดขึ้น
คำสั่ง cp -c ที่มีมากับ macOS ทำงานตามที่คาดไว้ ดังนั้นแอป WebCatalog บนเดสก์ท็อปจึงใช้กลไกนี้โดยตรง
เรื่องนี้ยังเตือนเราอีกว่า ควรตรวจสอบผลของการเพิ่มประสิทธิภาพระบบไฟล์ด้วยการวัดผลจากระบบไฟล์จริง การเรียก API ที่ร้องขอให้ใช้ copy-on-write ไม่ได้หมายความว่าไฟล์ที่ได้จะใช้พื้นที่จัดเก็บจริงร่วมกันเสมอไป
ทำให้การเพิ่มประสิทธิภาพล้มเหลวได้อย่างปลอดภัย
การประหยัดพื้นที่ดิสก์เป็นการเพิ่มประสิทธิภาพ แต่การติดตั้งแอปให้สำเร็จเป็นสิ่งที่ต้องเกิดขึ้น
เราออกแบบกระบวนการใหม่ให้การโคลนล้มเหลวได้โดยไม่ทำให้การติดตั้งแอปล้มเหลว หากการเตรียมแอปฐาน Photon ไม่สำเร็จ หากฐานที่แคชไว้เสียหาย หากการโคลนล้มเหลว หรือหากการตรวจสอบลายเซ็นไม่ผ่าน แอปเดสก์ท็อปจะกลับไปใช้กระบวนการสร้างแบบเดิม ซึ่งแตกไฟล์ทั้งหมดและเซ็นลายเซ็นทั้งหมด
ดังนั้นกรณีที่แย่ที่สุดคือใช้พื้นที่ดิสก์เท่าเดิม ไม่ใช่ติดตั้งไม่สำเร็จ
เรายังต้องคำนึงถึงการทำงานพร้อมกันด้วย แอปฐาน Photon จะถูกเตรียมในไดเรกทอรีชั่วคราวก่อน และจะถูกย้ายไปยังตำแหน่งสุดท้ายก็ต่อเมื่อเตรียมเสร็จสมบูรณ์แล้ว วิธีนี้ทำให้การสร้างแอปสองตัวพร้อมกันไม่เผลอไปใช้แอปฐานที่ยังสร้างไม่เสร็จ
แอปฐานรุ่นเก่าก็ลบได้อย่างปลอดภัยเช่นกัน APFS clone ที่ติดตั้งแล้วไม่ต้องพึ่งพาการมีอยู่ของแอปฐานต้นฉบับ หากลบแอปฐาน โคลนจะยังคงมีบล็อกข้อมูลจริงที่ใช้งานอยู่
วิธีนี้ใช้ได้บน 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 โดยไม่ต้องเปิดใช้งานอะไรเพิ่มเติม แอปใหม่จะใช้คุณสมบัตินี้โดยอัตโนมัติ และแอปที่คุณมีอยู่แล้วจะใช้พื้นที่ลดลงเมื่อได้รับการอัปเดตครั้งถัดไป