hruk
What do you consider "serious" work? We've served a SaaS product from SQLite (roughly 300-500 queries per second at peak) for several years without much pain. Plus, it's not like PG and MySQL are pain-free, either - they all have their quirks.
If I had to start something new, I'd use SQLite until at least high 5 digit queries per second. Maybe more.
SQLite (actually SQL-ite, like a mineral) maybe be light, but so are many workloads these days. Even 1000 queries per second is quite doable with SQLite and modest hardware, and I've worked at billion dollar businesses handling fewer queries than that.
I'm curious what issues the author had with high availability - I totally understand the issue in theory, but in practice our SQLite-backed service has had 6 minutes of downtime in the last 3 years when we were upgrading the host. That works out to being quite a few 9s.
I do agree with the article on a few points - distributed SQLite doesn't really seem all that worthwhile to me (yet, anyway).
That being said, I agree that MABs are poor for experimentation (they produce biased estimates that depend on somewhat hard-to-quantify properties of your policy). But they're not for experimentation! They're for optimizing a target metric.
The highest-traffic site I run off of SQLite gets roughly 1 million hits per day. I run it on a VPS with 2vCPU and 4 GB RAM and it hovers around ~8% CPU utilization and 12% memory usage during peak hours. Last I checked, the database is handling about ~80 queries per second during peak usage, and clearly there's a ton of headroom.
If the site got 10x as much traffic, maybe I'd double the instance size. If it 100xed, maybe I'd need to upgrade from storing the DB on an EBS volume to storing it on a NVME drive. If it 1000xed... maybe I'd have problems? That seems like a pretty long way away, and in the meantime I get super simple deployment, replication, disaster recovery, and an easy route to pull down snapshots for debugging, etc.
Caching is cheap, backup is trivial and cheap, and pulling down a perfect snapshot of production when I need to fiddle with something is also trivial and cheap.
A pattern I've been using a lot is not changing the backend at all - each endpoint still returns the full page. Each hypermedia control makes liberal use of hx-select and hx-select-oob to update the elements that may have changed.
This isn't super efficient, but it provides some "locality of behavior" in that you can look at a hypermedia control and know what other elements might be changed by it. It also means the backend doesn't really need to "know" about HTMX.
Regarding Litestream, I learned pretty much all I know from their documentation: https://litestream.io/
Every service is running on a single (somewhat meaty) host, so we get true snapshot isolation on reads and serialized isolation on writes without doing anything extra. We run weekly tests to determine how long AWS takes to spin everything back up upon catastrophic failure, and it's between 6 and 10 minutes, depending on how much data the service has. It does bother me that this can only get longer, but we've never actually had a host crash in production.
Like the other commentator, we did our BI for a while by restoring the Litestream backup to another server. Then that started to get expensive (because the file was very large), so we just added a scraping endpoint to each table. I think there's probably a more elegant way of doing this.
* You need to have your code repopulate index statistics with ANALYZE or PRAGMA OPTIMIZE now and again, or you may get a confused query planner.
* It's best if your code defines meaningful types to scan SQLite values into, and you ideally avoid ever writing to the DB via the CLI. Alternatively, be aggressive in writing CHECK constraints and triggers to ensure data validity.
* You need to be working with a team of developers that are pretty comfortable with databases to get a lot of value out of SQLite. I've been surprised at how performant features can be if a single mind is designing the schema, constraints, triggers, and queries, examining the query plan to choose indexes, and writing the application code that accesses the database. SQLite minimizes the barrier to doing this with real snapshots of production data - but that still doesn't mean it's going to be easy.
* You need to think about how long your code holds a write transaction open. Individual statements in SQLite run very quickly (I've seen ~250 microseconds per insert on a EBS-backed EC2 instance), but if you have a Django app with @transaction.atomic everywhere, you're going to run into lock contention quite quickly because your Python functions take much longer to run. If possible, "organize" all the data for your writes outside the transaction and then hold the lock for as short as possible.
* Any transaction that may eventually write must start with BEGIN IMMEDIATE, otherwise SQLite may throw an error to keep it's promise of serialized isolation on writes.
All said, I'd do it again for any other services-based backend. It also incidentally enforces the good hygiene of "services can only access their own data." Happy to answer any questions you have.
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.