How (and why) to run SQLite in production
fractaledmind.github.io
fractaledmind.github.io
No, it's not a single executable. There's no database executable as far as your app goes. There's no engine. The "engine" is a library, meant to be embedded into other code and may be concurrent and have many "executables" if you implement it that way or have different apps access the database.
Think of SQLite as an intricate file format for which your app will use an API (ie sqlite.c) to access.
[1] - https://www.youtube.com/watch?v=b2F-DItXtZs
edit: and now I believe you are directly referencing this but may be useful for others who don't get the reference
Yep haha.
> but may be useful for others who don't get the reference
Probably also yes. Important lore for techno-archeologists.
Between this and WAL, which-ever one you pick, you have the following caveats:
- You aren't supposed to use a SQLite DB through a hard or soft link
- By default, in rollback journal mode, it will create a temporary `-journal` file during every write
- If you're doing atomic transactions on multiple DBs, it also creates a super-journal file
- You definitely can't `cp` a database file if it's in use, the copy may be corrupt
- It relies on POSIX locks to cooperate with other SQLite threads / processes, the docs advise that locks don't work right in many NFS implementations
- In WAL mode it needs a `-wal` and a `-shm` file, and I believe the use of shared memory makes it extra impossible to run it over NFS
SQLite is not simple, it's like a million lines of code. It is as simple as a DB can be while still implementing SQL and ACID without any network protocol.
I am thinking of writing a toy DB for fun and frankly I am not sure if I want to start with SQLite's locking and pager or just make an on-demand server architecture like `sccache` uses. Then the only lock I'd have to worry about is an exclusive lock on the whole DB file.
I have a bit of battle-tested code that gives me a nice key-value interface to SQLite files, perhaps others will find it useful: https://github.com/aaviator42/StorX
It seems he uses Litestream with DigitalOcean Spaces for this. Looks like they start at $5 per month for 250 GB [2]. Would that be the best way for a hobbyist to get started?
[1] https://fractaledmind.github.io/2023/09/09/enhancing-rails-s... [2] https://www.digitalocean.com/pricing/spaces-object-storage
(no affiliation to fly/litefs, just a fan)
Here's how to use .backup:
sqlite3 data.db ".backup 'backup.db'"
You can also do this: sqlite3 data.db 'VACUUM INTO "backup.db";'
That's slower, but results in a smaller backup file: https://www.sqlite.org/lang_vacuum.htmlI interpret this as saying that you can't get R2 literally for free. Looks like Cloudflare KV could work, with values that go up to a bit over 25MB.
$5 per month is very reasonable if you're using it for real, though.
[1] https://blog.cloudflare.com/workers-pricing-scale-to-zero/
Here’s an article about using it in PowerShell scripts:
https://renenyffenegger.ch/notes/Windows/dirs/Windows/System...
Is it that the suggestions are for companies other than the big 3 that it caught your eye?
That article, along with the ability to have streaming backups with Litestream and not wanting to pay for a separate DB server, inspired me to use SQLite in my SaaS four years ago. I've been enjoying the operational simplicity a lot although there is not a lot of community documentation currently about tuning SQLite for web app loads. My app does about 120m hits a month (mostly cached and not hitting the DB) on a $14/month single-processor DigitalOcean droplet.
> But, my favorite feature of the gem is its improved concurrency support.
> [..] https://fractaledmind.github.io/2023/12/11/sqlite-on-rails-i...
Really. At the point you are experiencing database locked in your productive app that uses sqlite as backend, i would strongly suggest to use another database backend that was designed with concurrent writes in mind.
Nothing against sqlite in production, its nice, as long as your workload meets its feature set.
Anecdotally, I've seen the WAL file grow way too large even after all writes have finished and should shrink, but that's manageable.
If you loop-retry all transactions which fail due to transitory effects, then you won't have a problem with "database locked" situations.
Abysmally documented, but this is what I use for golang + sqlite:
https://pkg.go.dev/gitlab.com/martyros/sqlutil@v0.0.0-202312...
EDIT: Typo
...the problem here is that historically rails (and others) use deferred transactions and that causes sqlite to fail even under trivial load conditions without simply waiting for the db to free for the next write, because people who've written the drivers don't understand how to use sqlite.
If you use sqlite correctly under heavy load it's slow not unreliable.
> as long as your workload meets its feature set.
Sure... but to be fair that probably covers a lot of microservices and probably a lot of apps too.
Obviously as you scale, it won't, so sure, it's a limited use case... but, heck, I've seen dozens of microservices each with their 'own database' (ie. read same RDS, with different database instances) that all fail at once when that instance goes down. Woops.
Better? Worse?
Hm. Sqlite is insanely reliable. I like isolated reliable services.
It's not for everything, but nothing is... I think it's suitable for more use cases than you're giving it credit for.
Do your writes really need to be concurrent if they run that fast?
Hard to get upset about waiting for the current write to complete before you get your turn when we are talking delays measured in thousandths of a second.
If you have more than 1000 writes per second then maybe this is something to worry about. The solution there is probably to run a slightly more powerful server!
Does SQLite support overlapping write transactions with locks on different rows?
Also do you know if WAL2 mode changes anything? https://www.sqlite.org/cgi/src/timeline?r=wal2
ive just put it here: https://github.com/abbbi/sqlitestress
maybe its useful for some people to simulate their workloads.
I am no sqlite fanboy, although I might be, but I found the industry seems to run to postgres for just about anything. I prefer simplicity first.
To batch updates makes the code far more complex.
To install any full strength DB is trivial.
I don't get the 'simplicity'?
The simplicity is not having a separate process that can fail, and that requires fail over, and monitoring.
We're talking about concurrent writes. We're talking about batching inserts. Because SQLite can't handle a high throughput, so you batch a bunch of inserts together.
If the only way to get performance is to batch inserts, then you've got to write a whole load of manual queue code to queue up X number of inserts to insert them all at once.
Worse still, if your server crashes, bug, etc. you've just lost all those inserts. But you've already responded with 201s! So if you want any sort of guarantee, you've got to write even more code to cache them on disk or redis or something.
You're basically re-implementing features of postgre, badly, to make up for SQLite's deficiencies,
It really doesn't matter HOW you do it, it's the fact you have to do it at all. It's not simpler, it's more complicated. Installing/using a fully fledged DB is trivial these days.
Not needing a managed database also allows us to move away from AWS onto much better value Hetzner servers, much love to Litestream [1] which makes replication to R2/S3 effortless. It's a silly 30-40x cheaper hosting SQLite Web Apps on Hetzner compared to the "recommended" managed DB configurations on AWS/Azure.
It really is quite nice how much database you get for the cost and maintenance overhead required when litefs is thrown in the mix.
Doesn't help that most ORMs (or other migration tools) don't generate you the correct migration you need
https://centraltrunks.blogspot.com/2022/07/django-sqlite-dat...
https://code.djangoproject.com/ticket/29280
(I authored the rant in the first link)
https://code.djangoproject.com/ticket/24018
With the proper PRAGMAs set and modern hardware, you can do 400+ writes/sec with 4 uvicorn workers and ~100 clients connected. The achilles heal is writing lots of large files. That will cause concurrent requests to wait around for the disk to finish writing to the WAL.
I've got a more basic backup solution running currently and don't want to put another moving piece in the way of a production service unless it's extremely solid, but I do like the idea.
[1] Had a problem with corruption back in 2021 but that was fixed quickly.
Another one of my rule of thumbs is that unless there is massive evidence to the contrary, you should simply use what "most people use" for each specific use case. In my world, when it comes to web applications, that is Postgres.
> Why? So, let’s explore that together. Who here is running or has run an application in production with SQLite? Who has experimented with SQLite for an app, but not shipped it to production? There are a couple hands up, but not many. So, let’s turn this question around.
It literally appears twice in the article at different places.
Maybe it could be more accessible when rewritten as a blog post, but if it’s a useful talk I’d much rather have an official transcript than only a video link and maybe Youtube’s auto-transcript.
So much good content is buried in video form for me (I really don’t like watching technical videos and prefer reading that content).
I don't think the speaker said it twice word by word in the talk.
Is the DB hosted on something like nfs, or are writes synced to all app servers or something else?
This is the sweet-spot for SQLite applications, but there have been explorations and advances to running SQLite across a network of app servers. LiteFS (https://fly.io/docs/litefs/), the sibling to Litestream for backups (https://litestream.io), is aimed at precisely this use-case. Similarly, Turso (https://turso.tech) is a new-ish managed database company for running SQLite in a more traditional client-server distribution.
Why? Because once your app gets more than _n_ users you will run into scaling problems and then you end up with huge tech debt
Anecdote: the sqlite forum runs on a single sqlite db and has well over 1500 users. Similarly, the sqlite source site is heavily visited by thousands of folks and actively used in write mode by its developers. It sibling project, the Fossil SCM, also runs entirely from a single sqlite db.
sqlite db forum stats: <https://sqlite.org/forum/reports>
Honestly, 99.9% uptime is pretty generous - we can fit in quite a few catastrophes per year and still have 99.9% uptime. In the 2 years this service has been running, we've had 100% uptime via zero-downtime deployments, anyway.
In terms of monitoring, traces and error logs are shipped to our observability solution, yes.