News for hackers. Stuff that matters.
Re:Django SQLite Production Config by kgeist on Saturday June 15, 2024 @07:10AM (#40688987)
I want to warn that one huge issue with SQLite in WAL mode is that if your site gets a high load, the WAL file will grow unboundedly. I ran a stress test of 14k RPS (that's the maximum my PC can pull off in my Go application, but it probably can happen with a more modest RPS) and the WAL file quickly exploded to tens of gigabytes, which can render your machine inoperable (it almost broke my PC). The default checkpointer can't properly function if the DB is continuously written to, due to the internal limitations of SQLite.

I managed to solve it by running a separate goroutine (thread) which monitors the WAL size on disk every second: if it goes above the target size of 8 MB, my framework initiates the "slow down" mode where all reads and writes are slowed down by artificially calling sleep(), starting with 16ms and gradually increasing the sleep time according to a few heuristics. This allows the application to have small time gaps where no reads or writes happen and the checkpointer can actually proceed (the goroutine activates it manually). The slow down mode is deactivated when the WAL size is within the target size again.

I think SQLite in WAL mode is not really fit for production without this kind of hack.

Re:Django SQLite Production Config by baq on Saturday June 15, 2024 @07:23AM (#40689045)
I love this story because it shows you really should read some docs about your storage layer before you jump in head first and just assume it’ll work forever and/or gracefully handle any load including overload. Postgres is the same, it’ll work until it won’t, in some case it’ll break in a way you can’t recover from without stopping production for a long time. (You can guess how I know - you’re right, I didn’t read the relevant part of the manual.)

Read your database’s manual, people! Even just going through the table of contents will put you in the top 20%.

Re:Django SQLite Production Config by stavros on Saturday June 15, 2024 @07:53AM (#40689200)
Well, maybe the database should have more suitable defaults? Arguably, slowing down a bit once when the WAL grows beyond X MB to clean it up is better behavior than having it grow unboundedly, but maybe that's not possible.
Re:Django SQLite Production Config by kmac_ on Saturday June 15, 2024 @08:38AM (#40689417)
There is no one-size-fits-all solution in the SQL world, and using SQLite in such a scenario is not a good idea. SQLite is an embedded database for embedded use. Even PostgreSQL is not universal, as many people assume. For use cases like analytics, there are many better-suited database engines.
Re:Django SQLite Production Config by stavros on Saturday June 15, 2024 @08:40AM (#40689428)
Sure, but there are one-size-fits-95% solutions, and they're better than one-size-fits-90% solutions.
Re:Django SQLite Production Config by kmac_ on Saturday June 15, 2024 @09:43AM (#40689756)
Absolutely agree. As most projects are small or medium in size, PostgreSQL is a good default choice. Problems arise when the problem does not fit the solution or tool. For example, using SQLite for high-volume transactions and then complaining that it doesn't work.
Re:Django SQLite Production Config by belter on Saturday June 15, 2024 @07:47AM (#40689167)
> you can’t recover from without stopping production for a long time.

Should that not be addressed at the level of Architectural design of your System? Everything fails all the time...bla bla....and all that?

Re:Django SQLite Production Config by baq on Saturday June 15, 2024 @07:57AM (#40689212)
If the system is small enough for a single postgres (which let's be honest 99% of them are) and short maintenance downtimes are easily tolerated by customers it's easy to forget about some failure modes and especially if you haven't been on the receiving end of such an incident. An experienced DBA would also notice the problem if only because they've been resolving a few of them in their professional past (hence 'experienced').
Re:Django SQLite Production Config by anze3db on Saturday June 15, 2024 @07:22AM (#40689040)
I've heard this is a potential issue but I've never encountered it. Do you know about the experimental WAL2 branch[0] that splits the WAL file into two to circumvent this problem? You'd have to compile SQLite yourself from the WAL2 branch to try it out, but it might be worth it at your scale.

Python is much slower than Go, so I don't think we could get 14k RPS as easily with Django, but I do have to see if I can reproduce the problem in Django. Topic for a future blog post!

Thanks for sharing this, I really appreciate knowing where the limits of SQLite are!

[0] https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md

Re:Django SQLite Production Config by sgbeal on Saturday June 15, 2024 @08:01AM (#40689235)
> I ran a stress test of 14k RPS

My built-in Devil's Advocate feels compelled to ask: do you have any reason to believe that your production site would get even a small fraction (single-digit percentage) of that traffic?

As a point of comparison, i have it on good authority that sqlite3's own sites typically get far less than 0.1% of that traffic. Two of those sites (sqlite.org/src and sqlite.org/forum) are database-driven applications.

Re:Django SQLite Production Config by kgeist on Saturday June 15, 2024 @08:17AM (#40689307)
Sure I don't expect my application to have 14k RPS all the time. However, some spikes can happen, and it can leave you with a large WAL file which doesn't shrink by itself by default. 14k RPS is the maximum my PC could pull off, I think it can happen with a more modest RPS. I ran the stress test with 12 parallel threads, so technically it can happen with just 12 RPS too, if your read or write transactions take a lot of time each (say, it has to read a large table, or you forgot to add indexes, or write transactions update many rows). In fact, a single open read transaction already can trigger checkpoint starvation. Also, it's not only about HTTP requests, my application is event-driven so a few event handlers processing events in the background can easily trigger it, too. A system can accidentally produce many events without having a lot of user requests (say, you forgot to batch events and generate N separate events for N items).
Re:Django SQLite Production Config by anze3db on Saturday June 15, 2024 @09:53AM (#40689826)
While this blog post was at the #2 spot on HN it was getting 0.138 rps, so yeah 14k RPS is A LOT!

That said it's still good to know when your tools will break! As far as I understand it's not really about RPS but more about constant write operations preventing the WAL file from being flushed. You can reproduce this issue with a lot fewer RPS.

Re:Django SQLite Production Config by stefanos82 on Saturday June 15, 2024 @07:40AM (#40689129)
I think there's a workaround if I understood the official documentation correctly: https://www.sqlite.org/wal.html#avoiding_excessively_large_w...
Re:Django SQLite Production Config by kgeist on Saturday June 15, 2024 @07:43AM (#40689145)
Yeah, when implementing this hack, I took the advice from this exact page:

>This scenario can be avoided by ensuring that there are "reader gaps": times when no processes are reading from the database and that checkpoints are attempted during those times

I create reader/writer gaps by calling sleep() (before opening transactions) and activate the checkpointer manually in the checkpointer goroutine.

Re:Django SQLite Production Config by brigadier132 on Saturday June 15, 2024 @08:28AM (#40689357)
From what I remember reading 1k rps is the recommended limit for sqlite right?
Re:Django SQLite Production Config by tiffanyh on Saturday June 15, 2024 @08:28AM (#40689358)
Not an issue for WAL2 mode, which mitigates this issue.

—-

> In wal2 mode, wal files do not grow indefinitely

https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md

Re:Django SQLite Production Config by kissgyorgy on Saturday June 15, 2024 @09:07AM (#40689559)
Isn't journal_size_limit is exactly for this? From the doc:

"Each time a transaction is committed or a WAL file resets, SQLite compares the size of the rollback journal file or WAL file left in the file-system to the size limit set by this pragma and if the journal or WAL file is larger it is truncated to the limit."

Re:Django SQLite Production Config by anze3db on Saturday June 15, 2024 @09:55AM (#40689839)
From what I understand, you still need the experimental WAL2 mode[0] to not have this problem.

[0] https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md

Re:Django SQLite Production Config by kgeist on Saturday June 15, 2024 @09:58AM (#40689854)
If there's checkpoint starvation, the WAL file cannot be reset, and it will still grow indefinitely even with this setting.
Re:Django SQLite Production Config by kissgyorgy on Saturday June 15, 2024 @09:13AM (#40689586)
I would argue that if you have this problem, SQLite is not a good database for your use-case.
Re:Django SQLite Production Config by jfloresremar on Tuesday June 18, 2024 @08:52AM (#40717241)