SQLite bilang Production Database sa Isang VPS
Alamin kung kailan sapat ang SQLite sa isang VPS, paano gamitin ang WAL at busy_timeout, at bakit kailangan ang Litestream bago sumablay ang one-writer limit.
Kailan tamang production database ang SQLite sa isang VPS
Ang paggamit ng SQLite sa production sa isang VPS ang tamang pagpili para sa karamihan ng maliliit na application. Simple ang dahilan: ang isang process sa isang machine na nagsusulat sa isang file ay hindi nangangailangan ng database server. Walang daemon na kailangang pangasiwaan, walang port na kailangang protektahan gamit ang firewall, walang password na kailangang palitan, at walang pangalawang machine na kailangang panatilihing gumagana. Ang query ay function call sa halip na network round trip. Kaya ang page na nagpapatakbo ng 40 query ay nagkakahalaga ng 40 function call.
Makitid ngunit totoo ang limitasyon. Isang writer lang ang pinapayagan ng SQLite sa buong database file sa bawat pagkakataon, at hindi maaaring i-share ang file sa pagitan ng dalawang machine. Parehong ayos ang mga limitasyong ito para sa isang VPS na nagpapatakbo ng isang application. Pareho rin itong nagiging kritikal kapag lumampas ka na sa ganitong setup. Saklaw ng gabay na ito ang mga setting na ginagawang ligtas ang SQLite sa isang server, ang tuloy-tuloy na backup gamit ang Litestream, at ang punto kung kailan ka dapat huminto sa paggamit nito.
I-install muna ang command line tool. Lahat ng nasa ibaba ay pinatakbo sa Ubuntu 24.04.
sudo apt update
sudo apt install -y sqlite3
sqlite3 --versionMagpi-print ito ng bersyong nagsisimula sa 3., na sinusundan ng build date at source hash. Ang Ubuntu 24.04 ay may SQLite 3.45.1 noong July 2026. Malamang na hindi ginagamit ng iyong application ang binary na ito. Karamihan sa language runtime ay may sarili nitong kopya ng SQLite library, na kadalasang mas bago. Kaya tingnan muna ang bersyong iniuulat ng iyong database driver bago umasa sa isang bagong feature.
Bakit ang WAL mode ang unang binabago
Bilang default, gumagamit ang SQLite ng rollback journal. Bago baguhin ang isang page, kinokopya nito ang orihinal na page sa isang -journal file, saka ine-edit ang database nang direkta. Para magawa ito nang ligtas, kumukuha ito ng exclusive lock sa buong file. Dahil dito, naghihintay ang bawat reader habang may isinasagawang write. Sa laptop, karaniwang hindi ito napapansin. Sa web server, isang mabagal na write ang nakakapagpatigil sa bawat request na gumagamit ng database.
Binabaligtad ng WAL (write-ahead log) mode ang pagkakasunod-sunod. Idinadagdag ng writer ang mga bagong page sa hiwalay na -wal file at hindi nito binabago ang pangunahing database. Patuloy na nagbabasa ang mga reader mula sa pangunahing file batay sa snapshot na nakuha nila sa pagsisimula. Dahil dito, hindi hinaharangan ng mga reader ang writer, at hindi rin hinaharangan ng writer ang mga reader. Kalaunan, kinokopya ng isang checkpoint ang naipong WAL pages pabalik sa pangunahing database. Ang pagbabagong ito ang pangunahing dahilan kung bakit nagagamit nang maayos ang SQLite sa likod ng isang web application.
I-on ang WAL mode at kumpirmahing nanatili ito
mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"Ipi-print ng command ang wal. Hindi palamuti ang output na iyon. Ibinabalik ng PRAGMA journal_mode ang aktuwal na mode ng database, kaya ang sagot na delete ay nangangahulugang nabigo ang pagbabago at nasa rollback journal ka pa rin.
Persistent ang WAL mode. Isa itong flag sa database header, hindi setting ng connection. Kaya isang beses mo lang itong patakbuhin para sa bawat database file, at mamamana ito ng lahat ng kasunod na connection, pati pagkatapos ng reboot. Patunayan ito gamit ang bagong connection.
sqlite3 ~/app/app.db "PRAGMA journal_mode;"Ngayon, gumawa ng table at tingnan kung ano ang lumilitaw sa disk.
sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/Tatlo na ang file: app.db, app.db-wal at app.db-shm. Ang -wal file ay naglalaman ng mga committed page na hindi pa na-checkpoint. Ang -shm file ay isang shared memory index na mina-map ng bawat connection upang magkasundo ang lahat kung ano ang nasa WAL. Parehong bahagi ng database ang mga ito at hindi scratch file. Kung kokopyahin mo nang mag-isa ang app.db habang tumatakbo ang application, makakakuha ka ng file na kulang sa lahat ng kamakailang commit. Kung ide-delete mo ang app.db at iiwan ang dalawa pang file, ia-apply ng SQLite ang mga lumang WAL page sa anumang bagong file na lilitaw sa ilalim ng pangalang iyon. Sa ganitong paraan nasisira ng mga tao ang isang bagong database habang sinusubukang i-reset ito.
Mga setting ng koneksyon na kailangan ng bawat production app
journal_mode lamang ang iniimbak sa database. Ang lahat ng iba pang setting sa ibaba ay para sa bawat koneksyon. Ibig sabihin, kailangang patakbuhin ng iyong application ang mga ito sa bawat koneksyong binubuksan nito, kasama ang bawat koneksyong ginagawa ng pool sa background.
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;Sinasabi ng busy_timeout = 5000 sa SQLite na patuloy na subukang i-access ang naka-lock na database nang hanggang 5000 milliseconds bago nito ibalik ang database is locked. Ang default ay 0. Kaya bilang default, agad na nabibigo ang SQLite sa unang pagkakataong magsabay ang dalawang writer. Inaalis ng pagtatakda ng nag-iisang value na ito ang karamihan sa mga lock error na ibinibintang sa SQLite.
Ang synchronous = NORMAL ang tamang setting sa WAL mode, at mahalagang maunawaan ang trade-off. Sa FULL, tumatawag ang SQLite ng fsync sa WAL sa bawat commit. Sa NORMAL, nagsi-sync ito sa mga checkpoint. Malinaw ang dokumentasyon ng SQLite tungkol sa isinusuko mo: hindi na durable ang mga transaction pagkatapos ng power failure o hard reset. Hindi masisira ang database dahil sa pagkawala ng kuryente; mawawala lamang ang mga huling commit na hindi pa naisusulat sa disk. Sa isang VPS, karaniwan itong tamang trade-off dahil inaalis nito ang isang fsync sa proseso ng bawat write.
Naka-off bilang default ang foreign_keys = ON para sa backwards compatibility, at para ito sa bawat koneksyon. Walang ipinapatupad ang schema na puno ng mga clause na REFERENCES hangga't hindi ito ina-activate ng bawat koneksyon.
May isa pang setting na magiging mahalaga sa susunod. Awtomatikong nagsasagawa ng checkpoint ang SQLite kapag lumampas sa 1000 pages ang laki ng WAL. Ginagawa ito ng koneksyong nagkataong makatapos ng transaction sa sandaling iyon. Maayos ito kapag mag-isa. Nagiging usapin ito kapag tumatakbo ang Litestream dahil nais ng Litestream na kontrolin kung kailan isinasagawa ang mga checkpoint.
Bakit nangyayari pa rin ang database is locked pagkatapos itakda ang busy_timeout
Ito ang error na nagtutulak sa mga tao na bumalik sa Postgres, at mayroon itong isang partikular na sanhi.
Nag-i-install ang busy timeout ng busy handler, pero hindi ginagarantiya ng SQLite na tatawagin nito ang handler.
Kung matukoy ng SQLite na maaaring magresulta sa deadlock ang pagtawag sa busy handler, ibabalik nito ang SQLITE_BUSY sa application sa halip na tawagin ang busy handler.
Nangyayari ang iniiwasang deadlock kapag nag-u-upgrade ang isang transaction. Ang isang standalone na BEGIN sa SQLite ay nangangahulugang BEGIN DEFERRED. Kung ang unang statement pagkatapos nito ay SELECT, nasa read transaction ka. Kapag kailangan ng isang susunod na UPDATE sa parehong transaction na maging write transaction, at may ibang connection na nagsulat mula nang magsimula ang iyong read, hindi ka maaaring paghintayin ng SQLite. Luma na ang iyong snapshot, at ang paghihintay ay magdudulot lamang ng deadlock sa dalawang connection. Direktang inilalahad ng documentation ang magiging resulta:
Iu-upgrade ng mga kasunod na write statement ang transaction sa isang write transaction kung maaari, o ibabalik ang SQLITE_BUSY.
Hindi kailanman ginagamit ang iyong 5000 millisecond timeout. Agad dumarating ang error, kaya mukhang walang epekto ang setting.
Isang salita ang solusyon.
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;Kinukuha ng BEGIN IMMEDIATE ang write lock sa simula, bago magbasa ng anuman. Walang upgrade na nagaganap, kaya walang deadlock na kailangang iwasan. Dahil dito, gumagana ang busy handler at naghihintay ang connection sa turno nito sa halip na mabigo. Panatilihing deferred ang mga read-only transaction. Dapat immediate ang anumang transaction na naglalaman ng write.
Mas mahirap mapansin ang ikalawang sanhi ng mga lock error: ang pagpapanatiling bukas ng write transaction habang isinasagawa ang mabagal na trabaho. Nagsi-serialize ang SQLite ng mga writer, kaya haharangin ng transaction na magbubukas, tatawag sa external API sa network, at pagkatapos ay magko-commit ang lahat ng iba pang writer sa buong tagal ng tawag na iyon. Basahin ang kailangan mo, isara ang transaction, isagawa ang mabagal na trabaho, at pagkatapos ay magbukas ng maikling write transaction para i-store ang resulta.
Tuloy-tuloy na backup gamit ang Litestream
Ang nightly copy ay maaaring mawalan ng hanggang isang araw na writes, at ang pagpapatakbo ng cp laban sa isang live na SQLite database ay maaaring lumikha ng copy na hindi magbubukas. Dalawang paraan ang ligtas. Ginagamit ng sqlite3 app.db ".backup /path/to/backup.db" ang online backup interface ng SQLite at gumagana ito laban sa database na ginagamit. Mas malayo ang kayang gawin ng Litestream: mino-monitor nito ang WAL at tuloy-tuloy na ina-upload ang mga pagbabago sa object storage. Dahil dito, mula isang araw ay nagiging humigit-kumulang isang segundo ang worst-case data loss.
Ang Litestream ay isang Go binary na tumatakbo katabi ng iyong application. Hindi ito nasa pagitan ng app at database. Gaya ng dati, nagsusulat ang iyong application sa SQLite, habang binabasa ng Litestream ang WAL at ina-upload ang mga nabago.
cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream versionAng v0.5.14 ang release na nakadokumento sa opisyal na Linux install page noong July 2026, at sinundan ito ng v0.5.15 noong 21 July 2026. Palitan ang version sa dalawang linya upang tumugma sa kasalukuyang tag sa releases page. Gamitin ang katugmang arm64 package kung arm64 ang iyong VPS.
Nasa /etc/litestream.yml ang configuration file. Magsimula sa local file replica. Pinatutunayan nito ang buong proseso nang hindi nangangailangan ng cloud credentials.
dbs:
- path: /home/appuser/app/app.db
replica:
type: file
path: /var/backups/litestream/appTandaan na replica ang field at singular ito. Pinalitan ng Litestream 0.5 ang replicas array mula sa 0.3 series ng isang replica block. Mabibigo na sa startup ang configuration na may dalawang entry. Marami pa ring third-party guide ang nagpapakita ng lumang array, kaya gamitin ang structure sa itaas sa halip na ang unang halimbawang makita sa search. Pinalitan din ng 0.5 series ang litestream wal subcommand ng litestream ltx dahil nagbago ang on-disk backup format.
Suriin muna kung tama ang pag-parse ng config bago mo paganahin ang anumang bagay.
sudo litestream databases -config /etc/litestream.ymlPagkatapos, manu-manong patunayan ang round trip. Nilalaktawan ng form na ito ang config file at nire-replicate ang isang database sa isang path.
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/appTumatakbo ito sa foreground at patuloy na tumatakbo. Sa pangalawang shell, magsulat ng row at i-restore ang replica sa bagong file.
sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"Kasama sa count ang bagong row. Kung hindi ito kasama, hindi pa nagsi-sync ang pagbabago. Nagpu-push ang Litestream sa isang sync-interval na default na 1 second, kaya maghintay at mag-restore muli. Ang isang segundo ring iyon ang iyong recovery point. Kapag nag-crash ang system, pinakamalaki ang mawawala ay ang writes mula sa huling sync interval. Walang configuration na makapagbibigay ng zero data loss.
Para sa production storage, palitan ang replica block ng S3 URL. Gumagana ito sa Amazon S3 at sa S3-compatible object storage mula sa ibang providers.
dbs:
- path: /home/appuser/app/app.db
replica:
url: s3://your-bucket-name/app
region: us-east-1
snapshot:
interval: 24h
retention: 24hHuwag ilagay ang credentials sa file na iyon. Binabasa ng Litestream ang LITESTREAM_ACCESS_KEY_ID at LITESTREAM_SECRET_ACCESS_KEY mula sa environment. Ilagay ang mga ito sa systemd drop-in na pagmamay-ari ng root at may mode 600.
Ang mga snapshot value sa itaas ang defaults, at maaaring makagulat ang default na retention. Ang retention ay kung gaano katagal pinananatili ng Litestream ang mga snapshot at ang mga file na kabilang sa mga ito. Tinutukoy rin nito kung gaano kalayo sa nakaraan ang maaari mong i-restore. Kapag 24 hours ang itinakda, hindi na mare-recover mula sa state noong Monday ang isang masamang migration na napansin mo noong Wednesday morning. Itakda ang retention: 168h sa isang linggo at tanggapin ang dagdag na storage cost.
Patunayan ang restore bago mo ito kailanganin
litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"Kapag ibinigay ang path ng database, hinahanap ng litestream restore ang katugmang replica sa /etc/litestream.yml at dina-download ito. Ipinapakita ng PRAGMA integrity_check ang ok para sa maayos na file. Ang anumang ibang output ay nangangahulugang hindi magagamit ang na-restore na kopya. Patakbuhin ito nang nakaiskedyul gamit ang isang systemd service at timer at basahin ang output. Hangga't hindi ka nakakapag-restore ng backup kahit isang beses, hindi mo alam kung gumagana ito.
Patakbuhin ang Litestream sa ilalim ng systemd
Ini-install ng Debian package ang isang litestream unit na nagbabasa ng /etc/litestream.yml.
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -fSa normal na output, inililista ang bawat database mula sa config at pagkatapos ay walang ibang ipinapakita maliban sa mga pana-panahong sync line. Ang error na no such file or directory para sa path ng iyong database ay nangangahulugang mali ang path sa config, o hindi ito mabasa ng process. Bilang default, root ang account na ginagamit ng unit, na mas mataas na privilege kaysa sa kailangan ng job na ito. Kailangang mabasa at maisulat ng Litestream ang database at ang directory na kinalalagyan nito, dahil gumagamit ito ng mga file na -wal at -shm na nasa tabi ng iyong database. Kaya gamitin ang account na ginagamit na ng iyong application.
# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuserIlapat ito gamit ang sudo systemctl daemon-reload at sudo systemctl restart litestream. Ilang minuto lang ang kailangan para mag-set up ng dedicated service account na may pinakamababang privilege, at ito ang pagkakaiba sa pagitan ng backup agent at pangalawang root process sa server.
Mahalaga ang isang detalye sa pagkakasunod-sunod kung bubuuin mong muli ang machine mula sa simula. Dapat ma-restore ang database bago magsimula ang application. Tumatanggap ang litestream restore ng -if-db-not-exists, na nag-e-exit na may code 0 kapag naroon na ang file, kaya ligtas itong patakbuhin sa bawat boot. Ilagay ito sa isang ExecStartPre line sa unit ng iyong application. Sa bagong VPS, ida-download ang database habang walang gagawin ang kasalukuyang instance. May katumbas na -restore-if-db-not-exists flag ang litestream replicate kung mas nais mong ilagay ang setting sa iisang lugar.
Mga limitasyon ng SQLite sa isang VPS
Mga network filesystem. Ito ang limitasyong hindi malulutas sa pamamagitan ng configuration. Kinakailangan ng WAL mode na magbahagi ang bawat process na gumagamit ng database ng maliit na bahagi ng memory, na ibinibigay ng -shm file. Malinaw ang tuntunin sa dokumentasyon ng SQLite:
Dapat nasa iisang host computer ang lahat ng process na gumagamit ng database; hindi gumagana ang WAL sa network filesystem.
Dahil dito, maaaring ma-corrupt ang database na nasa naka-mount na NFS (network file system) o SMB share, at walang pragma na makapipigil dito. May mahalagang pagkakaiba rito na madalas hindi napapansin. Ang network block device, na karaniwang ikinakabit ng mga VPS provider bilang karagdagang storage, ay lumilitaw sa Linux bilang ordinaryong disk na may ordinaryong filesystem, kaya maayos ito. Ang naka-mount na file share ay hindi.
Ikalawang application server. Walang setting na magpapagana rito. Kapag kailangan nang maghatid ng parehong data ang dalawang machine, kailangan mo ng database na nakikipag-ugnayan sa network. Pagpasyahan ang paglipat na ito habang may sapat ka pang oras para magplano.
Mga workload na maraming write. Katangian ng file format ang isang writer lang sa bawat pagkakataon; hindi ito configurable. Mura ang maiikling write dahil bawat commit ay append sa WAL, kaya mas nakadepende ang throughput sa latency ng disk para sa maliliit na write kaysa sa CPU. Tingnan ang NVMe kumpara sa SATA SSD storage sa isang VPS para makita ang epekto ng pagkakaibang ito. Ang mahahabang transaction ang tunay na problema dahil pinipila ng mga ito ang lahat ng iba pang writer.
Mga analytical query. Ang SQLite ay isang row store na ginawa para sa mga transaction. Iba ang trabaho ng dashboard na nag-i-scan ng isandaang milyong row at nangangailangan ito ng ibang tool. Ipinapaliwanag ng DuckDB kumpara sa SQLite para sa server work kung saan nagkakaroon ng ganitong pagkakaiba.
VACUUM sa panahon ng replication. Ganap na nire-rewrite ng isang full VACUUM ang buong database file. Dahil dito, kailangang i-upload muli ng Litestream ang lahat, at hindi inirerekomenda ng dokumentasyon ng Litestream na patakbuhin ito habang aktibo ang replication. Ihinto ang replicator, isagawa ang vacuum, patakbuhin itong muli, at asahang magkakaroon ng bagong full snapshot.
Dalawang replicator sa iisang database. Huwag kailanman magpatakbo ng dalawang Litestream process laban sa parehong database o sa parehong replica destination. Malinaw sa dokumentasyon na responsibilidad mong pigilan ito, at ang magiging resulta ay replica na hindi mo maibabalik.
Mga hindi saklaw ng Litestream
Pinoprotektahan ng Litestream ang database file lamang. Kailangan mo pa ring pangasiwaan ang mga na-upload na file, configuration ng application, TLS (transport layer security) certificates, at mga unit file. Ipares ito sa mga naka-schedule na encrypted off-box backup gamit ang restic upang masaklaw ang dalawang bahaging ito. Kung bago ang machine, saklaw ng unang sampung minuto sa bagong VPS ang user account at firewall na ipinapalagay ng gabay na ito na natapos na.
FAQ
Sapat na ba ang SQLite para sa isang production application?
Para sa isang application sa isang server, oo, basta i-enable mo ang WAL mode, magtakda ng busy timeout, at patuloy itong i-back up. Ang mahahalagang limitasyon ay nasa structure: isang writer lang bawat pagkakataon, at isang host machine lang. Ang application na pasok sa mga limitasyong ito ay magkakaroon ng database na walang network hop at walang hiwalay na process na kailangang i-monitor. Ang application na hindi pasok dito ay nangangailangan ng client-server database, at walang tuning na makapagbabago nito.
Bakit nakakakuha pa rin ako ng database is locked pagkatapos itakda ang busy_timeout?
Dahil nilalaktawan ng SQLite ang busy handler kapag maaaring magdulot ng deadlock ang paghihintay. Ang transaction na nagsisimula sa isang bare BEGIN ay deferred: inilalagay ito ng pambungad na SELECT sa isang read transaction, at kailangang mag-upgrade ng kasunod na write. Kung may ibang connection na nagsulat sa pagitan, ibinabalik agad ng SQLite ang SQLITE_BUSY sa halip na tawagin ang busy handler mo, dahil luma na ang read snapshot mo. Simulan sa BEGIN IMMEDIATE ang anumang transaction na magsasagawa ng write upang makuha agad ang write lock at maipatupad ang timeout.
Maaari ko bang ilagay ang SQLite database sa network storage?
Hindi sa network filesystem gaya ng NFS o SMB. Kailangan ng WAL mode na magbahagi ang lahat ng process ng memory sa pamamagitan ng -shm file, at sinasabi ng dokumentasyon ng SQLite na dapat nasa iisang host computer ang bawat process na gumagamit ng database. Ibang bagay ang network block device na naka-attach ng provider mo: normal na disk na may normal na filesystem ang nakikita ng Linux, at gumagana roon ang SQLite.
Kailangan ko ba ang Litestream kung nagpapatakbo na ako ng nightly backups?
Depende ito sa dami ng data na kaya mong mawala. Ang nightly job ay nangangahulugang maaari kang mawalan ng hanggang dalawampu't apat na oras ng mga write. Nagsi-sync ang Litestream humigit-kumulang isang beses bawat segundo, kaya kapag nag-crash, karaniwang huling isang segundo lang ang mawawala. Mas ligtas din ito kaysa kopyahin ang database file gamit ang cp, na maaaring makakuha ng database habang may isinusulat dito. Database lang ang saklaw ng Litestream, kaya panatilihing tumatakbo ang isang pangkalahatang file backup kasabay nito.