SQLite in productie op een VPS: instellingen en grenzen
Ontdek waarom SQLite voor kleine apps op één VPS vaak volstaat, met WAL, busy_timeout, Litestream-replicatie en duidelijke grenzen voor opschaling.
Wanneer SQLite de juiste productiedatabase op een VPS is
SQLite in productie op een VPS gebruiken is voor de meeste kleine toepassingen de juiste keuze. De reden is eenvoudig: één proces op één machine dat naar één bestand schrijft, heeft geen databaseserver nodig. Er is geen daemon die u moet bewaken, geen poort die u met een firewall moet beveiligen, geen wachtwoord dat u moet rouleren en geen tweede machine die beschikbaar moet blijven. Een query is een functieaanroep en geen netwerkroundtrip. Een pagina die veertig queries uitvoert, kost u dus veertig functieaanroepen.
De beperking is duidelijk en reëel. SQLite staat voor het hele databasebestand slechts één schrijver tegelijk toe. Het bestand kan ook niet tussen twee machines worden gedeeld. Beide beperkingen zijn geen probleem voor één VPS waarop één toepassing draait. Zodra u deze opzet ontgroeit, vormen beide beperkingen een onoverkomelijk probleem. In deze handleiding behandelen we de instellingen die SQLite veilig maken op een server, continue back-ups met Litestream en het punt waarop u moet stoppen.
Installeer eerst het opdrachtregelprogramma. Alles hieronder is uitgevoerd op Ubuntu 24.04.
sudo apt update
sudo apt install -y sqlite3
sqlite3 --versionDit geeft een versienummer weer dat begint met 3., gevolgd door een builddatum en een source hash. Ubuntu 24.04 levert vanaf juli 2026 SQLite 3.45.1. Uw toepassing gebruikt dit binaire bestand waarschijnlijk niet. De meeste taalruntimes bundelen hun eigen kopie van de SQLite-library, vaak een nieuwere versie. Controleer daarom eerst welke versie uw databasestuurprogramma rapporteert voordat u op een recente functie vertrouwt.
Waarom WAL-modus het eerste is wat u wijzigt
SQLite gebruikt standaard een rollbackjournal. Voordat SQLite een pagina wijzigt, kopieert het de oorspronkelijke pagina naar een -journal-bestand en bewerkt het daarna de database op zijn plaats. Hiervoor wordt het hele bestand veilig vergrendeld met een exclusieve lock. Daardoor moet elke lezer wachten zolang er een schrijfbewerking actief is. Op een laptop merkt niemand dat. Op een webserver vertraagt één trage schrijfbewerking elk verzoek dat de database gebruikt.
De WAL-modus (write-ahead log) draait deze volgorde om. Een schrijver voegt de nieuwe pagina's toe aan een afzonderlijk -wal-bestand en laat de hoofddatabase ongewijzigd. Lezers blijven het hoofdbestand lezen vanuit de snapshot waarmee ze zijn gestart. Daardoor blokkeren lezers de schrijver niet en blokkeert de schrijver de lezers niet. Later kopieert een checkpoint de verzamelde WAL-pagina's terug naar de hoofddatabase. Deze ene wijziging maakt SQLite voor het grootste deel geschikt voor gebruik achter een webapplicatie.
WAL-modus inschakelen en controleren of deze behouden blijft
mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"De opdracht geeft wal weer. Die uitvoer is niet alleen informatief. PRAGMA journal_mode geeft de modus terug waarin de database zich daadwerkelijk bevindt. Een antwoord van delete betekent dus dat de wijziging is mislukt en dat nog steeds het rollback-journal wordt gebruikt.
De WAL-modus blijft behouden. Het is een vlag in de databaseheader en geen verbindingsinstelling. U hoeft deze daarom slechts eenmaal per databasebestand in te stellen. Elke volgende verbinding neemt de instelling over, ook na een herstart. Bewijs dit met een nieuwe verbinding.
sqlite3 ~/app/app.db "PRAGMA journal_mode;"Maak nu een tabel en bekijk wat er op de schijf verschijnt.
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/Er zijn nu drie bestanden: app.db, app.db-wal en app.db-shm. Het bestand -wal bevat gecommitte pagina's die nog niet zijn gecontroleerd. Het bestand -shm is een gedeelde-geheugenindex die elke verbinding in kaart brengt, zodat alle verbindingen dezelfde inhoud van de WAL zien. Beide bestanden horen bij de database en zijn geen tijdelijke bestanden. Als u alleen app.db kopieert terwijl de applicatie actief is, krijgt u een bestand waarin elke recente commit ontbreekt. Als u app.db verwijdert en de andere twee bestanden laat staan, past SQLite die verouderde WAL-pagina's toe op elk nieuw bestand dat onder die naam verschijnt. Zo beschadigen mensen een nieuwe database wanneer ze een database proberen te resetten.
De verbindingsinstellingen die elke productieapplicatie nodig heeft
Alleen journal_mode wordt in de database opgeslagen. Elke andere instelling hieronder geldt per verbinding. Uw applicatie moet deze daarom uitvoeren op elke verbinding die wordt geopend, inclusief elke verbinding die een pool op de achtergrond aanmaakt.
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;busy_timeout = 5000 zorgt ervoor dat SQLite maximaal 5000 milliseconden blijft proberen bij een vergrendelde database voordat het database is locked retourneert. De standaardwaarde is 0. SQLite faalt daarom standaard onmiddellijk wanneer twee schrijfbewerkingen elkaar voor het eerst overlappen. Met deze ene instelling verdwijnen de meeste vergrendelingsfouten die aan SQLite zelf worden toegeschreven.
synchronous = NORMAL is de juiste instelling in WAL mode. Het is belangrijk om de afweging te begrijpen. Bij FULL roept SQLite bij elke commit fsync aan op de WAL. Bij NORMAL synchroniseert SQLite tijdens checkpoints. De documentatie van SQLite is duidelijk over wat u hiermee opgeeft: transacties zijn niet langer persistent na een stroomstoring of een harde reset. De database kan door dat stroomverlies niet beschadigd raken. U verliest alleen de laatste commits die nog niet naar de schijf waren geschreven. Op een VPS is dat meestal de juiste afweging, omdat een fsync daarmee niet meer bij elke afzonderlijke schrijfbewerking wordt uitgevoerd.
foreign_keys = ON is standaard uitgeschakeld voor achterwaartse compatibiliteit en geldt per verbinding. Een schema vol met REFERENCES-clausules handhaaft helemaal niets totdat elke verbinding deze instelling inschakelt.
Eén andere instelling wordt pas later relevant. SQLite voert automatisch een checkpoint uit zodra de WAL groter wordt dan 1000 pagina's. De verbinding die op dat moment toevallig een transactie voltooit, voert dit werk uit. Op zichzelf is dat geen probleem. Het wordt wel een aandachtspunt wanneer Litestream actief is, omdat Litestream controle wil hebben over het moment waarop checkpoints plaatsvinden.
Waarom database is locked nog steeds optreedt nadat u busy_timeout hebt ingesteld
Dit is de fout waardoor mensen teruggaan naar Postgres. Er is één specifieke oorzaak.
Een busy-timeout installeert een busy handler, maar SQLite garandeert niet dat deze wordt aangeroepen.
Als SQLite bepaalt dat het aanroepen van de busy handler tot een deadlock kan leiden, retourneert SQLite SQLITE_BUSY aan de applicatie in plaats van de busy handler aan te roepen.
De deadlock die SQLite hiermee voorkomt, treedt op wanneer een transactie wordt opgewaardeerd. Een losse BEGIN in SQLite betekent BEGIN DEFERRED. Als de eerste instructie daarna een SELECT is, bevindt u zich in een leestransactie. Wanneer een latere UPDATE in diezelfde transactie moet worden omgezet in een schrijftransactie en een andere verbinding sinds het begin van uw leesbewerking heeft geschreven, kan SQLite u niet laten wachten. Uw snapshot is dan al verouderd. Wachten zou de twee verbindingen alleen in een deadlock tegen elkaar laten wachten. De documentatie beschrijft het resultaat rechtstreeks:
Latere schrijfinstructies upgraden de transactie indien mogelijk naar een schrijftransactie of retourneren SQLITE_BUSY.
Uw timeout van 5000 milliseconden wordt nooit geraadpleegd. De fout treedt onmiddellijk op. Daarom lijkt het alsof de instelling niets heeft gedaan.
De oplossing bestaat uit één woord.
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;BEGIN IMMEDIATE neemt het schrijfslot aan het begin in, voordat er iets wordt gelezen. Er vindt geen upgrade plaats. Er is dus geen deadlock die moet worden voorkomen. De busy handler wordt daarom wel toegepast en de verbinding wacht op zijn beurt in plaats van te falen. Houd transacties die alleen lezen uitgesteld. Elke transactie die een schrijfbewerking bevat, moet onmiddellijk worden gestart.
De tweede oorzaak van vergrendelingsfouten is moeilijker te herkennen: een schrijftransactie openhouden tijdens traag werk. SQLite serializeert schrijfbewerkingen. Een transactie die wordt geopend, via het netwerk een externe API aanroept en daarna wordt gecommit, blokkeert elke andere schrijver zolang die aanroep duurt. Lees wat u nodig hebt, sluit de transactie, voer het trage werk uit en open daarna een korte schrijftransactie om het resultaat op te slaan.
Continue back-uppen met Litestream
Een nachtelijke kopie kan tot een dag aan schrijfbewerkingen verliezen. Bovendien kan het uitvoeren van cp tegen een actieve SQLite-database een kopie opleveren die niet kan worden geopend. Twee opties zijn veilig. sqlite3 app.db ".backup /path/to/backup.db" gebruikt de online-back-upinterface van SQLite en werkt met een database die in gebruik is. Litestream gaat verder: het monitort de WAL en verstuurt wijzigingen continu naar objectopslag. Daarmee verkleint u het maximale gegevensverlies van een dag naar ongeveer een seconde.
Litestream is één Go-binary die naast uw toepassing draait. Het bevindt zich niet tussen de toepassing en de database. Uw toepassing schrijft zoals voorheen naar SQLite. Litestream leest de WAL en uploadt de wijzigingen.
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 versionv0.5.14 is de release die op de officiële Linux-installatiepagina wordt beschreven op het moment van schrijven in juli 2026. v0.5.15 volgde op 21 juli 2026. Wijzig de versie in beide regels zodat deze overeenkomt met de huidige tag op de releasespagina. Gebruik in plaats daarvan het bijbehorende arm64-pakket als uw VPS arm64 gebruikt.
Het configuratiebestand staat op /etc/litestream.yml. Begin met een lokale bestandsreplica. Daarmee controleert u de volledige keten zonder cloudreferenties.
dbs:
- path: /home/appuser/app/app.db
replica:
type: file
path: /var/backups/litestream/appLet erop dat het veld replica enkelvoudig is. Litestream 0.5 heeft de replicas-array uit de 0.3-serie vervangen door één replicablok. Een configuratie met twee vermeldingen mislukt nu bij het opstarten. Veel handleidingen van derden tonen nog steeds de oude array. Neem daarom de bovenstaande structuur over en niet het eerste voorbeeld dat u via een zoekopdracht vindt. In de 0.5-serie is de subopdracht litestream wal bovendien hernoemd naar litestream ltx, omdat de indeling van back-ups op schijf is gewijzigd.
Controleer of de configuratie kan worden geparseerd voordat u iets inschakelt.
sudo litestream databases -config /etc/litestream.ymlControleer daarna handmatig of de volledige keten werkt. Met deze vorm slaat u het configuratiebestand over en repliceert u één database naar één pad.
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/appDit proces draait op de voorgrond en blijft actief. Schrijf in een tweede shell een rij en herstel de replica naar een nieuw bestand.
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;"De telling bevat de nieuwe rij. Als dat niet het geval is, is de wijziging nog niet gesynchroniseerd. Litestream verstuurt wijzigingen op een sync-interval, die standaard 1 seconde bedraagt. Wacht daarom en voer het herstel opnieuw uit. Die ene seconde is ook uw herstelpunt. Bij een crash gaan hoogstens de schrijfbewerkingen van het laatste synchronisatie-interval verloren. Geen enkele configuratie kan dat verlies tot nul reduceren.
Gebruik voor echte opslag een S3-URL in plaats van het replicablok. Dit werkt met Amazon S3 en met S3-compatibele objectopslag van andere providers.
dbs:
- path: /home/appuser/app/app.db
replica:
url: s3://your-bucket-name/app
region: us-east-1
snapshot:
interval: 24h
retention: 24hSla referenties niet in dat bestand op. Litestream leest LITESTREAM_ACCESS_KEY_ID en LITESTREAM_SECRET_ACCESS_KEY uit de omgeving. Plaats deze daarom in een systemd drop-in die eigendom is van root en de modus 600 heeft.
De bovenstaande snapshotwaarden zijn de standaardwaarden. De standaardwaarde voor retentie leidt vaak tot verwarring. Retentie bepaalt hoelang Litestream snapshots en de bijbehorende bestanden bewaart. Daarmee bepaalt deze ook hoever u in de tijd kunt teruggaan bij een herstel. Vierentwintig uur betekent dat een onjuiste migratie die u woensdagochtend opmerkt, niet meer kan worden hersteld naar de toestand van maandag. Stel retention: 168h in op een week en houd rekening met de extra opslagkosten.
Test het herstel voordat u het nodig hebt
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;"Op basis van een databasepad zoekt litestream restore de overeenkomende replica op in /etc/litestream.yml en haalt deze op. PRAGMA integrity_check geeft ok weer voor een gezond bestand. Elke andere uitvoer betekent dat de herstelde kopie niet bruikbaar is. Voer dit volgens een schema uit met een systemd-service en timer en lees de uitvoer. Zolang u niet één keer een back-up hebt hersteld, weet u niet of deze werkt.
Litestream uitvoeren onder systemd
Het Debian-pakket installeert een litestream-unit die /etc/litestream.yml leest.
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -fEen gezonde uitvoer vermeldt elke database uit de configuratie en blijft daarna stil, behalve bij periodieke synchronisatieregels. Een foutmelding no such file or directory voor het pad naar uw database betekent dat het pad in de configuratie onjuist is of dat het proces het bestand niet kan lezen. De unit wordt standaard uitgevoerd als root. Dat geeft deze taak meer privileges dan nodig. Litestream moet zowel de database als de map waarin deze staat kunnen lezen en schrijven. Het werkt namelijk met de bestanden -wal en -shm naast uw database. Geef de unit daarom de account die uw toepassing al gebruikt.
# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuserPas dit toe met sudo systemctl daemon-reload en sudo systemctl restart litestream. Het instellen van een speciale serviceaccount met minimale privileges duurt enkele minuten. Het verschil is dat tussen een back-upagent en een tweede root-proces op de server.
De volgorde is belangrijk als u de machine ooit volledig opnieuw opbouwt. U wilt dat de database is teruggezet voordat de toepassing start. litestream restore accepteert -if-db-not-exists. Deze optie retourneert 0 als het bestand al bestaat en is daarom veilig om bij elke start uit te voeren. Voeg dit toe aan een ExecStartPre-regel in de unit van uw toepassing. Een nieuwe VPS haalt de database dan op, terwijl een bestaande VPS niets doet. litestream replicate heeft een overeenkomstige -restore-if-db-not-exists-flag als u dit liever op één plaats beheert.
Waar SQLite op een VPS tegen grenzen aanloopt
Netwerkbestandssystemen. Dit is een beperking die u niet met configuratie kunt omzeilen. De WAL-modus vereist dat elk proces dat de database gebruikt, een klein geheugengebied deelt. Het bestand -shm levert dit geheugengebied. De SQLite-documentatie stelt deze regel zonder voorbehoud:
Alle processen die een database gebruiken, moeten zich op dezelfde hostcomputer bevinden; WAL werkt niet via een netwerkbestandssysteem.
Een database op een gekoppelde NFS-share (network file system) of SMB-share kan dus beschadigd raken. Geen enkele pragma voorkomt dit. Hierbij wordt een belangrijk onderscheid vaak over het hoofd gezien. Een netwerk-block device, zoals de meeste VPS-providers als extra opslag koppelen, verschijnt in Linux als een gewone schijf met daarop een gewoon bestandssysteem. Dat is geen probleem. Een gekoppelde file share is dat wel.
Een tweede applicatieserver. Geen enkele instelling maakt dit werkend. Zodra twee machines dezelfde gegevens moeten aanbieden, hebt u een database nodig die via het netwerk communiceert. Neem die beslissing terwijl u nog tijd hebt om de migratie te plannen.
Workloads met veel schrijfbewerkingen. Eén schrijver tegelijk is een eigenschap van de bestandsindeling, geen instelbare optie. Korte schrijfbewerkingen zijn efficiënt, omdat elke commit aan de WAL wordt toegevoegd. De doorvoer hangt daarom sterker samen met de latentie van kleine schrijfbewerkingen op uw schijf dan met uw CPU. Zie NVMe tegenover SATA SSD-opslag op een VPS voor het verschil in de praktijk. Lange transacties vormen het echte probleem, omdat ze alle andere schrijvers achter zich in de wachtrij plaatsen.
Analytische query's. SQLite is een row store voor transacties. Een dashboard dat honderd miljoen rijen scant, is een andere taak voor een ander hulpmiddel. DuckDB vergeleken met SQLite voor serverwerk behandelt waar die grens ligt.
VACUUM tijdens replicatie. Een volledige VACUUM herschrijft het volledige databasebestand. Litestream moet het bestand daarom opnieuw volledig uploaden. De Litestream-documentatie raadt af om dit uit te voeren terwijl replicatie actief is. Stop de replicator, voer vacuum uit, start de replicator opnieuw en houd rekening met een nieuwe volledige snapshot.
Twee replicators voor één database. Voer nooit twee Litestream-processen uit voor dezelfde database of dezelfde replicabestemming. De documentatie vermeldt uitdrukkelijk dat u dit zelf moet voorkomen. Het resultaat is anders een replica die u niet kunt herstellen.
Wat Litestream niet dekt
Litestream beschermt alleen het databasebestand. Geüploade bestanden, de applicatieconfiguratie, TLS-certificaten (transport layer security) en de unitbestanden moet u nog steeds zelf beheren. Combineer het met versleutelde back-ups buiten de server met restic volgens een schema, zodat beide onderdelen zijn afgedekt. Als de machine nieuw is, behandelt de eerste tien minuten op een nieuwe VPS het gebruikersaccount en de firewallconfiguratie waarvan deze handleiding ervan uitgaat dat ze al zijn uitgevoerd.
FAQ
Is SQLite geschikt voor een toepassing in productie?
Voor één toepassing op één server: ja, op voorwaarde dat u de WAL-modus inschakelt, een wachttime-out instelt en voortdurend back-ups maakt. De relevante beperkingen zijn structureel: er kan telkens maar één proces schrijven en er is één hostcomputer. Een toepassing die binnen deze beperkingen past, gebruikt een database zonder netwerkvertraging en zonder afzonderlijk proces dat u moet monitoren. Een toepassing die niet binnen deze beperkingen past, heeft een client-serverdatabase nodig. Geen enkele optimalisatie verandert dat.
Waarom krijg ik nog steeds database is locked nadat ik busy_timeout heb ingesteld?
Omdat SQLite de busy handler overslaat wanneer wachten een deadlock kan veroorzaken. Een transactie die begint met een kale BEGIN is uitgesteld: een beginnende SELECT plaatst de transactie in een leestransactie, en een latere schrijfactie moet deze transactie upgraden. Als een andere verbinding intussen heeft geschreven, retourneert SQLite onmiddellijk SQLITE_BUSY in plaats van uw busy handler aan te roepen, omdat uw leesmomentopname al verouderd is. Start elke transactie die gaat schrijven met BEGIN IMMEDIATE. Dan wordt de schrij vergrendeling vooraf verkregen en is de time-out van toepassing.
Kan ik mijn SQLite-database op netwerkopslag bewaren?
Niet op een netwerkbestandssysteem zoals NFS of SMB. De WAL-modus vereist dat alle processen geheugen delen via het bestand -shm. In de SQLite-documentatie staat dat elk proces dat de database gebruikt, zich op dezelfde hostcomputer moet bevinden. Een netwerkblokapparaat dat door uw provider is gekoppeld, is iets anders: Linux ziet daarop een normale schijf met een normaal bestandssysteem en SQLite werkt daar.
Heb ik Litestream nodig als ik al elke nacht back-ups maak?
Dat hangt af van hoeveel gegevens u kunt verliezen. Met een nachtelijke taak kunt u tot vierentwintig uur aan schrijfacties verliezen. Litestream synchroniseert ongeveer eenmaal per seconde, waardoor een crash u ongeveer de laatste seconde kost. Het is ook veiliger dan het databasebestand kopiëren met cp, omdat daarmee een database midden in een schrijfactie kan worden vastgelegd. Litestream dekt alleen de database. Laat daarom daarnaast een algemene bestandsback-up uitvoeren.