Cara menyalin database SQLite antar komputer dengan lebih cepat
(alexwlchan.net)- Saat memindahkan database SQLite besar dari server jarak jauh ke lokal, menyalin dump SQL yang dikompresi gzip alih-alih file
.dbasli dapat mengurangi jumlah data yang ditransfer secara signifikan - Penyebab utama membesarnya file adalah indeks; untuk mempercepat kueri, indeks menyimpan duplikasi data tabel sehingga meningkatkan penggunaan disk
.dumpSQLite mengubah database menjadi teks berisi pernyataan SQL, dan indeks direpresentasikan sebagai satu barisCREATE INDEX, bukan data indeks sebenarnya, sehingga menghindari transfer data duplikat- Pada contoh database, file asli berukuran 3,4GB, teks dump 1,3GB, dan hasil kompresi gzip 240MB; dump terkompresi 14 kali lebih kecil daripada file asli
- Membuat file dump tetap sebelum menyalin dapat mengurangi situasi ketika sumber berubah selama transfer dan memunculkan galat
database disk image is malformed
Menyalin dump SQLite yang dikompresi
- Untuk database SQLite kecil, menyalinnya langsung dengan
rsyncsudah cukup- Contoh:
rsync --progress username@server:my_remote_database.db my_local_database.db
- Contoh:
- Ketika database membesar, penyalinan langsung bisa menjadi lambat dan kurang stabil
- Mengunduh database 250MB dari server web lewat internet rumahan membutuhkan sekitar 1 menit
- Untuk database berukuran beberapa GB, waktu tunggunya akan lebih lama
- Bottleneck pertumbuhan ukuran ada pada indeks
- Indeks sangat meningkatkan kecepatan kueri, tetapi juga memperbesar file database
- Dalam satu database, satu indeks tertentu mengambil separuh ukuran disk
- Indeks mempercepat pencarian dengan menduplikasi data dari tabel lain, alih-alih menyimpan data unik
- SQLite dapat men-dump seluruh database menjadi file teks
- Perintah:
sqlite3 my_database.db .dump > my_database.db.txt - File hasilnya terdiri dari pernyataan SQL seperti
CREATE TABLE,INSERT INTO, danCREATE INDEX - Indeks disimpan sebagai perintah pembuatan seperti
CREATE INDEX [idx_photo_locations] ON [photos] ([longitude], [latitude]);, bukan sebagai data indeks sebenarnya
- Perintah:
- File dump dapat dipulihkan kembali menjadi database SQLite
- Perintah:
cat my_database.db.txt | sqlite3 my_reconstructed_database.db
- Perintah:
- Dump SQL memiliki banyak pengulangan, sehingga efisiensi kompresi gzip tinggi
- Contoh:
sqlite3 explorer.db .dump | gzip -c > explorer.db.txt.gz - Perbandingan ukuran:
- Database SQLite asli: 3,4GB
- Dump teks: 1,3GB
- Teks terkompresi gzip: 240MB
- Hasil kompresi gzip 14 kali lebih kecil daripada database SQLite asli
- Contoh:
Prosedur nyata dan efek terhadap stabilitas
- Metode baru ini membuat dump terkompresi di server jarak jauh, hanya mengunduh file tersebut, lalu merekonstruksi database secara lokal
- Prosedur penyalinannya sebagai berikut
- Membuat file teks terkompresi gzip di server:
ssh username@server "sqlite3 my_remote_database.db .dump | gzip -c > my_remote_database.db.txt.gz" - Menyalin ke lokal:
rsync --progress username@server:my_remote_database.db.txt.gz my_local_database.db.txt.gz - Menghapus file kompresi sementara di server:
ssh username@server "rm my_remote_database.db.txt.gz" - Mendekompresi di lokal:
gunzip my_local_database.db.txt.gz - Merekonstruksi database lokal:
cat my_local_database.db.txt | sqlite3 my_local_database.db - Menghapus file teks lokal:
rm my_local_database.db.txt
- Membuat file teks terkompresi gzip di server:
- Dump terkompresi berperan sebagai sumber salinan yang konsisten
- Jika sumber diperbarui saat database besar sedang disalin dalam waktu lama, bagian awal file hasil bisa berada dalam keadaan sebelum pembaruan sementara bagian akhirnya setelah pembaruan
- File seperti ini dapat memunculkan galat
database disk image is malformedsaat dibuka di lokal
- Jika dump teks dibuat sebelum penyalinan, file yang ditransfer oleh
rsynctidak berubah di tengah proses- Peluang menyalin file teks yang utuh dan konsisten menjadi lebih tinggi
- Jika Anda sering memindahkan database SQLite besar, metode ini dapat mengurangi waktu unduh dan meningkatkan keandalan penyalinan
1 komentar
Komentar Hacker News
Jika menyalin begitu saja file database yang sedang berjalan dan menerima pembaruan, tentu saja file itu pasti bisa rusak
Untuk mereplikasi database SQLite dengan aman, ada Litestream: https://github.com/benbjohnson/litestream
Ada layanan yang lebih disukai, tetapi server SFTP apa pun bisa digunakan
[1] https://github.com/benbjohnson/litestream/issues/140
[2] https://www.rsync.net/resources/notes/2021-q3-rsync.net_technotes.html
Tentu saja, jika ada beberapa file, semuanya harus dikelompokkan sebagai subvolume agar disalin dalam transaksi yang sama, dan sekadar
cp --reflink=alwayssaja tidak cukupMenghentikan proses dengan
SIGSTOPjuga bisa menghasilkan hasil serupa, tetapi saya tidak ingin bergantung pada itu.backupbawaan juga merupakan tool resmi yang dimaksudkan untuk membuat versi snapshot dari database yang sedang berjalan agar bisa disalinKalau ingatan saya benar, ia menulis panduan yang sempurna, tetapi peluang saya menemukannya nyaris 0. Semoga ada yang tahu referensi ini dan membagikan tautannya
Katanya menyalin database antar-komputer, tetapi rasanya seperti hanya menggambar lingkaran lalu melewatkan sisa burung hantunya
Seperti yang dikatakan orang lain,
rsyncinkremental akan jauh lebih cepat, tetapi yang lebih mengganggu adalah bagian yang mengatakan mengirim pernyataan SQL lebih cepat daripada mengirim database, sambil sepenuhnya melewatkan fakta bahwa pernyataan SQL itu harus dieksekusi. Setelah itu/optimize/juga harus dijalankan, dan/vacuum/jugaSaat ini saya punya situasi yang mengharuskan “membangun ulang secara inkremental” database dari file CSV, dan dalam kasus saya lebih optimal untuk membuatnya lagi dari awal. Meski sudah banyak dioptimalkan, memasukkan batch ke database kosong di memori lalu membuat indeks saja memakan waktu 30 menit
Itu tulisan yang merangkum dengan sangat baik cara melakukan insert cepat di SQLite3
Disebutkan bahwa mengunduh database 250MB butuh 1 menit; dengan koneksi fiber 1Gbps, saya mengambil database uji SQLite 2GB dari server perusahaan dalam 15 detik
Memang harus memakai trik seperti Bloom filter dan cache LRU, tetapi sekarang saya bisa membuat database 6GB dalam sekitar 20 menit
SQLite punya tool resmi untuk tujuan ini: https://www.sqlite.org/rsync.html
Bekerja pada level halaman
“Protokolnya adalah replika mengirim hash kriptografis dari setiap halaman ke sisi asal, lalu asal mengirim kembali seluruh isi halaman yang hash-nya tidak cocok”
Satu-satunya cara untuk memakai tool ini adalah mengompilasinya sendiri
Utilitas
sqlite_rsyncyang baru dirilis menggunakan versi algoritma rsync yang dioptimalkan sesuai struktur internal database SQLite. Ia membandingkan halaman data internal secara efisien, lalu hanya menyinkronkan halaman yang berubah atau tidak adaTips di artikelnya juga bagus, tetapi sekarang lebih mudah memakai utilitas bawaan :)
Saya menulis detail cara kerjanya di blog: https://nochlin.com/blog/how-the-new-sqlite3_rsync-utility-works
sqlite3_rsyncsekarang sudah terintegrasi di platform rsync.netssh user@rsync.net sqlite3_rsync … blah blah …Baru ditambahkan minggu lalu dan belum digelar ke semua region, tetapi para pengguna awal mengatakan semuanya berjalan persis seperti yang diharapkan
sqlite_rsynchanya bisa digunakan dalam mode WAL. Sebagai batasan tambahan mode WAL, file database harus berada di disk lokalKebanyakan orang tentu begitu, tetapi jika itu tidak memungkinkan, utilitas ini tidak bisa digunakan
Saya cukup heran
rsynctidak mencoba kompresi saat transfer yang memang disediakan-z, --compressmengompresi data file selama transfer, dan--compress-level=NUMmenetapkan level kompresi secara eksplisitMungkin mengompres dengan gzip lalu mentransfernya bisa lebih cepat, tetapi bagus bahwa transfer bisa diperbaiki hanya dengan satu flag
sqlite3_rsync(https://sqlite.org/rsync.html) bersama-zArtinya rsync yang memahami transaksi SQLite dan WAL ditambah kompresi saat transfer
Saat melakukan hal seperti ini, saya biasanya men-stream dump langsung ke gzip. Biasanya juga bisa ditemukan cara untuk men-stream langsung ke tujuan tanpa file perantara
Selain itu, dengan cara ini data tersimpan dalam keadaan terkompresi di tujuan. Terutama jika tujuannya backup, bukan replikasi ala orang miskin
rsyncpada file terkompresi, Anda sepenuhnya melewati inti penggunaanrsync, yaitu transfer perbedaan berbasis rolling checksum--remove-source-filessehingga tidak perlu masuk lagi lewat SSH untuk menjalankanrmMenyimpan sebagai file teks itu tidak efisien. Saya menyimpan database SQLite dengan
VACUUM INTOsqlite3 -readonly /path/db.sqlite "VACUUM INTO '/path/backup.sqlite';"Menurut https://sqlite.org/lang_vacuum.html, perintah
VACUUMdengan klausaINTOadalah alternatif untuk backup API yang membuat salinan cadangan dari database yang sedang berjalan. Keuntungannya adalah ukuran database backup yang dihasilkan diminimalkan sehingga I/O filesystem bisa berkurangVACUUM INTOmempertahankan indeksFile teks mungkin tidak efisien apa adanya, tetapi bisa dikompresi dengan baik bahkan dengan alat sederhana seperti gzip. Saya tidak yakin format biner SQLite juga bisa dikompresi sebaik itu, meski mungkin saja
Di DuckDB, Anda bisa melakukan hal yang sama tetapi mengekspor ke Parquet, sehingga ukuran data menjadi lebih kecil hingga satu orde besaran dibanding pernyataan SQL berbasis teks. Transfernya juga lebih cepat dan pemuatannya juga lebih cepat
https://duckdb.org/docs/stable/sql/statements/export.html
duckdb -c "attach 'sqlite-database.db' as db; copy db.table_name to 'table_name.parquet' (format parquet, compression zstd)"Pada database pengujian saya, ukurannya sekitar 20% lebih kecil daripada pernyataan SQL teks yang dikompresi dengan gzip
SQLite memiliki ekstensi session yang melacak perubahan pada sekumpulan tabel dan membuat changeset/patchset yang dapat diterapkan ke versi lama database SQLite
https://www.sqlite.org/sessionintro.html
Di salah satu proyek saya, saya mengimplementasikan sendiri session ala orang miskin dengan mencatat semua pernyataan SQL dan parameternya ke database terpisah, lalu menyinkronkan dan memutarnya ulang. Ini bekerja cukup baik untuk database sekitar 30GB yang hanya berubah sekitar 0,1% per hari
Jika menyinkronkan secara berkala dari versi lama ke versi baru, ini bisa dioptimalkan lagi dengan opsi
--rsyncablepada gzipRasio kompresi turun sekitar 1%, tetapi perbedaan dari satu versi ke versi berikutnya menjadi terlokalisasi dan tidak merambat ke seluruh output terkompresi
Cara lain adalah tidak mengompresi output dump, membiarkan
rsyncmenghitung perbedaan antara dump lama yang tidak terkompresi dan dump saat ini, lalu hanya mengompresi kumpulan perubahan yang dikirim lewat jaringan. Dengan kata lain, gunakanrsync -zSepertinya ini bisa ditangani sebagai satu pipeline
ssh username@server "sqlite3 my_remote_database.db .dump | gzip -c" | gunzip -c | sqlite3 my_local_database.db-oCompression=onatau-C,gzip/gunzipmungkin menjadi duplikasipvseharusnya masuk di sana