> Gerçek bir production olayından çıkardığım, uçtan uca bir kurtarma ve iyileştirme hikâyesi. Bir Exchange 2016 mailbox veritabanı bir sabah mount olmayı reddetti; sorunun kökü beklenmedik bir yerden çıktı: **NTFS dosya parçalanma limiti**. Aşağıda hem kurtarma adımlarını hem de bir daha yaşamamak için yaptığım kalıcı altyapı düzeltmesini paylaşıyorum.
**Ortam:** Exchange 2016 CU23, Windows Server 2012 R2, sanallaştırılmış (VMware), SSD datastore. Tek Exchange sunucu (DAG yok). Birden fazla mailbox veritabanı, toplam ~1 TB.
> Not: Komutlardaki veritabanı adları, sürücü harfleri ve yollar örnektir; kendi ortamınıza uyarlayın. Yıkıcı komutları (Remove-Item, eseutil /p, disk kaldırma) çalıştırmadan önce mutlaka yedeğinizi doğrulayın.
---
## 1. Belirti
Bir mailbox veritabanı mount olmuyordu. EMS'te `Mount-Database` ve `eseutil /r` (soft recovery) sürekli şu hatayla düşüyordu:
```
Operation terminated with error -1022 (JET_errDiskIO, Disk IO error)
```
İlginç olan: soft recovery **her denemede daha erken ve daha hızlı** başarısız oluyordu (önce ~%99'da 600 sn sonra, sonra ~%37'de 3 sn'de, en son <1 sn'de). Bu, "geçici disk IO problemi" teorisini çürüttü — **tekrarlanabilir, kalıcı bir sorun** vardı.
## 2. Teşhis
Klasik ESE kontrolleri **temiz** çıktı, bu da kafa karıştırıcıydı:
```powershelleseutil /mh "<DB yolu>\DB.edb" # Header: checksum OK, sadece Dirty Shutdown
eseutil /k "<DB yolu>\DB.edb" # 0 bad checksum — fiziksel temiz
eseutil /ml "<log yolu>\E0n" # Tüm loglar OK, hasarlı log yok
Get-PSDrive C # Diskte bol boş alan var
Get-PhysicalDisk | Select FriendlyName, HealthStatus, OperationalStatus # Healthy/OK
```
Diskler sağlıklı, checksum temiz, loglar sağlam, yer bol — ama recovery -1022 veriyor. Cevap **Event Viewer → Application → kaynak "ESE"** loglarındaydı:
```
ESE Event ID 482:
An attempt to write to the file "...\DB.edb" at offset 408692981760 ...
failed with system error 665 (0x00000299):
"The requested operation could not be completed due to a file system limitation".
The write operation will fail with error -1022.
```
**System error 665 = NTFS dosya sistemi limiti.** Bu donanım değil, bir dosya sistemi sınırı. Doğrulamak için Sysinternals **Contig** ile dosyanın parçalanmasına baktım:
```powershell
contig64.exe -a "<DB yolu>\DB.edb"
# Sonuç: ... is in 1,732,384 fragments
```
**1,7 milyon fragment.** İşte kök neden buydu.
## 3. Neden olur? (NTFS extent limiti)
NTFS, bir dosyanın disk üzerindeki parçalarını (extent/fragment) MFT kaydındaki bir öznitelik listesinde tutar. Bir dosya **aşırı parçalandığında** bu liste büyüyemeyeceği bir sınıra dayanır ve NTFS yeni yazma/genişletme isteğini **error 665** ile reddeder. Exchange'in ESE motoru veritabanını büyütmek istediğinde tam da bu olur → -1022.
Parçalanmayı tetikleyen faktörler:
- **4 KB allocation unit** (Exchange için önerilen **64 KB** değil) → aynı boyut için kat kat fazla cluster/fragment.
- Veritabanının zamanla **küçük artışlarla büyümesi** (boş alanın kendisi de parçalıysa).
- Büyük EDB'nin sistem diskiyle aynı, dolu bir volume'de durması.
> Önemli: `contig` ile **yerinde defragmentasyon ÇALIŞMAZ**. Taşıma işlemi de aynı extent limitine çarpar (`Move cluster status: 0xc0000427` = STATUS_FILE_SYSTEM_LIMITATION).
## 4. Kurtarma: Taze kopya yöntemi
Parçalanmayı kırmanın yolu, dosyayı **boş bir alana yeniden kopyalamaktır**. Kopyalama işlemi NTFS'i dosyayı tek seferde, çok daha bütün (contiguous) tahsis etmeye zorlar; fragment sayısı bir anda birkaç parçaya düşer.
```powershell
# 1) EDB'yi yeterli boş alanı olan bir hedefe kopyala (/J = unbuffered, büyük dosya için)
robocopy "<kaynak klasör>" "<hedef klasör>" DB.edb /J /NP /TEE /R:3 /W:5 /LOG:C:\Temp\copy.log
# 2) Parçalanmanın kalktığını doğrula
contig64.exe -a "<hedef klasör>\DB.edb" # -> 1 fragment
# 3) Soft recovery'yi YENİ kopya üzerinde çalıştır
eseutil /r E0n /l "<log klasörü>" /d "<hedef klasör>"
# -> "Operation completed successfully" (artık -1022 yok)
# 4) Temiz kapanışı doğrula
eseutil /mh "<hedef klasör>\DB.edb" | Select-String "State" # State: Clean Shutdown
# 5) Veritabanı yolunu yeni konuma yönlendir (dosyayı TAŞIMADAN, sadece config)
Move-DatabasePath -Identity DB -EdbFilePath "<hedef klasör>\DB.edb" -ConfigurationOnly -Confirm:$false
# 6) Mount et
Mount-Database DB
```
> `-ConfigurationOnly` kritik: dosya zaten yeni konumda olduğu için Exchange'in onu eski konumdan tekrar kopyalamasını (ve parçalanmayı geri getirmesini) engeller.
**Sonuç:** Veritabanı veri kaybı olmadan mount oldu. Yıkıcı `eseutil /p` (hard repair) **hiç gerekmedi** — sorun bozuk veritabanı değil, dosya parçalanmasıydı. Mailbox erişimi `Get-MailboxStatistics -Database DB` ile doğrulandı.
## 5. Kalıcı çözüm: 64 KB allocation unit'li ayrı diske migrasyon
Kurtarma tek seferlik bir yamadır; aynı tuzak diğer veritabanları için de geçerliydi (hepsi 4 KB volume'lerdeydi). Microsoft best-practice'i: **Exchange DB ve log volume'leri 64 KB allocation unit ile formatlanmalı ve DB'ler sistem diskinden ayrı tutulmalı.**
### 5.1 Yeni diskleri hazırlama
İki ayrı disk önerilir: biri DB, biri log (IO ayrımı ve kurtarılabilirlik için). 2 TB ve üzeri diskte **GPT** kullanın.
```powershell
Update-HostStorageCache
Get-Disk | Select Number, FriendlyName, @{N='GB';E={[math]::Round($_.Size/1GB)}}, PartitionStyle
# DB diski -> E: (64 KB NTFS)
New-Partition -DiskNumber <N> -UseMaximumSize -DriveLetter E |
Format-Volume -FileSystem NTFS -AllocationUnitSize 65536 -NewFileSystemLabel "ExchangeDB" -Confirm:$false
# Log diski -> F: (64 KB NTFS)
New-Partition -DiskNumber <M> -UseMaximumSize -DriveLetter F |
Format-Volume -FileSystem NTFS -AllocationUnitSize 65536 -NewFileSystemLabel "ExchangeLogs" -Confirm:$false
# Allocation unit'i doğrula (65536 olmalı)
Get-Volume -DriveLetter E,F |
Select DriveLetter, @{N='AllocUnit';E={(Get-CimInstance Win32_Volume -Filter "DriveLetter='$($_.DriveLetter):'").BlockSize}}
```
### 5.2 Her veritabanını taşıma (manuel, güvenli yöntem)
`Move-DatabasePath`'in dosya taşıyan hali, uzun kopya boyunca remote PowerShell oturumunu açık tutar ve onay istemi sırasında oturumun düşmesine yol açabilir. Bunun yerine **kopyayı robocopy ile, sadece config güncellemesini cmdlet ile** yapmak daha sağlamdır:
```powershell
Dismount-Database DB -Confirm:$false
eseutil /mh "<kaynak>\DB.edb" | Select-String "State" # Clean Shutdown şart
robocopy "<kaynak klasör>" "E:\ExchangeDB\DB" DB.edb /J /NP /TEE /R:3 /W:5 /LOG:C:\Temp\move_DB.log
New-Item -ItemType Directory -Force "F:\ExchangeLogs\DB" | Out-Null
Move-DatabasePath -Identity DB -EdbFilePath "E:\ExchangeDB\DB\DB.edb" -LogFolderPath "F:\ExchangeLogs\DB" -ConfigurationOnly -Confirm:$false
Mount-Database DB
```
**Performans ipucu — logları kopyalamayın:** Temiz dismount sonrası veritabanı *Clean Shutdown / Log Required: 0-0* olur, yani eski transaction loglarına ihtiyaç yoktur; mount yeni log akışını başlatır. Bir veritabanında ~80.000 log dosyası vardı ve bunları robocopy ile kopyalamak (dosya başına overhead yüzünden) saatler sürüyordu. Sadece EDB'yi kopyalayıp log klasörünü boş bırakmak işi dakikalara indirdi.
### 5.3 Dikkat edilecek tuzaklar
- **ERROR 32 (sharing violation):** Dismount edilmiş EDB'yi başka bir process (antivirüs, yedekleme ajanı, WMI sağlayıcısı) kilitleyebilir. Kilidi bulun ve serbest bırakın:
```powershell
handle64.exe -accepteula "<klasör>\DB.edb" # PID'i bulur (Sysinternals Handle)
Stop-Process -Id <pid> -Force
```
En iyi önlem: taşıma sırasında antivirüs gerçek-zamanlı taramayı ve yedekleme işini durdurun. robocopy'de `/R:3 /W:5` ekleyin ki kilitte sonsuza dek beklemesin.
- **Onay istemi komutları yutar:** Bir cmdlet onay sorusu çıkardığında, altına yapıştırdığınız satırlar cevap olarak tüketilir. Komutları **tek tek** çalıştırın.
- **Paralel taşıma:** Yalnızca SSD'de mantıklı (HDD'de kafa çekişmesi yapar). Paralelde birden çok DB aynı anda offline olur.
### 5.4 İçerik indeksi (Content Index)
Manuel taşıma sonrası `ContentIndexState` *Failed* olabilir (mail erişimini etkilemez, sadece arama). Yalnızca Failed olanları sıfırlayın:
```powershell
Stop-Service MSExchangeFastSearch -Force; Stop-Service HostControllerService -Force
Get-ChildItem "E:\ExchangeDB\DB\" -Directory -Filter "*.single" | Remove-Item -Recurse -Force
Start-Service HostControllerService; Start-Service MSExchangeFastSearch
# Birkaç dk sonra: Failed -> Crawling -> Healthy
Get-MailboxDatabaseCopyStatus DB | Select Name, Status, ContentIndexState
```
## 6. Antivirüs dışlamaları (kritik ve sık atlanan)
Exchange sunucusunda gerçek-zamanlı dosya taraması, büyük EDB/log dosyalarını kilitleyip (ERROR 32) performans ve hatta corruption sorunlarına yol açar. Microsoft, AV'nin şunları **hariç tutmasını** ister:
- **Klasörler:** DB volume (`E:\ExchangeDB`), log volume (`F:\ExchangeLogs`), `...\V15\Mailbox`, `...\TransportRoles\data\Queue`, `...\V15\Logging`
- **Uzantılar:** `.edb` `.log` `.jrs` `.chk` `.jfm` `.que`
- **Process'ler:** `Microsoft.Exchange.Store.Worker.exe`, `noderunner.exe`, `Microsoft.Exchange.Search.Service.exe`, `MSExchangeHMWorker.exe`, `MSExchangeTransport.exe`
Bakım sırasında korumayı geçici kapattıysanız, **dışlamaları ekledikten sonra geri açın** — sunucuyu korumasız bırakmayın.
## 7. Transaction log'ların temizlenmemesi (truncation)
Bir başka gizli sorun: binlerce log dosyası birikmişti. Exchange logları **yalnızca başarılı, uygulama-farkında (VSS) bir full backup'tan sonra** silinir (truncate). Durum tespiti:
```powershell
Get-MailboxDatabase -Status | Select Name, CircularLoggingEnabled, LastFullBackup
```
`LastFullBackup` eski/boş + loglar birikmiş → yedekler logları kesmiyor demektir. Bizdeki kök neden:
- **Hypervisor/depolama seviyesi VM-snapshot yedekleri** (ör. hyperconverged çözümlerin anlık görüntüleri) Exchange VSS writer ile konuşmaz → **log truncate etmez**. Bunlar DR için değerlidir ama Exchange log yönetimi için yeterli değildir.
- Asıl yedekleme yazılımında (image-level backup) **application-aware (VSS) işleme KAPALIYDI**.
**Çözüm:** Yedekleme yazılımının job'ında **application-aware processing**'i açın, geçerli guest admin kimlik bilgisi verin ve **transaction log'ları bu job ile işle / truncate** seçeneğini etkinleştirin (copy-only modunda DEĞİL). Bir Active Full alın:
```powershell
# Active full sonrası doğrulama
Get-MailboxDatabase -Status | Select Name, LastFullBackup # bugünün tarihi olmalı
```
Sonuç: tüm veritabanları ilk kez düzgün yedeklendi, on binlerce log birkaç yüze düştü, log volume rahatladı. (Yedek yoksa ve point-in-time kurtarma gerekmiyorsa geçici çare olarak *circular logging* düşünülebilir, ama doğru çözüm çalışan bir VSS yedeğidir.)
## 8. Çıkarılan dersler
1. **-1022 her zaman donanım değildir.** ESE Event ID 482 + system error 665, dosya sistemi/parçalanma sorununu işaret eder. Event log'a bakmadan donanım suçlamayın.
2. **Exchange volume'lerini 64 KB allocation unit ile formatlayın.** 4 KB, büyük EDB'lerde aşırı parçalanma ve 665 riski demektir.
3. **DB'leri sistem diskinden ayırın**, mümkünse DB ve log'u ayrı disklere koyun.
4. **`eseutil /p`'ye koşmayın.** Çoğu "mount olmuyor" vakası soft recovery veya parçalanma giderme ile veri kaybı olmadan çözülür.
5. **Antivirüs dışlamalarını yapın** — bu opsiyonel bir iyileştirme değil, Exchange için gerekliliktir.
6. **Yedeklerinizin gerçekten log truncate ettiğini doğrulayın.** VM-snapshot ≠ Exchange-aware backup. `LastFullBackup`'ı periyodik kontrol edin.
7. **Taşıma yöntemini sağlam seçin:** uzun kopyaları robocopy'ye bırakın, sadece kısa config cmdlet'lerini remote oturumda çalıştırın; komutları tek tek girin.
---
*Bu makale gerçek bir kurtarma operasyonundan derlenmiştir. Komutlar kendi ortamınıza uyarlanmalı ve production'da uygulanmadan önce test/yedek ile doğrulanmalıdır.*
Exchange 2016'da Mount Olmayan Veritabanını Kurtarma: -1022 / Error 665 (NTFS Parçalanma Limiti) ve 64 KB Diske Migrasyon
Her sistem yöneticisinin bildiği o sabahlardan biriydi: kullanıcılar e-postalarına erişemiyor, bir mailbox veritabanı bir türlü mount olmuyor ve elimde sadece kriptik bir hata kodu var: **-1022**. İlk refleksim çoğu kişininki gibi "disk bozuldu" oldu — ama olay hiç de öyle değildi. Saatler süren teşhisin sonunda karşıma, çok da konuşulmayan bir suçlu çıktı: **NTFS'in dosya parçalanma limiti (system error 665)**. Tek bir veritabanı dosyası tam **1,7 milyon parçaya** bölünmüştü. Bu yazıda, o veritabanını **tek bir byte veri kaybetmeden** nasıl kurtardığımı; ardından aynı tuzağa bir daha düşmemek için tüm veritabanlarını nasıl **64 KB'lik, doğru yapılandırılmış SSD disklere taşıdığımı**; yol boyunca karşılaştığım antivirüs kilitlerini, yedekleme/log birikmesi sorunlarını ve bunların hepsini nasıl çözdüğümü adım adım paylaşıyorum. Aynı hatayı gören birine bu notların saatlerce zaman kazandıracağını umuyorum — çünkü o sabah bana kazandıracak böyle bir yazı yoktu. 🙂