logoalt Hacker News

SQLite concurrency and why you should care about it

353 pointsby HunOLlast Saturday at 12:59 PM163 commentsview on HN

Comments

asa400last Saturday at 1:32 PM

In SQLite, transactions by default start in “deferred” mode. This means they do not take a write lock until they attempt to perform a write.

You get SQLITE_BUSY when transaction #1 starts in read mode, transaction #2 starts in write mode, and then transaction #1 attempts to upgrade from read to write mode while transaction #2 still holds the write lock.

The fix is to set a busy_timeout and to begin any transaction that does a write (any write, even if it is not the first operation in the transaction) in “immediate” mode rather than “deferred” mode.

https://zeroclarkthirty.com/2024-10-19-sqlite-database-is-lo...

show 8 replies
EionRobblast Saturday at 7:57 PM

One of the biggest contributors I've had in the past for SQLite blocking was disk fragmentation.

We had some old Android tablets using our app 8 hours a day for 3-4 years. They'd complain if locking errors and slowness but every time they'd copy their data to send to us, we couldn't replicate, even on the same hardware. It wasn't until we bought one user a new device and got them to send us the old one that we could check it out. We thought maybe the ssd had worn out over the few years of continual use but installing a dev copy of our app was super fast. In the end what did work was to "defrag" the db file by copying it to a new location, deleting the original, then moving it back to the same name. Boom, no more "unable to open database" errors, no more slow downs.

I tried this on Jellyfin dbs a few months ago after running it for years and then suddenly running into performance issues, it made a big difference there too.

show 6 replies
thaynelast Saturday at 4:40 PM

There seem to be some misunderstandings in this:

> If your application fully manages this file, the assumption must be made that your application is the sole owner of this file, and nobody else will tinker with it while you are writing data to it.

Kind of, but sqlite does locking for you, so you don't have to do anything to ensure your process is the only one writing to the db file.

> [The WAL] allows multiple parallel writes to take place and get enqueued into the WAL.

The WAL doesn't allow multiple parallel writes. It just allows reads to be concurrent with a single write transaction.

show 1 reply
mickeyplast Saturday at 1:48 PM

SQLite is a cracking database -- I love it -- that is let down by its awful defaults in service of 'backwards compatibility.'

You need a brace of PRAGMAs to get it to behave reasonably sanely if you do anything serious with it.

show 2 replies
stefanos82last Saturday at 1:56 PM

When hctree [1] becomes stable in SQLite, it will be the only database I will be using lol!

I presume the `hc` part in project's code name should be High Concurrency.

[1] https://sqlite.org/hctree/doc/hctree/doc/hctree/index.html

show 1 reply
ricardobeatlast Saturday at 3:17 PM

Articles like this leave me with an uneasy feeling that the “solutions” are just blind workarounds - more debugging/research should be able to expose exactly what the problem is, now that would be something worth sharing.

show 1 reply
ddtaylorlast Saturday at 4:32 PM

I have encountered this problem on Jellyfin before. It works like a dream, but there are some very strange circumstances that can cause the database to become locked and then just not work until I restart the docker container. If I check the logs it just says stuff about the database being locked. It happens quite rarely and seems to be when we fidget in the menus on the smart TV like starting to watch a show to realize it's the wrong episode as you click the button, then spam the back button, etc.

rpcope1last Saturday at 11:57 PM

Do these guys really not understand that WAL is still single writer multi reader? You could do concurrent (but not parallel) write DML in both the normal and WAL journaling models. WAL alleviates read transactions being blocked by writers but you still have to lock it down to a single writer. It would be nice if SQLite3 had full blown MVCC, but it still works if you understand it.

mangecoeurlast Saturday at 3:14 PM

Sqlite is a great bit of technology but sometimes I read articles like this and think, maybe they should have used postgres. I you don’t specifically need the “one file portability” aspect of sqlite, or its not embedded (in which case you shouldn’t have concurrency issues), Postgres is easy to get running and solves these problems.

show 11 replies
Leherennlast Saturday at 3:23 PM

A bit off topic, but there seems to be quite a few SQLite experts here.

We're having troubles with memory usage when using SQLite in-memory DBs with "a lot" of inserts and deletes. Like maybe inserting up to a 100k rows in 5 minutes, deleting them all after 5 minutes, and doing this for days on end. We see memory usage slowly creeping up over hours/days when doing that.

Any settings that would help with that? It's particularly bad on macOS, we've had instances where we reached 1GB of memory usage according to Activity Monitor after a week or so.

show 4 replies
tombertlast Saturday at 7:54 PM

Does this mean I can finally load-balance with multiple Jellyfin instances?

A million years ago, back when I still used Emby, I was annoyed that I couldn't use it across multiple in Docker Swarm due to locking of SQLite. It really annoyed me, enough to where I started (but never completed) a driver to change the DB to postgres [1]. I ended up moving everything over to a single server, which is mostly fine unless I have multiple people transcoding at the same time.

If this is actually fixed then I might have an excuse to rearchitect my home server setup again.

[1] https://github.com/Tombert/embypostgres

show 1 reply
yreadlast Saturday at 5:40 PM

I'm a bit confused. The point of this article is that the author used .NET Interceptors and TagWith to somehow tag his EF Core operations so that they make their own busy_timeout (which EF Core devs think is not necessary https://github.com/dotnet/efcore/issues/28135 ) or do a horrible global lock? No data is presented on how it improved things if it did. Nor is it described which operations were tagged with what. The only interesting thing about it are the interceptors but that's somehow not discussed in HN's comments at all.

dv35zlast Saturday at 2:39 PM

Curious if anyone has strategies on how to perform parallel writes to an SQLite database using Python's `multiprocessing` Pool.

I am using it to loop through a database of 11,000 words, hit an HTTP API for each (ChatGPT) and generate example sentences for the word. I would love to be able to asynchronously launch these API calls and have them come back and update the database row when ready, but not sure how to handle the database getting hit by all these writes from (as I understand it) multiple instances of the same Python program/function.

show 3 replies
slashdavelast Saturday at 9:50 PM

I am a little confused, but maybe I am missing some context? Wouldn't using a proper database be a lot easier than all of this transaction hacking? I mean, is Postgres that hard to use?

fitsumbelaylast Saturday at 6:11 PM

Very helpful and a model for how technical posts should be written: clarity, concision, anchor links that summarize the top lines. It was a pleasure to read.

ignoramouslast Saturday at 3:06 PM

  So, I decided on three locking strategies:

  No-Lock
  Optimistic locking
  Pessimistic locking

  As a default, the no-lock behavior does exactly what the name implies. Nothing. This is the default because my research shows that for 99% all of this is not an issue and every interaction at this level will slow down the whole application.
Aren't the mutexes in the more modern implementations (like Cosmo [0]) & runtimes (like Go [1]) already optimized so applications can use mutexes fearlessly?

[0] https://justine.lol/mutex/

[1] https://victoriametrics.com/blog/go-sync-mutex/

porridgeraisinlast Saturday at 2:48 PM

> So an application that wants to use SQLite as its database needs to be the only one accessing it.

No. It uses OS level locks. fcntl(). You can access it from how many ever processes. The only rule is, single writer (at a time).

> When another part of the application wants to read data, it reads from the actual database, then scans the WAL for modifications and applies them on the fly.

Also wrong. WAL does not contain modifications, it contains the full pages. A reader checks the WAL, and if it finds the page it won't even read the DB. It's a bit like a cache in this sense, that's why shared cache mode was discouraged in favour of WAL (in addition to its other benefits). Multiple versions of a page can exist in the WAL (from different transactions), but each reader sees a consistent snapshot which is the newest version of each page up to its snapshot point.

> For some reason on some systems that run Jellyfin when a transaction takes place the SQLite engine reports the database is locked and instead of waiting for the transaction to be resolved the engine refuses to wait and just crashes

You can set a timeout for this - busy_timeout.

> Reproducible

There's nothing unreliable here. It will fail every single time. If it doesn't, then the write finished too fast for the read to notice and return SQLite busy. Not sure what they are seeing.

> The solution

So they've reimplemented SQLites serialisation, as well as SQLites busy_timeout in C#?

> "engine", "crash"

Sqlite is not an engine. It's literally functions you link into your app. It also doesn't crash, it returns sqlite_busy. Maybe EF throws an exception on top of that.

I have to say, this article betrays a lack of fundamental DB knowledge and only knowing ORMs. Understand the DB and then use the ORM on top of it. Or atleast, don't flame the DB (context: blame-y tone of article) if you haven't bothered to understand it. Speaking of ORMs ...

> EF Core

You're telling me that burj khalifa of abstractions doesn't have room to tune SQLite to what web devs expect?

show 1 reply
keyliejenerlast Saturday at 9:14 PM

[dead]