Pernahkah rekan-rekan mengalami skenario di mana proses migrasi mail server Zimbra Collaboration Suite (ZCS) sudah selesai dilakukan dan dinyatakan sukses, namun selang 1–2 minggu kemudian ada pengguna yang melaporkan bahwa sebagian email lamanya tidak muncul di server baru?
Situasi ini menjadi sangat menantang apabila VM atau server lama sudah terlanjur di-decommission atau dihapus. Untungnya, kita masih memiliki salinan file backup mentah (direktori /opt/zimbra) di server storage backup. Namun, melakukan full restore satu server utuh hanya untuk mengambil email milik satu akun tentu sangat tidak efisien—membutuhkan waktu berjam-jam, kapasitas storage ratusan GB, serta spek hardware yang besar.
Di infrastruktur yang kita kelola, daripada merestore satu server penuh, solusi yang jauh lebih cepat, hemat storage, dan elegan adalah melakukan Selective Blob/Message Restore.
Tantangannya: bagaimana cara menemukan file blob email milik user tersebut beserta struktur folder aslinya (seperti /Inbox, /Sent, dan custom folder) dari database lama yang mati (cold database) tanpa merusak atau mengubah data backup asli? Pada artikel kali ini, saya akan membedah langkah demi langkah teknisnya yang sudah teruji di lapangan.
Memahami Struktur Penyimpanan Zimbra
Sebelum masuk ke tahapan teknis, ada dua konsep penting arsitektur penyimpanan Zimbra yang perlu kita pahami:
- Struktur Blob (
store): File fisik pesan email (format.msg) tidak dinamai berdasarkan alamat email, melainkan dikelompokkan berdasarkan nomor ID numerik (mailbox_id) di dalam direktorizimbra/store/0/<mailbox_id>/msg/. Format nama filenya adalah<item_id>-<mod_content>.msg(contoh:3165-5992.msg, di mana angka3165merupakan Item ID). - Metadata Database (
mailboxgroup): Relasi antaraitem_iddan path folder asli (Inbox, Sent, Trash, dll.) tersimpan di database MariaDB pada tabelmail_itemdi schemamboxgroup<N>, dengan formulaN = mailbox_id % 100(jika hasilnya 0, maka masuk kemboxgroup100).
1. Mencari Mailbox ID Instan Tanpa Menyalakan Database
Untuk mengetahui mailbox_id milik akun terkait, kita tidak perlu repot menginstal atau menyalakan service database MariaDB. Kita bisa mengekstraknya langsung dari berkas tablespace binary InnoDB (mailbox.ibd).
Langkah A: Ambil Account UUID
Jalankan perintah strings langsung ke file tablespace mailbox.ibd pada direktori backup server:
# cd /srv/backup/vps/old-mail.colamen.id # strings zimbra/db/data/zimbra/mailbox.ibd | grep -B 2 -A 2 -i "raihan.utomo@colamen.id"
Perintah di atas akan menampilkan string UUID akun milik user:
f557d63c-3784-454a-a6d2-93575e914f0c raihan.utomo@colamen.id
Langkah B: Ekstrak Mailbox ID Menggunakan Python
Di struktur internal InnoDB, secondary index leaf page memetakan UUID dengan 4-byte big-endian integer yang merepresentasikan mailbox_id. Jalankan skrip Python one-liner berikut untuk mencocokkan UUID tersebut dengan daftar direktori mailbox yang ada di folder store backup:
# python3 -c '
import os
uuid = b"f557d63c-3784-454a-a6d2-93575e914f0c"
store_dirs = set(os.listdir("zimbra/store/0"))
with open("zimbra/db/data/zimbra/mailbox.ibd", "rb") as f:
data = f.read()
pos = 0
found = set()
while True:
pos = data.find(uuid, pos)
if pos == -1:
break
after = int.from_bytes(data[pos + len(uuid) : pos + len(uuid) + 4], "big")
if str(after) in store_dirs:
found.add(after)
for off in range(15, 25):
before = int.from_bytes(data[pos - off : pos - off + 4], "big")
if str(before) in store_dirs:
found.add(before)
pos += len(uuid)
print("Mailbox ID Ditemukan:", list(found))
'
Output perintah tersebut:
Mailbox ID Ditemukan: [1100]
Hanya dalam hitungan detik, kita sudah mengetahui pasti bahwa mailbox_id akun tersebut adalah 1100.
2. Awas Jebakan: Periksa Multi-Volume Store / HSM
Satu hal penting yang kerap menjadi jebakan: jangan hanya memeriksa dan mengambil file dari direktori zimbra/store/! Apabila server Zimbra lama dikonfigurasi menggunakan fitur Hierarchical Storage Management (HSM) atau secondary volume disk, sebagian besar email lama justru tersimpan di volume sekunder.
Periksa seluruh direktori store yang ada pada backup:
# ls -d zimbra/*store*
Kemudian hitung sebaran jumlah file .msg milik user di setiap volume:
# find zimbra/store/0/1100/msg/ -type f -name "*.msg" | wc -l 1399 # find zimbra/2store/0/1100/msg/ -type f -name "*.msg" | wc -l 46 # find zimbra/ext-store/0/1100/msg/ -type f -name "*.msg" | wc -l 3934 # find zimbra/ext-store2/0/1100/msg/ -type f -name "*.msg" | wc -l 8624 # find zimbra/ext-store3/0/1100/msg/ -type f -name "*.msg" | wc -l 3480 # find zimbra/tempstore/0/1100/msg/ -type f -name "*.msg" | wc -l 415
Dari data di atas terlihat jelas bahwa volume utama (store) hanya memuat 1.399 email (~8%), sedangkan 16.000+ email lama lainnya (~92%) tersimpan di volume sekunder ext-store, ext-store2, dan ext-store3. Jika kita hanya mengambil dari store, sebagian besar email yang dicari user tidak akan pernah ditemukan.
3. Menjalankan Cold Database Secara Aman via OverlayFS & Docker
Jika kita langsung merestore belasan ribu file .msg tersebut ke satu folder (misalnya folder /Restored), seluruh email akan bercampur aduk antara Inbox, Sent, dan custom folder. Untuk merekonstruksi struktur foldernya, kita perlu membaca tabel mail_item pada database.
Namun, menjalankan MariaDB langsung di atas direktori backup mentah sangat berisiko merusak integritas data karena engine akan melakukan proses crash recovery atau mengubah header file. Di sisi lain, meng-copy database 38 GB ke direktori lain memakan waktu lama dan menghabiskan ruang disk.
Solusi terbaik adalah menggunakan Linux OverlayFS dipadukan dengan Docker Container MariaDB:
# 1. Siapkan direktori kerja sementara # mkdir -p /tmp/ovl_upper /tmp/ovl_work /tmp/zimbra_db_overlay # 2. Mount folder database backup sebagai lower layer (Read-Only murni level kernel) # mount -t overlay overlay -o lowerdir=/srv/backup/vps/old-mail.colamen.id/zimbra/db/data,upperdir=/tmp/ovl_upper,workdir=/tmp/ovl_work /tmp/zimbra_db_overlay
Jalankan container MariaDB dengan konfigurasi khusus berikut:
# docker run -d --name temp_zimbra_db --entrypoint mariadbd -v /tmp/zimbra_db_overlay:/var/lib/mysql mariadb:10.5 --datadir=/var/lib/mysql --socket=/tmp/mysql.sock --skip-grant-tables --user=root --table-definition-cache=50000 --table-open-cache=50000 --innodb-buffer-pool-size=1G --innodb-stats-persistent=OFF --innodb-stats-on-metadata=OFF
Catatan Penting Lapangan:
- Bypass Entrypoint (
--entrypoint mariadbd): Wajib digunakan agar container tidak macet menjalankan script bawaan Docker yang mencoba melakukanchown -R mysql:mysqlpada puluhan gigabyte file database. - Tuning Cache Tabel (
--table-definition-cache=50000): Mencegah deadlockDICT_SYS Mutex. Default cache bawaan hanya 400 tabel, sementara Zimbra memiliki ribuan tabel (schemamboxgroup1s/dmboxgroup100), yang bisa menyebabkan proses MariaDB terkunci permanen di status Opening tables. - Nonaktifkan Persistent Stats (
--innodb-stats-persistent=OFF): Menghindari error atau warning ketidakcocokan skema tabel statistik antara versi Zimbra lama dan image MariaDB baru.
Pastikan database sudah aktif dan siap menerima koneksi:
# docker logs --tail 10 temp_zimbra_db [Note] mariadbd: ready for connections.
4. Ekstraksi Pemetaan Item ID ke Folder Asli
Karena mailbox_id = 1100, datanya berada pada database mboxgroup100 (1100 % 100 = 0 -> 100).
Periksa daftar folder milik user pada tabel mail_item (tipe 1 = folder):
# docker exec temp_zimbra_db mariadb -S /tmp/mysql.sock mboxgroup100 -e " SELECT id, parent_id, name, type FROM mail_item WHERE mailbox_id = 1100 AND type = 1; "
Output query:
id parent_id name type 2 1 Inbox 1 3 1 Trash 1 5 1 Sent 1 6 1 Drafts 1 23200 1 Kiriman Eksternal 1
Folder standar Zimbra memiliki ID tetap (2 = Inbox, 3 = Trash, 5 = Sent), sedangkan ID 23200 merupakan folder custom yang dibuat oleh user.
Ekspor pemetaan Item ID ke path folder ke dalam file format TSV:
# docker exec temp_zimbra_db mariadb -S /tmp/mysql.sock mboxgroup100 -N -e "
SELECT id,
CASE folder_id
WHEN 2 THEN '/Inbox'
WHEN 3 THEN '/Trash'
WHEN 5 THEN '/Sent'
WHEN 23200 THEN '/Kiriman Eksternal'
ELSE '/Inbox'
END
FROM mail_item
WHERE mailbox_id = 1100 AND type = 5;
" > /tmp/raihan_map.tsv
Setelah file TSV mapping berhasil diekspor, hentikan container dan unmount OverlayFS:
# docker rm -f temp_zimbra_db # umount /tmp/zimbra_db_overlay # rm -rf /tmp/ovl_upper /tmp/ovl_work /tmp/zimbra_db_overlay
Data backup asli 38 GB tetap 100% aman dan tidak tersentuh.
5. Transfer & Batch Restore via zmmailbox (Single JVM)
Pindahkan file raihan_map.tsv beserta direktori store seluruh volume ke server Zimbra baru (misalnya pada direktori /srv/blob-message-raihan/), lalu berikan hak akses baca:
# chmod -R a+rX /srv/blob-message-raihan/
Hindari Shell Loop!
Jangan pernah menjalankan perintah zmmailbox addMessage di dalam loop shell biasa (for file in *.msg). Setiap pemanggilan perintah zmmailbox akan me-load Java Virtual Machine (JVM) dari awal. Mengimpor 17.000 file secara loop satuan dapat memakan waktu 7–10 jam!
Solusinya adalah menyusun seluruh perintah ke dalam satu file teks, lalu mem-pipe-nya ke satu sesi interaktif zmmailbox via stdin. Proses restore 17.600+ email akan tuntas hanya dalam waktu 10–15 menit.
Jalankan skrip Python berikut untuk menghasilkan berkas perintah batch:
# python3 << 'EOF'
import os
base_dir = "/srv/blob-message-raihan"
map_file = os.path.join(base_dir, "raihan_map.tsv")
output_file = os.path.join(base_dir, "restore_commands.txt")
item_to_folder = {}
with open(map_file, "r") as f:
for line in f:
parts = line.strip().split(" ")
if len(parts) == 2:
item_to_folder[parts[0]] = parts[1]
commands = []
commands.append('createFolder "/Kiriman Eksternal"')
store_dirs = ["store", "2store", "ext-store", "ext-store2", "ext-store3", "tempstore"]
for sdir in store_dirs:
full_store_path = os.path.join(base_dir, sdir)
if not os.path.exists(full_store_path):
continue
for root, dirs, files in os.walk(full_store_path):
for f in files:
if f.endswith(".msg"):
item_id = f.split("-")[0].split(".")[0]
target_folder = item_to_folder.get(item_id, "/Inbox")
full_path = os.path.join(root, f)
commands.append(f'addMessage "{target_folder}" "{full_path}"')
with open(output_file, "w") as f:
for cmd in commands:
f.write(cmd + "
")
print(f"Total commands generated: {len(commands)}")
EOF
Eksekusi Restore ke Akun Sandbox (Staging)
Untuk menghindari risiko timbulnya email duplikat atau konflik pada akun produksi, buat akun sandbox sementara (misal: restore-raihan@colamen.id):
# su - zimbra # zmprov ca restore-raihan@colamen.id 'PasswordSementara123!' # Jalankan batch restore di dalam screen session # screen -S restore_raihan # zmmailbox -z -m restore-raihan@colamen.id < /srv/blob-message-raihan/restore_commands.txt > /tmp/restore.log 2>&1
Kita bisa memantau progres import secara berkala dari terminal lain:
# grep -c "^[0-9]" /tmp/restore.log # zmmailbox -z -m restore-raihan@colamen.id gaf
6. Sinkronisasi & Deduplikasi Otomatis via imapsync
Setelah seluruh ~17.600 pesan terimpor ke akun sandbox, bagaimana cara memindahkan email yang hilang ke akun aktif user tanpa menduplikasi belasan ribu email yang sebenarnya sudah ada?
Jawabannya adalah menggunakan imapsync. Engine imapsync secara cerdas membandingkan Message-ID, header, dan ukuran pesan. Email yang sudah ada di akun aktif akan di-skip secara otomatis.
Uji Coba Dry-Run
Jalankan simulasi dengan flag --dry menggunakan akun admin untuk autentikasi:
# /usr/bin/imapsync --host1 mbox1.colamen.id --user1 restore-raihan@colamen.id --password1 'PasswordSementara123!' --host2 mbox1.colamen.id --user2 raihan.utomo@colamen.id --authuser2 admin@colamen.id --password2 'AdminPassword' --port1 7993 --port2 7993 --ssl1 --ssl2 --nofoldersizes --nofoldersizesatend --errorsmax 1000 --addheader --dry
Periksa statistik pada log output:
Messages to transfer : 45 Messages skipped : 17408 Messages found in host2 not in host1 : 258 messages Detected 0 errors
Hasil ini sangat presisi:
- 17.408 pesan di-skip: Karena sudah ada di akun aktif.
- 45 pesan: Inilah tepat email lama yang hilang saat migrasi awal dan akan dipulihkan.
- 258 pesan di host2: Email baru user selama 2 minggu pasca migrasi tetap aman dan tidak tersentuh (karena kita tidak menggunakan parameter
--delete2).
Eksekusi Sinkronisasi Real
Jalankan perintah yang sama tanpa flag --dry:
# /usr/bin/imapsync --host1 mbox1.colamen.id --user1 restore-raihan@colamen.id --password1 'PasswordSementara123!' --host2 mbox1.colamen.id --user2 raihan.utomo@colamen.id --authuser2 admin@colamen.id --password2 'AdminPassword' --port1 7993 --port2 7993 --ssl1 --ssl2 --nofoldersizes --nofoldersizesatend --errorsmax 1000 --addheader
Output akhir yang sukses:
The sync looks good, all 17449 identified messages in host1 are on host2. Messages transferred: 45 Messages skipped: 17408 Detected 0 errors Exiting with return value 0 (EX_OK: successful termination)
Tepat 45 email yang hilang telah kembali ke folder /Inbox akun aktif user secara utuh dengan tanggal dan attachment yang lengkap tanpa menimbulkan duplikasi.
7. Pembersihan Akhir
Setelah diverifikasi oleh user melalui Webmail atau email client, lakukan pembersihan environment staging:
# su - zimbra -c "zmprov da restore-raihan@colamen.id" # rm -rf /srv/blob-message-raihan/
Kesimpulan
Dengan memadukan ekstraksi langsung tablespace InnoDB, proteksi kernel OverlayFS, Docker container MariaDB, batching zmmailbox via stdin, dan fitur deduplikasi cerdas imapsync, kita dapat memulihkan email yang hilang secara cepat, aman, dan presisi tanpa perlu membuang waktu serta resource storage untuk melakukan full restore server Zimbra lama.
Dengan demikian proses pemulihan email yang hilang dapat dilakukan secara cepat dan presisi tanpa perlu membuang waktu dan kapasitas storage untuk melakukan full restore server Zimbra lama. Apabila rekan-rekan mengalami kesulitan atau memiliki kebutuhan terkait instalasi, pemeliharaan, migrasi, maupun pemulihan data (recovery) Zimbra Mail Server, Excellent menyediakan layanan implementasi dan Excellent Managed Services (EMS) untuk Zimbra. Bagi rekan-rekan yang berminat untuk jasa layanan tersebut bisa langsung kontak & tanya-tanya ke email sales@excellent.co.id.