Maildir'den mdbox'a Geçiş: Dovecot Posta Kutularını Kayıpsız Dönüştürmek
Maildir 100 binin üzerinde mesajda inode ve stat() yükü altında çöker; mdbox bunu çözer. Ama asıl tehlike geçişin kendisinde. rsync ile değil, GUID koruyan dsync ile iki fazlı, sayım-doğrulamalı, geri dönülebilir bir göç reçetesi.
EvilMail Team11 Temmuz 202613 dk okuma
Üretimdeki bir müşteri kutusunda doveadm mailbox status -u [email protected] messages INBOX komutunu çalıştırıyorsunuz ve terminal saniyelerce takılıyor. du -a çıktısı milyon satırın üzerinde akıyor, find bitmek bilmiyor. Gece 03:00'te başlayan rsync tabanlı yedek altı saate uzamış ve hâlâ dosya sayıyor. Diskte yer var ama df -i inode kullanımını %90'da gösteriyor. Sorun postanın boyutu değil; sorun her mesajın diskte ayrı bir dosya, dolayısıyla en az bir inode tüketmesi. 250 bin mesajlı bir INBOX, 250 binden fazla küçük dosya demek — ve filesystem bunun altında readdir/stat maliyetiyle boğulur.
mdbox tam olarak bunun için var: onlarca mesajı tek bir depolama dosyasında toplar, index'i mesajdan ayırır, zlib ile sıkıştırır ve aynı eki yüzlerce kullanıcı için tek kopya olarak saklar. Ama bu yazının asıl uyarısı formatlarla ilgili değil — iş asıl geçişte batıyor. İnsanlar mdbox storage dosyalarını rsync
'leyerek ya da klasörleri elle kopyalayarak mesaj GUID'lerini, IMAP UID'lerini ve bayrakları bozar; sonuç ya istemcinin tüm kutuyu yeniden indirmesi, ya okundu bilgisinin sıfırlanması, ya da doğrudan mail kaybıdır. mdbox'a geçiş bir dosya kopyalama işi değildir;
doveadm
/
dsync
ile GUID koruyan bir replikasyon işidir. Aşağıda iki fazlı, kesintiye yakın-sıfır, sayımla doğrulanan ve geri dönülebilir bir reçete var.
Maildir nerede tıkanır, mdbox neyi çözer
Maildir'in tasarımı zarif ve dayanıklı: her posta kutusu cur/, new/ ve tmp/ dizinlerinden oluşur. Teslimat önce tmp/ altına yazılır, sonra new/'e atomik bir rename() ile taşınır — yani teslimat sırasında sunucu çökse bile yarım mesaj görünmez. IMAP bayrakları dosya adının içinde taşınır (...:2,S sonundaki S = \Seen). Bu yüzden Maildir crash-safe'dir ve teorik olarak rsync-dostudur. Onlarca yıl varsayılan olması boşuna değil.
Ölçek duvarı ise şurada: her mesaj = bir dosya = en az bir inode. ext4 varsayılan olarak her ~16 KB için bir inode ayırır (bytes-per-inode = 16384), yani ortalama küçük mesajlarda inode'lar veriden önce tükenebilir. Tek bir dizinde on binlerce dosya olduğunda readdir + stat çağrılarının maliyeti doğrusal büyür; IMAP istemcisi klasörü her açtığında Dovecot bu dosyaları taramak zorunda kalır. Yedekleme milyonlarca küçük dosyada metadata I/O'suna boğulur. Maildir'de sıkıştırma ve tek-örnek depolama (SIS) da yoktur — 300 kişiye giden 4 MB'lık bir ek diskte 300 kez durur.
mdbox bu modeli tersine çevirir. Birden çok mesaj tek bir storage/m.N dosyasında birleşir; mdbox_rotate_size her dosyanın hedef boyutunu kontrol eder. GUID→dosya eşlemesi ayrı bir dovecot.map.index içinde tutulur, klasör index'leri mailboxes/.../dovecot.index altında yaşar. zlib ile mesajlar diskte sıkıştırılır, mail_attachment_dir ile ekler tek kopya olarak dışarıda saklanır. 250 bin mesajlık kutu, 250 bin dosya yerine birkaç düzine m.N dosyasına iner.
Ne zaman geçmeli? Pratik eşik: kutu başına ~100–150 bin mesaj, VEYA yoğun IMAP eşzamanlılığı (çok cihazlı, sürekli IDLE tutan kullanıcılar), VEYA disk/inode baskısı. Bu eşiklerin altındaki tipik kutular için Maildir hâlâ gayet iyidir — geçişi bir ritüel olarak herkese dayatmayın. Ara bir formül isteyen için sdbox vardır: dbox formatının mesaj-başına-tek-dosya varyantı, ama SIS ve toplama avantajını kaybedersiniz; çoğu senaryoda ya Maildir'de kalın ya doğrudan mdbox'a geçin.
İki formatın dosya düzeni
Buradaki kritik detay: Maildir'de GUID mesaj dosyasının adında yaşar, mdbox'ta ise dovecot.map.index içinde tutulur. Bu yüzden mdbox storage dosyalarını index'ten ayrı kopyalarsanız — ya da tersini — eşleme kopar. Dosya kopyalama zihniyeti tam da bu noktada yıkıcıdır.
mdbox'ı üretim için doğru yapılandırmak
Hedef formatı /etc/dovecot/conf.d/10-mail.conf (ve zlib/attachment için ilgili plugin bloğu) üzerinde tanımlayın. Evilmail vhost düzeninde yollar %d (domain) ve %n (kullanıcı adı) ile parçalanır:
mdbox_rotate_size ayarını fazla küçük tutarsanız (örn. 1M) yine yüzlerce m.N dosyası üretirsiniz — Maildir'in inode problemine kısmen geri dönmüş olursunuz. Fazla büyük tutarsanız (örn. 100M) doveadm purge sırasında koca dosyaları yeniden yazmak pahalılaşır. Pratikte 8–16 MB dengeli çalışır; 10M iyi bir başlangıç. zlib_save_level = 6 CPU ile disk arasında makul bir orta noktadır; SSD üzerinde CPU boldur ama level'ı yükseltmek nadiren kayda değer kazanç verir. mail_attachment_min_size = 128k altındaki küçük ekleri SIS'e taşımaz — çünkü çok küçük dosyaları ayırmak, faydadan çok metadata yükü getirir.
Önemli operasyonel not: /var/mail/vhosts altındaki her şey 5000:5000 (mailuser/mailgroup) sahipliğinde olmalı. Geçişte oluşturacağınız yeni mdbox/ ve attachments/ dizinleri de öyle. Geçiş sonrası bir chown -R 5000:5000 /var/mail/vhosts unutmak, Dovecot'un "Permission denied" ile mesajları hiç görmemesine yol açar — panik yaratan ama tetiği basit bir hata.
Neden rsync değil dsync: GUID/UID koruması
dsync (ve onu saran doveadm backup/doveadm sync) mesajı bir bütün metadata paketi olarak taşır: mesaj GUID'i, IMAP UID'i, `UIDVALIDITY` değeri, `\Seen`/`\Flagged`/`\Answered`/`\Deleted`/`\Draft` bayrakları, kullanıcı keyword'leri, klasör hiyerarşisi ve abonelikler. Hedef mdbox kutusu, kaynağın birebir mantıksal kopyası olur. İstemci bir sonraki bağlantıda hiçbir farkı fark etmez — UID'ler aynı, okundu durumu aynı, hiçbir şey yeniden inmez.
Elle kopyalama ya da rsync bunların hiçbirini anlamaz. Dosya baytlarını taşır, ama mdbox'ın index/map katmanını tutarlı biçimde yeniden kuramaz. Sonuç senaryolarını sırayla sayalım: UIDVALIDITY değişirse istemci tüm klasörü baştan indirir (bir kullanıcı için gigabaytlar, tüm domain için felaket); bayraklar kaybolursa binlerce mesaj tekrar "okunmadı" olur; GUID/map eşleşmezse mesajlar hiç görünmez.
Ayrı bir tuzak: çalışan bir Dovecot altında mdbox `storage/` ve `index` dosyalarını `rsync` ile yedeklemek de aynı sebeple kırıktır. Storage dosyasını kopyaladığınız an ile index'i kopyaladığınız an arasında Dovecot bir purge veya teslimat yapmışsa, elinizde tutarsız bir kopya kalır. mdbox için "yedek" demek doveadm backup demektir — ham dosya kopyası değil.
Sıfır kayıplı geçiş: iki fazlı dsync akışı
Fikir basit: uzun süren senkronizasyonu kullanıcı hâlâ erişimdeyken canlı yapın (Faz 1), sonra teslimatı kısa süreliğine durdurup yalnızca değişen mesajları taşıyan bir delta çalıştırın (Faz 2). Kesinti penceresi yalnızca Faz 2 + reload süresidir — tipik olarak kutu başına 30 saniyenin altında.
Faz 1'de kaynak (mevcut Maildir) config'ini -o ile geçici olarak override edip hedefi mdbox olarak verirsiniz. Bu, üretim mail_location'ına dokunmaz — sadece bu tek komut için farklı bir kaynak okur:
bash
# FAZ 1 — kullanıcı erişimdeyken, servis açık
doveadm -o mail_location=maildir:/var/mail/vhosts/%d/%n \
backup -u [email protected] \
mdbox:/var/mail/vhosts/%d/%n/mdbox
doveadm backup hedefi kaynağa birebir eşitler — hedefte fazlalık varsa siler, dolayısıyla yön kritiktir: kaynak, soldaki -o mail_location ile verilen Maildir; hedef ise komutun sonundaki mdbox argümanıdır. Buraya -R bayrağı EKLEMEYİN: -R yönü ters çevirir ve henüz boş olan mdbox'ı Maildir'in üstüne yazarak postayı siler. Faz 1 dakikalar sürebilir; sorun değil, kullanıcı çalışmaya devam eder, yeni gelen mailler eski Maildir'e düşmeyi sürdürür.
Faz 2'de o kullanıcıya teslimatı durdurun (Postfix transport'unda hold, ya da hesabı geçici kilitleyin) ve aynı backup komutunu tekrar çalıştırın. Bu sefer yalnızca Faz 1'den beri değişen mesajlar akar — genellikle saniyeler. Sonra mail_location'ı kalıcı olarak mdbox'a çevirip config'i yeniden yükleyin:
for u in $(doveadm user '*@evilmail.pro'); do
doveadm -o mail_location=maildir:/var/mail/vhosts/%d/%n \
backup -u "$u" mdbox:/var/mail/vhosts/%d/%n/mdbox
done
Doğrulama: mesaj sayısı ve boyutla kanıtla
mail_location'ı değiştirmeden ÖNCE, geçişin gerçekten bütünlüklü olduğunu sayılarla ispatlayın. Kabul kriteri net: klasör-klasör toplam mesaj sayısı ve toplam vsize kaynakla hedefte eşleşmeli.
bash
# Öncesi (Maildir) ve sonrası (mdbox) — tüm klasörler
doveadm mailbox status -u [email protected] messages vsize '*'
# Tek klasörde çapraz kontrol
doveadm search -u [email protected] mailbox INBOX all | wc -l
# Örnek GUID/UID kontrolü
doveadm fetch -u [email protected] 'guid uid' mailbox INBOX all | head
Kural sert: sayılar tutmuyorsa `mail_location`'ı DEĞİŞTİRMEYİN. vsize'ın birebir aynı olmasını bekleyin (zlib disk üzerinde sıkıştırır ama vsize mesajın mantıksal RFC822 boyutunu raporlar, sıkıştırılmış boyutu değil). Bir uyuşmazlık görürseniz Faz 2 delta'yı tekrar çalıştırın; hâlâ tutmuyorsa geçişi durdurup logları inceleyin — henüz üretimde hiçbir şey kaybetmediniz, çünkü kullanıcı hâlâ Maildir okuyor.
Geçiş sonrası bakım ve tuzaklar
mdbox'ın toplama modeli bir bakım borcu getirir. Bir mesaj expunge edildiğinde m.N dosyasından hemen silinmez; yerinde bir "delik" kalır. O alanı geri kazanmak doveadm purge işidir:
bash
# Tek kullanıcı
doveadm purge -u [email protected]
# Tümü için gecelik cron
doveadm purge -A
Bunu gecelik cron'a koyun; aksi halde diskte sızıntı gibi görünen ama aslında purge bekleyen alan birikir. İkinci araç doveadm force-resync: bozuk bir index'i storage'dan yeniden inşa eder — mdbox'ın en kullanışlı kurtarma komutu.
Dürüst olmak gerekirse mdbox, Maildir'e göre daha kırılgandır: tek bir m.N dosyası birçok mesaj taşıdığından, o dosya veya index bozulursa etki tek mesajla sınırlı kalmaz. Bunun karşılığı disiplindir — düzenli doveadm backup tabanlı yedek (asla çalışan sunucudan ham rsync değil) ve periyodik resync. Yedekleme stratejinizi buna göre güncelleyin: mdbox yedeği doveadm backup ile başka bir hedefe alınır; dosya sistemi snapshot'ı alacaksanız da Dovecot sessizken (veya LVM/ZFS tutarlı snapshot ile) alın.
Geçiş kontrol listesi
inode ve disk baseline'ını al (df -i, doveadm mailbox status ... vsize)
config'i önce staging'de test et, doveconf -n ile doğrula
tam Maildir yedeği al (tar czf) — geri dönüş sigortası
eşleşiyorsa mail_location = mdbox:... yap ve doveadm reload
tek kullanıcıda smoke test: IMAP login + bir gönder/al turu
gecelik doveadm purge -A cron'unu kur
eski Maildir'i 7–14 gün sakla, sonra sil
yedekleme betiklerini ham rsync'ten doveadm backup'a çevir
Geçiş bir dosya taşıma işi değil, metadata koruyan bir replikasyon işidir — sayılar tutana kadar eski Maildir'e dokunmadığınız sürece her adım geri alınabilir kalır.