Files
FreeCrawl-SEO-Tool/docs/macos-apple-silicon-performance.md
T

12 KiB
Raw Blame History

macOS / Apple Silicon Performans Optimizasyonu — Araştırma Raporu

Tarih: 2026-07-21 Tetikleyici: MacBook Air M2 (Mac14,2), macOS 26.1 (25B78) üzerinde 40 dakikalık crawl sonrası post-crawl fazında main process V8 heap OOM crash'i (hata.txt — EXC_BREAKPOINT / brk 0, LowMemoryNotification → FatalProcessOutOfMemory, node::sqlite::StatementSync::All içinde). Kapsam: FreeCrawl SEO'nun düşük donanımlı Apple Silicon Mac'lerde (8–16 GB unified memory, fansız MacBook Air) darboğazsız çalışması.

Kanıt durumu: 26 kaynak tarandı, 125 iddia çıkarıldı. Doğrulama fazının bir kısmı oturum limitine takıldığından 25 iddianın 6'sı 3-0 çapraz doğrulamadan geçti, 3'ü çürütüldü (rapora alınmadı), kalanlar kaynak alıntılı ancak tek kaynaklı — ⚠️ ile işaretli.


1. En kritik bulgu: Crash'in tavanı RAM değil, Electron'un 4 GB V8 kafesi ✅

hata.txt'deki çökme 8 GB'lık Air'de değil, 64 GB'lık Mac Studio'da da aynen yaşanırdı.

  • Electron 14'ten beri pointer compression etkin: V8 heap'i process başına maksimum 4 GB ile sınırlı — makinedeki fiziksel RAM'den bağımsız. (electronjs.org/blog/v8-memory-cage) ✅ 3-0
  • Electron 21'den beri sandboxed pointers de etkin; crash raporundaki Memory Tag 255 = 1.3 TB bu sandbox'ın sanal adres rezervasyonu — gerçek RAM tüketimi değil, zararsız. (v8.dev/blog/sandbox) ✅ 3-0 (rezervasyon boyutu ⚠️)
  • ⚠️ --max-old-space-size işe yaramaz: pointer-compression kafesi flag'i ezer; app.commandLine.appendSwitch('js-flags', ...) main process için no-op (isolate zaten yaratılmış). Maintainer'lara göre runtime workaround yok; tek çıkış özel Electron derlemesi. (electron#31330)
  • ⚠️ En sinsi detay: Electron'da ArrayBuffer/Buffer verisi de Node.js'in aksine kafes içinde tutuluyor (maintainer nornagon'un tespiti). "Büyük veriyi Buffer'a al, heap rahatlasın" taktiği Electron'da çalışmaz.
  • ⚠️ Mimari sonuç: worker_threads aynı process'te yaşadığından main isolate + reader worker'lar + writer worker'lar tek 4 GB kafesi paylaşıyor. Worker havuzları CPU paralelliği sağlıyor ama bellek tavanını genişletmiyor; worker→main postMessage structured clone anlık olarak veriyi ikiye katlıyor.

Windows/Intel kafasından fark: "Page file büyür, idare eder" varsayımı Electron'da her platformda geçersiz — kafes 4 GB. Düşük RAM'li Mac bunu sadece daha erken hissettirir (compression/swap baskısı kafes OOM'undan önce başlar).

Gerçek çözüm — veri hacmini heap'e hiç sokmamak:

  • StatementSync.prototype.iterate() — tam bu amaçla eklendi, Node v22.13.0'a backport edildi, Electron 41.2.2'nin Node 22.x'inde mevcut. (nodejs.org/api/sqlite.html, nodejs/node#54213)
  • Gerçekten büyük heap gerektiren işler için maintainer önerisi: işi Electron dışına taşımak. Pratik karşılığı utilityProcess — ayrı OS process = ayrı 4 GB kafes + ayrı crash domain'i.

2. procRole: Background — App Nap + E-core hapsi ✅

Crash raporundaki Role: Background tesadüf değil; crawl arka planda çalışıyordu:

  • App Nap koşulları: ön planda değil + görünür içerik güncellenmiyor + ses yok + assertion alınmamış → aday. ✅ 3-0
  • App Nap üç mekanizmayla vurur: ✅ 3-0
    1. Priority reduction → daha az CPU payı
    2. Timer throttling → setInterval/setTimeout tabanlı PQueue iş dağıtımı seyrekleşir
    3. I/O throttling → SQLite disk yazışları kısılır
  • Resmi çıkış: NSProcessInfo activity assertion → sistem App Nap'e sokmaz. ✅ 3-0 Electron karşılığı: powerSaveBlocker.start('prevent-app-suspension'). (Apple App Nap docs)
  • ⚠️ Ödünleşim: prevent-app-suspension sistem uykusunu da engeller; "sadece App Nap'i engelle" granülerliği Electron'da yok ('not planned' kapatıldı). Crawl aktifken aç, bitince stop().
  • ⚠️ E-core hapsi (Apple Silicon'a özgü): QoS background (9) thread'ler yalnızca E-core'larda çalışır; P-core'lar boşta olsa bile terfi yok, dışarıdan terfi de mümkün değil. (eclecticlight.co)
  • ⚠️ App Nap'e girmiş uygulamalar bellek baskısında compression/swap için öncelikli hedef.

3. Fansız Air'de sustained load — termal adaptasyon ⚠️

  • macOS termal durum API'si: nominal / fair / serious / critical. Apple önerisi: fair'de proaktif azalt, serious'ta CPU+I/O düşür, critical'de minimuma in. (Apple thermal docs)
  • Native modül gerekmez: Electron 41.2.2'de powerMonitor.getCurrentThermalState() ve 'thermal-state-change' event'i mevcut (electron.d.ts'ten doğrulandı).
  • Uyarı: desteklenmeyen sistemler her zaman nominal döndürür → tek sinyal olarak kullanma; mevcut adaptive-throttle (yanıt süresi bazlı) ile birleştir.
  • Önerilen merdiven: fair → concurrency %75, serious → %50, critical → minimum.

4. SQLite × APFS — Windows'tan gerçekten farklı davranan katman ⚠️

  • macOS'ta fsync() dayanıklılık garantisi vermez — güç kesintisi garantisi için F_FULLFSYNC gerekir; o da çok pahalı. Apple'ın sistem SQLite'ı bile fullfsync=on'u sessizce F_BARRIERFSYNC'e çevirir. (mjtsai.com, avi.im/blag/2025/sqlite-fsync)
  • Crawl projesi için doğru nokta: WAL + synchronous=NORMAL — corruption-safe, fsync yalnızca checkpoint'te; kaybedilebilecek şey son commit'ler (yeniden fetch edilebilir veri). fullfsync açma.
  • WAL sınırsız büyüyebilir: 40+ dk sürekli yazan crawl'da wal_autocheckpoint + faz aralarında PRAGMA wal_checkpoint(TRUNCATE). (phiresky)
  • mmap_size (256 MB–1 GB): read syscall'larını page-cache mapping'e çevirir; yalnızca sanal bellek rezerve eder.
  • Apple Silicon 16 KB VM sayfası kullanır — 1 MB'lık TEXT blob'lar için page_size=8192/16384 düşünülebilir (yeni DB'lerde; mevcutlara VACUUM gerekir).
  • Sorgu hijyeni: body kolonunu list/aggregate sorgularından dışla; Google eşiği "4 KB'ın birkaç katı üstü dosyaya" — 1 MB gövdeler için uzun vadede dosya + path kolonu meşru seçenek.

5. macOS bellek yönetimi — düşük RAM'li Mac'te ne olur? ⚠️

  • Kademeli davranış: önce memory compression (10.9+), sonra swap; process kill son çare ve önce düşük öncelik bandı. Jetsam'in masaüstü agresifliği konusunda kaynaklar çelişkili (iki karşıt iddia da çürütüldü) — net olan: bizim crash sistem kill'i değil, V8'in 4 GB tavanına çarpması (LowMemoryNotification → abort zinciri kanıtı).
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE (10.9+) native baskı bildirimi verir; native köprüsüz pratik alternatif: büyük materialization öncesi v8.getHeapStatistics() headroom kontrolü.
  • Unified memory'de RAM aynı zamanda VRAM: 8 GB Air'de efektif bellek nominalden az.

6. macOS 26 (Tahoe) — bilinen Electron sorunları ⚠️

Sorun Durum FreeCrawl'a etkisi
_cornerMask private-API override → WindowServer GPU spike, sistem geneli kasma Electron 36+'ya backport (PR #48376, 2025-09-26) ile düzeltildi Yok — 41.2.2 düzeltmeyi içerir (theregister, #48311)
MAS/App Sandbox + arm64 + macOS 26'da başlangıçta EXC_BREAKPOINT (MAP_JIT sıkılaşması hipotezi) Açık; Apple DTS "JIT rehberi değişmedi" diyor, maintainer onayı yok App Store dağıtımı planlanırsa kritik; sandbox'sız DMG'de üremiyor (#51351, Apple forums)
26.2'de Electron uygulamalarının crash log'suz SIGKILL'lenmesi Tek kullanıcı raporu, 'not planned', doğrulanmamış İzlenmeli (#49261)
AutoFill (NSAutoFillHeuristicController) scroll yavaşlığı 26.0.1'de kısmen düzeltildi Düşük

Teşhis ipucu: Bu regresyon sınıfının istisna imzası bizimkiyle aynı (EXC_BREAKPOINT / brk 0, V8 içinde). Ayrım: OOM'da stack'te LowMemoryNotification + node::sqlite frame'leri var ve çökme dakikalar sonra; JIT regresyonunda çökme başlangıçta ve JIT/compiler thread'inde.


7. Eylem planı ve durum

# Öncelik İş Durum
1 P0 recomputeBoilerplateCoverage: .all(sampleSize) → 64'lük keyset batch + SUBSTR(body,1,200000); dbCall ile writer worker'a taşındı (tepe bellek ~2–4 GB → ~25 MB) ✅ 2026-07-21
2 P0 Diğer toplu url_sources.body okuyucuları tarandı — seoTextCorpus body'yi zaten dışlıyor, duplicate clustering yalnızca simhash okuyor; başka toplu okuyucu yok ✅ 2026-07-21
3 P0 Crawl sırasında powerSaveBlocker('prevent-app-suspension') — ref-count'lu, done + yeni stopped event'iyle bırakılıyor ✅ 2026-07-21
4 P1 callReaderOrFallback: ağır metotlarda (HEAVY_METHODS) main-thread fallback kaldırıldı — rejection döner, sidebar eski sayıları korur ve sonraki tick'te yeniden dener ✅ 2026-07-21
5 P1 Termal-adaptif concurrency: Crawler.setThrottleScale (dış tavan, lag-adaptasyonuyla kompoze) + macOS'ta powerMonitor thermal-state-change → nominal 1 / fair 0.75 / serious 0.5 / critical 0.25; crawl başlangıcında mevcut durum da uygulanır ✅ 2026-07-21
6 P1 SQLite pragma seti — inceleme sonucu zaten mevcuttu (WAL, synchronous=NORMAL, mmap_size=30GB, wal_autocheckpoint=2000, busy_timeout, temp_store=MEMORY); eksik olan tek parça eklendi: post-crawl wal_checkpoint(TRUNCATE) (writer worker üzerinden) ✅ 2026-07-21
7 P2 DB worker'ları worker_threads → utilityProcess (ayrı 4 GB kafes + crash izolasyonu) — bilinçli ertelendi (2026-07-21): crawl yazma yolunu değiştiren en riskli iş, gerçek crawl smoke-testleriyle doğrulanabileceği ayrı bir oturuma bırakıldı; watchdog SAB'ı cross-process çalışmadığından heartbeat'lerin mesaj tabanlıya çevrilmesi de gerekecek ⏸
8 P2 Heap headroom guard: ensureHeapHeadroom (core, v8.getHeapStatistics tabanlı, 256 MB rezerv) — XLSX export satır biriktirme döngüsü (50K'da bir) + workbook build öncesi + Log Analyzer export'una uygulandı; OOM abort yerine anlaşılır "use CSV" hatası ✅ 2026-07-21
9 P2 Sorgu/export hijyeni: (a) iterateUrlsByCategory + iterateIndexableUrls sahte streamer'dı (içeride .all()) — keyset-paginated gerçek streaming'e çevrildi (1M-URL CSV export tepe belleği ~yüzlerce MB → sabit); (b) queryUrls'a server-side limit clamp (≤10K); (c) SELECT * bilinçli korundu — urls'ta sınırsız kolon yok, mapping tüm kolonları tüketiyor, 187 kolonluk el listesi drift riski; (d) bonus: proje şifreleme/çözme tam-dosya-Buffer'dan 8 MB streaming'e çevrildi (format aynı; 9 senaryoluk round-trip testiyle doğrulandı) ✅ 2026-07-21
10 P2 Uzun vade: url_sources.body'yi DB dışına (dosya + path) taşıma — şema migrasyonu + tüm okuma/yazma yollarını değiştiren büyük iş; P0–P2 sonrası acil bellek baskısı kalmadığından ihtiyaç doğana kadar ertelendi ⏸