Kenapa Kamu Tidak Bisa Sekadar cp File-nya
Database SQLite adalah satu file, jadi menggoda untuk mem-backup-nya dengan menyalin file biasa. Kadang cara itu berhasil. Sering kali tidak.
Ada dua hal yang bisa salah:
- Koneksi lain sedang menulis saat kamu menyalin. File tujuan berakhir dengan transaksi yang baru setengah diterapkan, sehingga rusak saat dibuka.
- Database berada dalam mode WAL (bawaan di sebagian besar aplikasi modern). Perubahan terbaru disimpan di file terpisah
database.db-wal. Kalau hanya file utama yang disalin, kamu kehilangan data tanpa sadar.
SQLite menyediakan alat yang tepat untuk ini. Alat-alat itu menangani lock, isi WAL, dan penulis yang berjalan bersamaan tanpa kejutan. Pakai itu, bukan cp.
Perintah .backup
Cara tercepat untuk mem-backup database dari CLI adalah dot command .backup:
sqlite3 app.db
sqlite> .backup backup.db
sqlite> .quit
Perintah itu menulis salinan lengkap app.db ke backup.db. Ia tetap bekerja meskipun proses lain sedang membaca atau menulis database: backup API mengambil serangkaian lock kecil, bukan satu lock besar, menyalin page secara bertahap, dan mengulang penyalinan page yang berubah selama proses berlangsung.
Hasilnya adalah database SQLite yang bisa langsung dipakai. Buka seperti database lainnya:
sqlite3 backup.db
sqlite> .tables
Kamu juga bisa melakukan semuanya dalam satu perintah shell, yang biasanya menjadi bentuk akhir sebuah cron job:
sqlite3 app.db ".backup '/var/backups/app-$(date +%Y%m%d).db'"
Satu file masuk, satu file keluar. Tidak ada proses dump lalu restore, tidak ada parsing SQL: hanya page yang disalin di lapisan penyimpanan.
VACUUM INTO untuk Salinan yang Dipadatkan
VACUUM INTO adalah alat yang mirip tapi berbeda. Ia menulis salinan database yang baru dibangun ke file baru:
Hasilnya adalah database yang sama secara logis, tapi ditulis ulang dari awal: setiap page tersusun rapat, tanpa fragmentasi, tanpa page kosong sisa baris yang dihapus. Dengan begitu, file backup jadi sekecil mungkin.
Kapan memilih yang mana:
.backup: backup rutin yang sering. Lebih cepat, tidak bermasalah dengan penulis yang berjalan bersamaan, dan sama persis sampai ke byte.VACUUM INTO: snapshot berkala saat kamu juga ingin file yang rapi dan berukuran minimum. Lebih lambat karena menulis ulang semuanya, dan memegang write lock pada database sumber selama berjalan.
Keduanya menghasilkan file .db yang valid dan bisa langsung dibuka.
Online Backup API dari Kode Aplikasi
Di dalam aplikasi, kamu tidak memanggil sqlite3 lewat shell. Kamu memakai online backup API yang disediakan driver-mu. Di sqlite3 bawaan Python, itu adalah Connection.backup:
import sqlite3
source = sqlite3.connect("app.db")
dest = sqlite3.connect("backup.db")
with dest:
source.backup(dest)
source.close()
dest.close()
Method backup menyalin page dari source ke dest sementara koneksi lain tetap bekerja. Kamu juga bisa memberikan pages= untuk menyalin per potongan dan progress= untuk menerima callback. Ini berguna untuk database besar saat kamu ingin membatasi kecepatan penyalinan atau menampilkan progres.
Sebagian besar driver di bahasa lain menyediakan C API yang sama (sqlite3_backup_init, _step, _finish) dengan nama yang mirip. Polanya selalu sama: buka sumber, buka tujuan, salin page langkah demi langkah, selesaikan.
Backup Saat Database Sedang Dipakai
Di sinilah SQLite unggul tanpa banyak gembar-gembor. Baik .backup maupun online backup API dirancang untuk hot backup: database sumber boleh tetap terbuka dan aktif sepanjang waktu.
Yang sebenarnya terjadi:
- Backup mengambil shared lock dan mulai menyalin page.
- Kalau ada penulis yang mengubah page yang belum disalin, backup menyadarinya dan membacanya ulang.
- Penyalinan selesai begitu setiap page sudah konsisten.
Kamu tidak perlu menghentikan aplikasi, memutus koneksi, atau menjadwalkan downtime. Pada database yang sibuk, backup mungkin butuh beberapa putaran tambahan untuk tuntas, tapi pasti tuntas. File tujuan yang kamu dapat mewakili snapshot yang konsisten pada satu titik waktu.
Satu hal yang perlu diketahui: kalau kamu memakai mode WAL, sesekali jalankan PRAGMA wal_checkpoint(TRUNCATE); agar file WAL tidak membesar tanpa batas. Backup sendiri menangani WAL dengan benar; ini hanya kebiasaan baik umum untuk WAL.
Restore dari Backup
Me-restore database SQLite itu luar biasa membosankan, dan memang itulah intinya. File backup adalah database. Untuk memakainya, cukup buka:
sqlite3 backup.db
sqlite> SELECT COUNT(*) FROM notes;
Untuk me-restore di atas database yang sedang dipakai, misalnya untuk memulihkan data yang hilang, urutan yang aman adalah:
- Hentikan setiap proses yang membuka database.
- Hapus file
app.db,app.db-wal, danapp.db-shmyang ada. File WAL/SHM sisa dari database lama akan membingungkan SQLite kalau dipasangkan dengan file utama hasil restore. - Salin backup-mu ke tempatnya:
cp backup.db app.db. - Jalankan ulang aplikasimu.
File -wal dan -shm itu penting. Kalau kamu melewatkan langkah 2, SQLite mungkin mencoba menerapkan WAL usang di atas file utama hasil restore, dan kamu akan mendapat kerusakan atau data yang tercampur aneh.
Dari dalam CLI, ada juga perintah .restore, kebalikan dari .backup:
sqlite3 app.db
sqlite> .restore backup.db
sqlite> .quit
Perintah ini menimpa isi database yang sedang terhubung dengan isi backup.db. Ia memakai online backup API yang sama, hanya ke arah sebaliknya.
.dump Adalah Alat yang Berbeda
Kamu akan menemukan rujukan ke .dump di tutorial lama. Itu bukan backup dalam arti yang sama: ia menghasilkan file teks SQL berisi pernyataan CREATE dan INSERT:
sqlite3 app.db .dump > app.sql
Untuk me-restore, kamu menjalankan ulang SQL-nya:
sqlite3 new.db < app.sql
Ini berguna untuk migrasi antarversi SQLite, membandingkan skema di git, atau memindahkan data ke database engine lain. Cara ini lebih lambat, lebih besar, dan lebih berisiko kehilangan informasi dibanding .backup (collation kustom, generated column, dan beberapa pragma bisa butuh perhatian ekstra). Untuk backup sungguhan dari database yang sedang dipakai, pilih .backup atau VACUUM INTO.
Rutinitas Backup yang Masuk Akal
Untuk sebagian besar aplikasi, kombinasi ini bekerja dengan baik:
.backupyang dijadwalkan: per jam, per hari, sesuai seberapa banyak data yang boleh hilang. Murah, cepat, dan bisa berjalan saat database aktif.VACUUM INTOmingguan ke path terpisah. Menangkap penyimpangan, memberimu snapshot yang dipadatkan, dan menguji jalur kode yang berbeda.- Kebijakan retensi: simpan N backup harian terakhir dan M backup mingguan terakhir. Database SQLite bisa dikompresi dengan baik, jadi menjalankan
gzip backup.dbsetelahnya layak dilakukan. - Sesekali restore salah satu backup dan jalankan beberapa query terhadapnya. Backup yang belum pernah diuji hanyalah harapan, bukan backup.
# Daily, in cron:
sqlite3 /var/lib/app/app.db ".backup '/var/backups/app-$(date +%F).db'"
gzip "/var/backups/app-$(date +%F).db"
# Weekly:
sqlite3 /var/lib/app/app.db "VACUUM INTO '/var/backups/app-weekly-$(date +%F).db'"
Kedua perintah aman dijalankan saat aplikasi sedang melayani request.
Berikutnya: Pengaturan PRAGMA
Backup adalah salah satu urusan operasional, dan mengatur perilaku saat runtime adalah urusan lainnya. SQLite menyediakan pengaturannya lewat pernyataan PRAGMA: journal mode, tingkat synchronous, ukuran cache, penegakan foreign key. Halaman berikutnya membahas yang perlu kamu tahu.
Pertanyaan yang sering diajukan
Bagaimana cara backup database SQLite?
Dari CLI, jalankan .backup path/to/backup.db saat terhubung ke database sumber. Dari kode aplikasi, pakai online backup API (sqlite3_backup_init di C, atau padanannya di driver bahasa yang kamu pakai). Keduanya menghasilkan salinan yang konsisten meskipun koneksi lain sedang menulis.
Bisakah saya menyalin file .db begitu saja sebagai backup?
Hanya kalau kamu yakin tidak ada proses yang membuka database untuk menulis. Kalau tidak, kamu bisa menyalin file di tengah transaksi dan berakhir dengan backup yang rusak, atau kehilangan data yang masih ada di file WAL. Pakai .backup atau VACUUM INTO sebagai gantinya, karena keduanya menangani lock dan isi WAL dengan benar.
Apa bedanya .backup dan VACUUM INTO?
.backup memakai online backup API dan menghasilkan salinan yang sama persis sampai ke tingkat byte, termasuk page yang tidak terpakai. VACUUM INTO 'file.db' menulis salinan yang baru dipadatkan: lebih kecil dan bebas fragmentasi, tapi setiap page ditulis ulang. Pakai .backup untuk backup rutin, dan VACUUM INTO kalau kamu juga ingin mengambil kembali ruang.
Bagaimana cara restore database SQLite dari file backup?
Kalau backup-nya berupa file .db, cukup buka file itu, karena database SQLite adalah satu file. Untuk me-restore di atas database yang sudah ada, hentikan aplikasimu, ganti file-nya (dan hapus file -wal/-shm yang tersisa), lalu buka kembali. Dari CLI, kamu juga bisa menjalankan .restore path/to/backup.db saat terhubung ke database baru.