← All posts

ลบไฟล์ไป 44 GB แต่พื้นที่ไม่เพิ่มสักเมกฯ — เรื่องของ APFS Snapshot


Mac mini เครื่องใช้งานของผมขึ้นเตือนพื้นที่ใกล้เต็มมาสักพัก ลากยาวมาจนทนไม่ไหว เปิดดูจริงจังก็พบว่าใช้ไป 151 GB จาก 228 GB เหลือที่ว่างอยู่ 56 GB คิดเป็น 74% ซึ่งสำหรับเครื่อง dev ที่ต้อง npm install วันละหลายรอบ มันคือโซนอันตรายแล้ว

เรื่องนี้เลยควรจะเป็นเรื่องง่าย — ลบ cache ลบของไม่ใช้ จบ

แต่มันไม่จบ เพราะพอลบเสร็จจริงๆ พื้นที่ว่าง ไม่ขยับเลยแม้แต่เมกฯ เดียว

ตรวจก่อน อย่าเพิ่งลบ

บทเรียนแรกที่ผมยึดมาตลอดคือ อย่าลบอะไรที่ยังไม่รู้ว่ามันคืออะไร ผมเลยเขียน script อ่านอย่างเดียว(ไม่ลบอะไรทั้งนั้น) ไล่ du ทั้ง home, /Applications, ~/Library แล้วเขียนผลลัพธ์ลงไฟล์

ผลที่ได้คือ

~/Library         50 GB
/Applications     23 GB
dev caches        ~21 GB
~/Work            7.8 GB

พอเจาะลงไปอีกชั้น ของที่ทำให้ผมอึ้งคือ

  • Notion กิน 7.9 GB ทั้งที่ตัว app เองแค่ 282 MB ที่เหลือคือ Partitions/notion ซึ่งเป็น cache ของ Electron ล้วนๆ
  • Antigravity รวมแล้ว ~7.2 GB กระจายอยู่ 4 ที่ — ~/.antigravity, ~/.antigravity-ide, ~/.gemini/antigravity* และตัว app อีก 2 ตัว แต่ละที่ดูแล้วไม่เยอะ พอรวมกันคือมหาศาล
  • .vscode-test ในโปรเจคเดียว กิน 1.7 GB อันนี้คือ test harness ที่ VS Code โหลดมา ลบได้สบายมาก
  • ~/.npm/_npx กิน 1.8 GB เป็นขยะจาก npx ที่ค้างมาเป็นปีๆ
  • pnpm/store/v3 3.4 GB ทั้งที่ตอนนี้ผมใช้ store v11 ไปแล้ว ของเก่าค้างอยู่เฉยๆ
  • puppeteer โหลด Chrome for Testing เก็บไว้ 5 เวอร์ชั่น รวม 2.8 GB

จุดที่อยากเน้นคือ ของอ้วนมันไม่ได้อยู่ที่เดียว เครื่องมือตัวหนึ่งชอบกระจายตัวไปหลายโฟลเดอร์ ดูทีละที่แล้วรู้สึกว่า "ก็ไม่เท่าไหร่นี่" แต่พอรวมกันแล้วมันคือหลาย GB

เรื่องที่ต้องระวัง: วันที่ "เปิดล่าสุด" มันโกหก

ผมลองใช้ mdls -name kMDItemLastUsedDate ไล่ดูว่า app ไหนไม่เคยเปิดเลย เพื่อจะได้ลบทิ้ง

ผลออกมาว่า Google Chrome ไม่เคยถูกเปิด

...ทั้งที่ Chrome มี profile data อยู่ 966 MB

สรุปคือค่า null จาก Spotlight มันแปลว่า "ไม่มีข้อมูล" ไม่ได้แปลว่า "ไม่เคยใช้" ถ้าเชื่อมันแล้วลบตามเลยนี่คือได้ลบ app ที่ใช้อยู่ทุกวันทิ้งแน่นอน

ใช้เป็น ตัวช่วยกรองเบื้องต้น ได้ แต่ต้องมายืนยันด้วยตาอีกทีเสมอ

แบ่งเป็น Tier แล้วค่อยตัดสินใจ

ผมแบ่งของที่จะลบเป็นชั้นๆ ตามความเสี่ยง แทนที่จะลบรวดเดียว

Tierคืออะไรเสี่ยงแค่ไหน
Acache ที่สร้างใหม่เองได้ — npm, pnpm, puppeteer, playwrightลบได้เลย มันกลับมาเอง
Bของที่ต้องโหลดใหม่ — NuGet, SonarLint, Node เวอร์ชั่นเก่าลบได้ แต่ build รอบหน้าจะช้าหน่อย
Cnode_modules ของโปรเจคที่ไม่ได้แตะนานnpm i เอาคืนได้
Dapp ที่ไม่ใช้ + เศษที่มันทิ้งไว้ต้องดูเป็นตัวๆ
Ecache ที่ลบแล้วต้อง login ใหม่อันนี้ต้องยอมรับผลก่อน

ส่วนของที่ ไม่แตะเด็ดขาด คือ LINE 5.4 GB กับ Outlook/Office 3.5 GB ใน Group Containers — สองอันนี้คือแชทกับเมลจริงๆ ไม่ใช่ cache ลบไปแล้วไม่ได้คืน

เขียน script ให้ dry-run เป็น default ต้องใส่ --go เท่านั้นถึงจะลบจริง แล้วใส่ guard กันพลาดไว้ด้วย ไม่ให้แตะ /, $HOME, /Applications, /Library และห้ามมี wildcard เข้า rm เด็ดขาด

แล้วก็ถึงจุดที่งงที่สุด

รัน --go ไป ลบไปเป็นกอง แล้วเช็คผล

BEFORE: /dev/disk3s5  228Gi  151Gi  56Gi  74%  2.5M
AFTER:  /dev/disk3s5  228Gi  151Gi  55Gi  74%  1.7M

ใช้ไป 151 GB เท่าเดิม แล้วที่ว่างลดลงจาก 56 เหลือ 55

คือลบไปเยอะขนาดนั้น แล้วพื้นที่ว่างดันน้อยลงเนี่ยนะ?

ความรู้สึกแรกคือ "script พังแน่ๆ" แต่ก่อนจะไปแก้ script ผมลองมองตัวเลขอีกรอบ แล้วก็ไปสะดุดที่คอลัมน์ที่ปกติไม่เคยมอง

เบาะแสอยู่ที่ iused

iused: 2.5M  →  1.7M

หายไป แปดแสน inode แปลว่าไฟล์มันถูกลบไปจริงๆ ทั้งหมดนั้น script ไม่ได้พัง มันทำงานถูกต้องเป๊ะ

ไฟล์หายไปแล้ว แต่พื้นที่ไม่คืน — แปลว่ามีอะไรบางอย่าง "จอง" block พวกนั้นไว้อยู่

ตัวการคือ Time Machine Local Snapshot

APFS มีพฤติกรรมที่ผมไม่เคยรู้มาก่อน คือ macOS จะแอบสร้าง local snapshot ของ Time Machine เก็บไว้ในดิสก์ตัวเอง(ถึงจะไม่ได้ต่อ external drive ก็ตาม)

พอเราลบไฟล์ที่ snapshot ยัง reference อยู่ block พวกนั้นจะไม่ได้ถูกปล่อยเป็น free ทันที แต่จะกลายเป็นสถานะ purgeable แทน

และที่มันดูน่าหงุดหงิดกว่านั้นคือ พอเราลบไฟล์เยอะๆ snapshot มันจะโตขึ้นเพื่อเก็บของที่เราเพิ่งลบไป — นี่แหละคือสาเหตุที่ที่ว่างผมลดลง 1 GB

APFS purgeable space explained

ภาพด้านบนคือสิ่งที่เกิดขึ้นจริง — ตอนขั้นที่ 2 ไฟล์หายไปแล้ว แต่ block ยังถูก snapshot จองไว้ df เลยยังนับรวมเป็น used เหมือนเดิม

เช็คได้ด้วย

tmutil listlocalsnapshots /

ของผมมีอยู่ 3 ตัว

com.apple.TimeMachine.2026-09-11-194330.local
com.apple.TimeMachine.2026-09-12-193220.local
com.apple.TimeMachine.2026-09-12-201736.local

สองคำสั่งนี้ตอบไม่ตรงกัน

อีกเรื่องที่ทำให้สับสนหนักคือ สองคำสั่งนี้ให้ตัวเลขคนละอย่าง

คำสั่งนับ purgeable ไหม
df -hAvailไม่นับ
diskutil info /Container Free Spaceนับ

ตอนนั้นสองตัวนี้ต่างกันเกือบ 48 GB ซึ่งอธิบายได้เลยว่าทำไม Finder ถึงบอกว่าที่ว่างเยอะ แต่ df ยังบอกว่าเต็ม

ปลดล็อก

sudo tmutil deletelocalsnapshots 2026-09-11-194330
sudo tmutil thinlocalsnapshots / 100000000000 4

ผลลัพธ์

BEFORE: 228Gi  151Gi  56Gi   74%
AFTER:  228Gi  107Gi  100Gi  52%

44 GB กลับมาในพริบตา ทั้งที่ไม่ได้ลบอะไรเพิ่มอีกเลยสักไฟล์

ย้ำอีกที — local snapshot พวกนี้คือ restore point อัตโนมัติที่อยู่ในดิสก์เครื่องเรา ลบแล้วไม่กระทบ Time Machine backup ที่อยู่บน external drive หรือ NAS และ macOS ก็จะสร้างชุดใหม่ให้เองตามรอบอยู่ดี

สรุปบทเรียน

สิ่งที่ผมได้จากรอบนี้

  1. ถ้าลบแล้วพื้นที่ไม่เพิ่ม อย่าเพิ่งด่าตัวเอง ดู iused ก่อน ถ้ามันลดลง แปลว่าลบสำเร็จแล้ว ปัญหาอยู่ที่อื่น
  2. df กับ Finder ไม่ได้โกหก มันแค่นับคนละแบบ รู้ไว้จะได้ไม่งง
  3. สำรวจก่อนลบเสมอ และแยกให้ออกว่าอันไหน cache อันไหน data จริง
  4. อย่าเชื่อวันที่เปิดล่าสุดของ Spotlight มันบอกว่า Chrome ไม่เคยถูกเปิดได้หน้าตาเฉย
  5. ของอ้วนชอบกระจายตัว ดูทีละโฟลเดอร์จะไม่เห็นภาพ ต้องรวมยอดต่อเครื่องมือ

ทำให้ใช้ซ้ำได้

พอเจอปัญหาที่ต้องสืบขนาดนี้ ผมเลยไม่อยากมาเริ่มนับหนึ่งใหม่อีกรอบในอีก 3 เดือน เลยรวบมาเป็น tool เล็กๆ ชื่อ macreclaim เขียนด้วย bash ล้วน ไม่มี dependency อะไรทั้งนั้น

git clone https://github.com/Ariyapong/macreclaim.git
cd macreclaim
./macreclaim scan

มี 4 คำสั่ง — scan สำรวจแบบอ่านอย่างเดียว, drill เจาะดูตัวที่ใหญ่ผิดปกติ, clean ลบแบบแบ่ง tier (dry-run เป็น default), และ release ที่เป็นตัวเอกของเรื่องนี้ คือไปเคลียร์ snapshot ให้พื้นที่มันโผล่มาจริงๆ

จุดที่ตั้งใจออกแบบไว้คือ Tier D กับ E มันว่างเปล่าตั้งแต่แรก ใครจะใช้ต้องไปเขียน config ของตัวเองก่อน เพราะ script ลบไฟล์ที่แถม "รายการที่จะลบ" ของคนอื่นมาด้วยเนี่ย มันคือระเบิดเวลาชัดๆ และไฟล์ config ก็ถูก gitignore ไว้ด้วย

Repo อยู่ที่ github.com/Ariyapong/macreclaim ใครจะหยิบไปใช้ไปแก้ก็ตามสบายเลยครับ MIT


เรื่องนี้ถ้าจะมีอะไรให้จำสักอย่าง ผมว่ามันคือตอนที่เห็น df บอกว่าไม่มีอะไรเกิดขึ้น แล้วเกือบจะเชื่อมันไปแล้ว

ถ้าวันนั้นรีบสรุปว่า "script พัง" แล้วไล่แก้โค้ดที่ไม่ได้ผิดตั้งแต่แรก คงเสียเวลาไปอีกครึ่งวัน — ตัวเลขที่บอกความจริงมันอยู่ตรงนั้นมาตลอด แค่อยู่คนละคอลัมน์กับที่เรากำลังจ้องอยู่เด้อ

j ↑ k ↓