D
P
0
← Semua artikel Read in English

WordPress & PHP di Produksi

`wp db export --single-transaction` Meninggalkan Dump 840 Byte: `FLUSH TABLES WITH READ LOCK` Ditolak Tanpa Grant RELOAD

· · 6 menit baca
`wp db export --single-transaction` Meninggalkan Dump 840 Byte: `FLUSH TABLES WITH READ LOCK` Ditolak Tanpa Grant RELOAD

Tugas saya di sebuah situs WordPress produksi pada akhir Agustus cukup jelas di atas kertas: memperbarui plugin, tema, dan core yang usang, lalu membuang plugin dan tema yang tidak dipakai. Server itu menyediakan WP-CLI 2.12.0, jadi seluruh pekerjaannya dijalankan lewat SSH sebagai perintah satu baris. Urutannya juga sederhana. Backup dibuat lebih dulu, dan semua backup diverifikasi bisa dibaca sebelum ada apa pun yang dihapus.

Di langkah backup database, wp db export dengan opsi --single-transaction gagal. Yang membuatnya berbahaya bukan kegagalannya, melainkan sisanya: sebuah stub 840 byte yang masih terlihat seperti berkas dump. Pemeriksaan yang cuma bertanya apakah berkas backup-nya ada akan menjawab ya, dan backup semacam itu baru ketahuan isinya pada saat yang paling tidak enak, yaitu ketika ada sesuatu yang perlu dipulihkan.

Lock global yang butuh RELOAD

Penyebab yang tercatat: user database di server itu tidak punya grant RELOAD, sehingga FLUSH TABLES WITH READ LOCK ditolak. Perintah yang biasa disingkat FTWRL ini menutup semua tabel yang terbuka dan mengunci semua tabel di semua database dengan read lock global. RELOAD sendiri termasuk izin administratif MySQL, yang berlaku untuk seluruh server dan bukan untuk satu database tertentu.

Sekilas ini terdengar terbalik, karena --single-transaction justru dipakai supaya dump tidak perlu mengunci apa pun. Menurut dokumentasi mysqldump, opsi itu memasang isolasi REPEATABLE READ dan membuka transaksi sebelum membaca data, sehingga tabel InnoDB terbaca dalam keadaan konsisten tanpa memblokir aplikasi. Pengecualiannya juga tertulis di dokumentasi MySQL 8.0: sejak 8.0.32, mysqldump dengan --single-transaction membutuhkan izin RELOAD atau FLUSH_TABLES kalau gtid_mode=ON dan --set-gtid-purged bernilai ON atau AUTO, dan AUTO adalah nilai bawaannya. Laporan bug publik MySQL menunjukkan bahwa di rilis 8.0.32 dan 5.7.41 syarat itu sempat berlaku tanpa memandang GTID, sampai dipersempit lagi di 8.0.33 dan 5.7.42. Pengguna yang terkena melaporkan galat yang diawali Couldn't execute 'FLUSH TABLES WITH READ LOCK': Access denied.

Catatan saya tidak menyimpan versi mysqldump di server itu maupun teks galat persisnya, jadi saya tidak akan menebak jalur mana yang terjadi di sana. Kalau kamu bertemu gejala yang sama, mysqldump --version adalah hal pertama yang layak dicek.

Kode sumber mysqldump juga memberi gambaran kenapa kegagalan seperti ini tetap meninggalkan berkas. Kepala dump ditulis lebih dulu, FTWRL baru diminta sesudahnya, dan tanpa --force, query yang ditolak membuat program langsung keluar sebelum sempat menulis bagian penutup. Isi stub 840 byte itu tidak tercatat, tapi urutan ini cocok dengan adanya berkas kecil yang tertinggal.

--no-tablespaces yang malah membatalkan dump

Jebakan kedua ada di flag yang dikenal sebagai jalan keluar untuk user database berizin terbatas. Menurut dokumentasi mysqldump, sejak MySQL 8.0.21 izin PROCESS dibutuhkan kalau --no-tablespaces tidak dipakai. Di server itu, WP-CLI mengubah --no-tablespaces menjadi tablespaces=, dan mysqldump batal.

Penyebabnya ada di cara WP-CLI mengurai argumen. Setiap argumen berawalan --no- dibaca sebagai negasi, sehingga --no-tablespaces tersimpan sebagai argumen tablespaces bernilai false. Ketika argumen tambahan itu dirangkai menjadi perintah mysqldump, nilai false tadi berubah menjadi string kosong.

# disederhanakan, nama berkas netral
wp db export backup.sql --no-tablespaces
 
# diurai WP-CLI sebagai       : tablespaces = false
# sampai di mysqldump sebagai : --tablespaces=   (nilai kosong)

Gejala yang sama pernah dilaporkan di issue publik repositori WP-CLI, dengan pesan mysqldump: [ERROR] unknown variable 'tablespaces='. Aturan di catatan saya pendek: jangan pakai flag itu. Flag itu memang tidak perlu ditulis. Dokumentasi wp db export menyebut perintah itu sudah menambahkan --no-tablespaces ke mysqldump secara default, dan menyediakan --include-tablespaces untuk mematikannya. Di issue tadi, maintainer WP-CLI menyebut default ini berlaku sejak versi 2.5.0, sementara WP-CLI di server itu sudah 2.12.0. Untuk melihat perintah mysqldump persis yang dirakit WP-CLI, maintainer di utas yang sama menyarankan menambahkan --debug.

Resep untuk server itu

Resep yang saya catat untuk server itu terdiri dari dua aturan: pakai --skip-lock-tables, lalu pastikan dump berakhir dengan baris -- Dump completed.

# contoh disederhanakan, nama berkas netral
wp db export backup.sql --skip-lock-tables
 
tail -n 1 backup.sql | grep -q '^-- Dump completed' \
  && echo 'dump tuntas' \
  || echo 'dump TIDAK tuntas'

--skip-lock-tables mematikan --lock-tables, yang ikut aktif secara default lewat --opt dan mengunci tabel tiap database sebelum isinya dibaca. Jalan memutar ini ada harganya. Karena --single-transaction gagal di server itu, dump tidak lagi diambil dari satu snapshot. Tabel dibaca satu per satu, jadi kalau ada tulisan ke database selama dump berjalan, isi antartabel bisa tidak sinkron.

Aturan kedua inilah yang membedakan dump tuntas dari stub. Menurut dokumentasi mysqldump, komentar -- Dump completed on <tanggal> ditulis di akhir dump selama --comments aktif, dan --comments aktif secara default. Dump yang berhenti di tengah jalan tidak pernah sampai ke baris itu, jadi baris terakhir berkas lebih bisa dipercaya daripada ukuran atau keberadaannya. Ada tiga catatan kecil. --skip-comments dan --compact membuang komentar itu bahkan dari dump yang tuntas. Untuk dump yang langsung dikompresi, baris terakhirnya dibaca lewat zcat. Lalu di kode sumber mysqldump, --force membuat galat query tidak menghentikan program, sehingga komentar penutup tetap tertulis walaupun ada yang gagal. Pemeriksaan ini hanya bermakna tanpa --force.

Yang tercatat, dan yang tidak

Backup database sebelum update tercatat sebagai prod-db-20260829-preupdate.sql.gz, berukuran 32 MB dan berisi 112 tabel. Di sebelahnya ada arsip folder plugin lengkap sebelum update, 134 MB dengan 26.524 berkas, dan arsip plugin yang dihapus, 50 MB dengan 16.756 berkas. Semua backup itu diverifikasi bisa dibaca sebelum ada apa pun yang dihapus.

Ada batas yang perlu disebut terang-terangan. Catatan saya tidak menyimpan perintah persis yang menghasilkan dump 32 MB itu, tidak menyatakan dump itu dibuat dengan --skip-lock-tables, dan tidak menyatakan baris -- Dump completed diperiksa pada dump itu. Verifikasi yang tertulis untuknya hanya "bisa dibaca". Catatan itu juga tidak pernah menyebut grant RELOAD ditambahkan ke user database. Jadi yang terpasang di server itu adalah jalan memutar di sisi perintah backup, bukan perbaikan izin user database-nya.

Pembaruan yang dijaga backup itu

Backup-backup itu menjadi jalan kembali untuk pembaruan plugin yang dikirim satu per satu. Hasilnya 14 pembaruan selesai tanpa satu pun di-rollback, dua pembaruan lain sengaja ditahan, dan setiap pembaruan diverifikasi dulu sebelum yang berikutnya dikirim. Dua yang paling berisiko, advanced-custom-fields dan permalink-manager, dikerjakan paling akhir dan masing-masing sendirian. Permalink Manager karena URL di situs itu dipegang tema, sehingga lompatan versi plugin permalink bisa memindahkan semua URL. ACF karena tertinggal empat versi minor.

Verifikasinya berupa angka sebelum dan sesudah. ACF naik dari 6.4.1 ke 6.8.9, dan jumlah objek field group tetap 8 sementara referensi field tetap 401. Untuk Permalink Manager 2.5.2.1 ke 2.6.0, 50 permalink di 9 post type direkam sebelum dan sesudah, status code dan target redirect-nya identik, dan get_permalink() tidak berubah untuk ke-50-nya.

Berkas yang ada belum tentu backup

Stub 840 byte itu lolos dari pertanyaan paling malas yang bisa diajukan kepada sebuah backup, yaitu apakah berkasnya ada. Yang bisa membedakannya adalah pertanyaan tentang isi: apakah baris terakhirnya -- Dump completed, berapa tabel di dalamnya, dan apakah angka sebelum dan sesudah sebuah perubahan masih sama. Untuk wp db export di server itu, catatan saya menyimpan tiga aturan: pakai --skip-lock-tables, jangan tulis --no-tablespaces, dan pastikan baris terakhir dump berbunyi -- Dump completed. Aturan pertama ada harganya, yaitu dump yang tidak lagi berasal dari satu snapshot. Grant RELOAD yang menjadi akar masalahnya tidak diubah, dan itu layak diingat setiap kali backup berikutnya dibuat di server yang sama.