Restore Blob/Message Email Menggunakan Cold Database dari Backup Zimbra

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:

  1. Struktur Blob (store): File fisik pesan email (format .msg) tidak dinamai berdasarkan alamat email, melainkan dikelompokkan berdasarkan nomor ID numerik (mailbox_id) di dalam direktori zimbra/store/0/<mailbox_id>/msg/. Format nama filenya adalah <item_id>-<mod_content>.msg (contoh: 3165-5992.msg, di mana angka 3165 merupakan Item ID).
  2. Metadata Database (mailboxgroup): Relasi antara item_id dan path folder asli (Inbox, Sent, Trash, dll.) tersimpan di database MariaDB pada tabel mail_item di schema mboxgroup<N>, dengan formula N = mailbox_id % 100 (jika hasilnya 0, maka masuk ke mboxgroup100).

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 melakukan chown -R mysql:mysql pada puluhan gigabyte file database.
  • Tuning Cache Tabel (--table-definition-cache=50000): Mencegah deadlock DICT_SYS Mutex. Default cache bawaan hanya 400 tabel, sementara Zimbra memiliki ribuan tabel (schema mboxgroup1 s/d mboxgroup100), 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.

Leave a Reply

Your email address will not be published. Required fields are marked *