Learning a few things about running SQLite

surprisetalk1 pts0 comments

Learning a few things about running SQLite

Skip to main content

Hello! I&rsquo;ve been working on a Django site recently, and I decided to use SQLite<br>as the database.<br>When I was getting started with using SQLite as database for a website I read a bunch<br>of blog posts about how it is totally fine to use SQLite in production for a<br>small site and I think it is totally fine, but what I did not fully appreciate<br>is that SQLite is still a database, databases are complicated, and I do not know<br>a lot about operating databases.

So here are a couple of small things I&rsquo;ve been learning about running SQLite. This<br>is the 4th website I&rsquo;ve used SQLite for, and I think this one is harder because<br>with the power of the Django ORM I&rsquo;ve been making the database do more work than<br>I was previously without Django.

I started by turning on WAL mode like all the blog posts said to do and hoping for the best.

ANALYZE is apparently important

Today I was running a query (using SQLite&rsquo;s FTS5 for<br>full-text search) on a table with 4000 rows and it took 5 seconds. That seemed<br>wrong to me: computers are fast!

It turned out that what I needed to do was to run ANALYZE!<br>Immediately the problem query went from taking 5 seconds to like 0.05 seconds<br>(or some other number small enough that I didn&rsquo;t care to investigate further).<br>I still don&rsquo;t know exactly what went wrong in the query plan,<br>but my best guess is that it was some sort of accidentally quadratic thing.

ANALYZE generates &ldquo;statistics&rdquo; (I guess about the number of rows in each table? and presumably other things?)<br>so that the query planner can make better choices.

Maybe one day I&rsquo;ll learn to read a query plan.

cleaning up the database is tricky

Occasionally I&rsquo;ve run into situations where I accidentally put a bunch of rows<br>in my database that I don&rsquo;t want to be there (for example completed tasks from<br>django-tasks-db), and I want to clean them up.

What&rsquo;s happened to me a few times in this case is:

I run some kind of command to clean up the rows

The command takes more than 5 seconds, since there are a lot of rows (though I still have some questions about why these DELETE statements are so slow honestly, maybe there&rsquo;s a bunch of Python code running inside a transaction, I&rsquo;m not sure)

One of the other workers tries to write the database while this is happening, and times out after 5 seconds (I have a timeout of 5 seconds set)

The worker crashes because it couldn&rsquo;t write to the database and the VM shuts down

My approach so far has been to just do these cleanup operations in small batches<br>so that I don&rsquo;t need to do database queries that take more than 5 seconds to<br>run. This whole experience has given me more of an appreciation for why someone<br>might want to use a &ldquo;real&rdquo; database like Postgres which can have more than one<br>writer at the same time though.

Maybe in the future I&rsquo;ll just take the site down for scheduled maintenance<br>instead when I need to do this kind of thing, but I haven&rsquo;t figured out a<br>workflow for that yet.

no notes on performance of ORM queries yet

So far I&rsquo;ve been using Django&rsquo;s ORM to make any query I want without paying any<br>attention at all to query performance and it&rsquo;s mostly been going okay other<br>than the ANALYZE thing. The database is pretty small (maybe 10000 rows?) and<br>I expect it to stay pretty small forever, so I&rsquo;m hoping that that plan will<br>keep working.

backing up sqlite

I&rsquo;ve done SQLite backups a couple of ways. I don&rsquo;t think I&rsquo;ve actually tested<br>restoring from my backups but I do usually try to monitor them with a dead man&rsquo;s<br>switch.

way 1: restic

sqlite3 /data/calendar.db "VACUUM INTO '/tmp/calendar.sqlite'"<br>gzip /tmp/calendar.sqlite

# Upload backup to S3<br># Sometimes the backup gets OOM killed and so it stays locked, do an unlock<br>restic -r s3://s3.amazonaws.com/some_bucket/ unlock<br># Do the backup & prune old backups<br>restic -r s3://s3.amazonaws.com/some_bucket/ backup /tmp/calendar.sqlite.gz<br>restic -r s3://s3.amazonaws.com/some_bucket/ snapshots<br>restic -r s3://s3.amazonaws.com/some_bucket/ forget -l 1 -H 6 -d 2 -w 2 -m 2 -y 2<br>restic -r s3://s3.amazonaws.com/some_bucket/ prune

way 2: litestream

I started trying out Litestream recently because I felt like doing incremental backups might<br>be more efficient: my restic backups were sometimes getting OOM killed, and I<br>was a bit tired of it. Basically I just write a config file and run:

litestream replicate -config litestream.yml

I set retention: 400h in my config file in an attempt to<br>retain some amount of history of the database but I have no idea if it works.

I&rsquo;ve been backing up to AWS, which is always a pain because it&rsquo;s annoying to<br>navigate the AWS console to generate credentials. Maybe one day I&rsquo;ll move away<br>to some other S3-compatible alternative.

you can use multiple databases

My current project only has...

rsquo sqlite database query seconds restic

Related Articles