An unscientific benchmark of SQLite vs. the file system (btrfs)
github.com
github.com
> we're writing the entire file each time
This is a super-common misunderstanding of Copy-On-Write. Entire files are not re-written on each write to a file. New blocks are written and then the (btree) metadata is updated to include the updated block in the file. [1] This causes greater fragmentation than writing new blocks over old ones in the files existing structure as non-COW systems can do. Another thing which somewhat slows btrfs is all blocks have checksums calculated, stored in metadata and checked on read. If entire files really did need copied on each edit - it would be a great deal slower !
[1] https://btrfs.wiki.kernel.org/index.php/Btrfs_design#Files
It’s not a comparison as being in sqlite makes the ability to access this data significantly easier. This is comparing apples and dogs and i don’t see the merits.
SQLite advertises itself as an fopen replacement. Sounds like a perfect match for parent’s use case.
I find it much easier to add features to my post-2007 projects (when I started using SQLite) for the specific reason that I can open the SQLite file in a GUI and pretty quickly see what’s going on with data organization (schema) and how the customer uses the software I wrote (ie by what columns they actually use/misuse).
Prior to that, there’s various versions of my b-tree library and lots of zips, or linear text indexes, or any combination of whatever fit the need. Data storage implementation needs to be reasoned about in detail for each pre-2007 revisit in ways that don’t happen with SQLite projects.
For more involved work that I do at my shop or on my laptop, DataGrip by Jet Brains is great. Before I got fed up with Apple, I used https://menial.co.uk/base/
DataGrip’s benefit to me is mainly reworking a customer DB at the ER diagram level, then I manually update my code to match.
But sqlite is still better, it is more reliable, a bad write on that end of zip index destroys the whole zip archive and sqlite also gives you a lot more potential flexibility later on if you need to add metadata or something else. It is better in terms of inserts and deletes, although you will still need to vacuum.
For this situation it does matter, but it is recoverable.
For the 32 bit address problem, you could read forward from the front, and as long as no entry was over 4GB, you could still read the file. If the file count was low enough you could cache all of the entry objects, and if reads dominated opens, then you were functionally back to O(1) access (but O(n) startup).
There was a 64 bit extension going around, but when I stopped working with zip files every day I stopped tracking the progress.
[0]: https://www.kernel.org/doc/Documentation/filesystems/ext4.tx...
Edit: note this isn't a universal property either, so it's still wrong: https://btrfs.wiki.kernel.org/index.php/FAQ#What_are_the_cra...
Also, even if we were on ext4, it's worth pointing out that the ext4 documentation refers to apps that rely on that behavior as "broken applications", so it's hardly a good term of comparison.
And damn near every application is "broken" according to the ext4 documentation.
I can answer this one.
This is conflating atomicity (atomic rename) with durability (fsync; whether data is guaranteed to be on disk and will survive power failure). These are two orthogonal things.
When you use sqlite with default settings, it will give you both atomicity and durability.
If you want to get both in a file system like btrfs, you need to (1) fsync the written file, (2) do the atomic rename, (3) fsync the directory that contains the renamed file.
Some file systems, like ext4, do automatic fsyncs "every couple seconds", but that is of course not something to rely on when benchmarking.
sorry!
- https://pkg.go.dev/modernc.org/ql
- https://pkg.go.dev/modernc.org/sqlite
It’s hard to find much information about them, but the first one seems to be a database similar to sqlite, and the latter seems to be some kind of automated translation of sqlite to Go. I’m not sure either of these will outperform sqlite on CGo, but both of them should eliminate the CGo overhead and might give you some more room.
P.S.: The latter is a bit unorthodox in that it contains separate code for each architecture supported by Go, most likely a reflection of the method of the automated translation.
Not sure if it was updated after your comment.
Actually, having a sqlite filesystem is intriguing because you could in theory add any kind of metadata or filesystem feature you wanted to (such as a forward-error-correcting checksum field, auto-compressed/decompressed data, dedup, encryption, etc.) It would make it nearly trivial for anyone to experiment with new filesystems.
I’ve played with ramdisk before but didn’t notice any difference, probably due the fs cache.
Edit: just ran it on EXT4 on Linode.
SQLite: 8.7s
EXT4: 18.3s
(Sadly, it seems like both benchmarks are flawed.)
Anyway, the perf is good enough for what I need, and the flexibility of querying makes it well worth it. There is something nice about plain text files, though.