SQLite is not a toy database
antonz.org
antonz.org
No, the issue is it doesn't have high availability features: failover, snapshots, concurrent backups, etc. (Edit: oops, comment pointed out it does have concurrent backups.)
SQLite isn't a toy DBMS, it's an extremely capable embedded DBMS. An embedded DBMS is geared towards serving a single purpose-built client, which is great for a desktop application that wants a reliable way to store user data.
Once you have multiple clients being developed and running concurrently, and you have production data (customer accounts that are effectively legal documents that must be preserved at all times) you want that DBMS to be an independent component. It's not principally about the concurrent performance, rather it's the administrative tasks.
That requires a level of configuration and control that is contrary to the mission of SQLite to be embedded. They don't, and shouldn't, add that kind of functionality.
> Dqlite is a fast, embedded, persistent SQL database with Raft consensus that is perfect for fault-tolerant IoT and Edge devices.
Why do you say it works for "small" websites but presumably not large ones? If it's not transactions, concurrent reading, backups, or administrative tasks... then what's the issue you run into?
Genuinely curious... I'm wondering if everything I've heard about "don't use SQLite for websites" is wrong, or when it's right?
I'd guess the reason to be that people keep hearing things like "don't use SQLite for websites" and thus don't even try.
> Why do you say it works for "small" websites but presumably not large ones?
Not the GP, but the main reason I wouldn't use SQLite for a large website is that SQLite itself doesn't offer much re: failover/replication (i.e. multiple servers, one database), and I haven't used RQLite enough (or at all; I should fix that) to be comfortable with it in production. Because of that, I'm more likely to reach for / recommend PostgreSQL instead.
That being said, if your website has crazy "web scale" FAANGesque needs and you're at the point where you need to write your own replicated datastore, using SQLite as a base and building your own replication layer on top of it (or using RQLite and maybe adjusting it for your needs) seems like a reasonable way to go.
Exactly what Bloomberg did with Comdb2: https://github.com/bloomberg/comdb2
Wonderful job.
Would this enable any node to be a writer (i.e. would it lock the DB across all nodes)? Or would I have to designate some "master" server with exclusive write access and have any other servers forward write requests to that server?
[1] https://blog.expensify.com/2018/01/08/scaling-sqlite-to-4m-q...
SQLite just makes the tradeoff to be simpler since often it doesn't matter. But don't make the mistake that it doesn't matter. Since PG helps avoid data problems and you might need to scale out web servers that is why Django for instance recommends switching to Postgres (or whatever you're actually going to use) ASAP cause there are differences. You may end up relying on PG to reject things SQLite doesn't care about by default. SQLite might let you get away with inserting data which PG refuses to handle.
Not to mention the DB specific features can differ. Like PG's JSON field types or etc.
I should revisit this policy now that you can run a truly huge site off a 1U slot (I work for an alexa top10k, and our compute would fit comfortably in 1U); computers are so fast that vertical scaling is probably a viable option.
IMO a lot of organizations should start re-investing in on-premise as well. Having a mid-range AMD EPYC server can serve most businesses out there without ever having more than 40% CPU usage.
That, plus scaling down Kubernetes clusters. Most companies absolutely didn't need them in the first place.
Still, for the cloud I started preferring going for Rust (where applicable!) and optimizing the hell out of the performance hot-spots. So far the results have only been crushing successes. Horizontal scaling has been employed only as a means of backup instances with a load balancer in front of them. And for blue/green deployments.
Horizontal scaling hasn't been at all necessary otherwise. A $25 instance never got north of 15% CPU for one of the services I rewrote. I/O utilization never got beyond 60% and is usually 3-5%.
But I can agree that the cloud has unquestionable benefits. It's just that I feel that their number is gradually dwindling.
For instance I remember that at some point sqlite didn't have foreign keys, so I couldn't run my existing migrations on it (without rewriting everything) which was a big issue for me at the time. Now I see that they've added the support for referential integrity in the meanwhile, but I had no idea about it because simply there's so many other libs and technologies to follow - one just can't keep track of every single tool in the world obviously - so some great tools just fall out of focus.
My guess is the word will slowly get out and in a few years people will probably shift to using it more in a web world - but it will take some time.
I think it has concurrent backups via the backup api: https://www.sqlite.org/backup.html
And it doesn't have a native date type. Date handling has to be handled at the application layer. It can be tricky to do massive time-series calculations or date-based aggregations.
You can use integers or text types to represent dates, but this open-endedness means you can't share your db because everyone implements their own datetime representation.
In terms of scale, sqlite is just fine. But I am tired of fiddling with the dates, it's too easy for bugs to sneak into my code, and I want to use table valued functions to essentially parameterize views instead of having to build complex queries in the app layer.
If your web app is mostly reading and writing single rows, yeah, sqlite is just fine. But if there's substantial and complex logic involved, it has its limits.
The JSON extension library is amazing and works well. If SQLite were to grow a first-rate RFC 3339 library, one which could read from tz when available and do the things which strftime cant, acting on your choice of Unix timestamp and valid RFC 3339 date string, this would be a real boon to the ecosystem.
I haven't found typechecking inputs to be a real barrier. Sure, `val INTEGER CHECK (val = 0 or val = 1)` is a long way to spell `val BOOLEAN` but `CHECK json(metadata)` is a reasonable way to spell `metadata JSON`, and a similar function would surely exist for a SQLite datetime extension. You can do it now by coercing the string through an expected format, but that doesn't generalize well.
OTOH, as an embedded database with freeform advisory typing and easy extensibility (for functions, etc.) in most host languages, it's not hard at all to get whatever you need for datetimes if you are using a host language that has a decent datetime library itself (and, as a bonus, you then don't have to worry about subtle differences between manipulations of datetimes through SQL and manipulations through other mechanisms in the app.)
What it doesn't support is concurrent writes only.
I'd much rather SQLite not waste its time on implementing features they're not good at, leaving that to the tools we already have available for rolling file backups, instead spending their time and effort on offering the best file-based database system they can.
(Heck, even failover is just a file copy initiated "when your health check sees there's a problem")
Instead you need to use the .backup mechanism or the VACCUM INTO command, both of which safely create a backup copy of your database in another file - which you can then move anywhere you like.
The main issue here is time: because copying temporarily locks the db out of further changes, it will be "out of sync" if you naively believe that performing a write means your next call will see that data, and you bake that assumption into your code. While there's a wait, on modern hardware using SQLite for its intended purposes (any volume of reads, but low volume of writes), that's just not an issue.
There are other problems associated with file-based backup, of course, such as missing out on in-memory data, or corruptions caused by power outages, but those problems only exist if we were to copy a db once. Backups run regularly, and any data we miss out on the current pass, we'll get on the next pass.
Basically: when used for the purpose that SQLite was created for, file copies are a perfectly fine backup strategy that really only shows its limitations when you start to push your project into "SQLite isn't really appropriate here anymore" territory.
(having said that: the fact that VACUUM can be run into a new file is super nice, and everyone should know that it exists)
Transactions are only atomic from the perspective of other transactions. Other processes or commands on the system can see partial state. This applies to both the rollback journal & the WAL modes.
File copies are also covered in their "How to Corrupt a SQLite Database File" web page[1].
[1]: https://www.sqlite.org/howtocorrupt.html#_backup_or_restore_...
For SQLite, "copying files", rolling file backups, shadow volumes etc. are by definition valid strategies when it comes to SQLite.
SQLAlchemy, at least, lets you switch from SQLite to Postgres in a configuration file.
Isn't there a useful SQL subset which allows you to switch from one database to another without rewriting? There seems to be such a subset for C, for example, which multiple compilers all interpret the same way, and SQL is a standardized language, too.
Using a wrapper like SQLAlchemy is probably an improvement for most medium and large projects, but it has costs of its own. Using portable ANSI SQL - above the Hello World level - is something basically nobody does unless they make a serious effort, lean on linting tools and forego some of the most useful features of their DBMS. sh is perhaps a closer comparison than C here: it's at least possible to write portable C accidentally.
And neither of these help you if you started with a SQLite database - an excellent choice for most - and decide that you need to support a dozen or a hundred concurrent users.
Only if you're happy throwing away a lot of the features, which is taking away from what makes SQL an attractive solution in the first place. Plus, even some basic things aren't the same between different implementations (e.g., SELECT TOP 1 * FROM t vs SELECT * FROM t LIMIT 1), so you'd really have to be testing with different RDBMSes the whole way through to make sure you didn't accidentally break compatibility.
It absolutely has good use cases, but those are rather niche. You'll mostly be better off with the traditional postgres etc.
http://litereplica.io - single-master replication
http://litesync.io - multi-master replication
https://aergolite.aergo.io - highest security replication, using a small footprint blockchain
It's....not great.
> But use caution: this locking mechanism might not work correctly if the database file is kept on an NFS filesystem. This is because fcntl() file locking is broken on many NFS implementations. You should avoid putting SQLite database files on NFS if multiple processes might try to access the file at the same time.
Imo part of that is just web applications tend to be not very efficient when compared to something like unix cli utilities so to get performance you end up with potentially massive amounts of horizontal scaling
On the other hand, the lack of performance /usually/ buys you higher productivity so you can make product changes faster (you let a GC manage the memory to save time coding but introduce GC overhead)
It's less about performance than about resilience IMO. Of course if you don't have a proper HA datastore (master-master) then you kind of undermine that, but a datastore SPOF is better than the whole application being a SPOF.
For my reporting/read-only use case https://datasette.io/ solves the above beautifully.
On GCP:
- A regionally replicated disk will replicate writes synchronously to another zone (another data centre 100km away). This means when the SQLite write call returns, the data will be geographically replicated, and the second disk can be used as a fail over. This is all transparent to app/db.
- Disk snapshots are incremental, and are stored in Cloud Storage, which is geo replicated across regions (E.g. Europe and US).
In a way, this gets you even better failover and backups than traditional server DBMS's that have these features built in, and often need custom administration to ensure they are working.
Actually, I think it's network access that is the issue. SQLite isn't really designed to have 4-5 network connected apps using it as a shared data store. In most shared hosting setups, database is treated as a network service, so you can have 200 different sites leveraging the same db server... and sometimes the db server isn't on the same hardware as the http server.
Friendly reminder that you shouldn't spend time fine tuning your horizontal autoscaler in k8s before making money.
Mindblowing.
Aren't these symptoms of a deeper problem? Many Product Manager I talk to wanted me to build something that is as flexible as possible and solves all problems for everyone, everywhere. Go microservices with gRPC and Kubernetes feels like the only high-level technical decisions I can take in light of such information. :)
* I'm worried about my server blowing up: Transactions have to be committed to more than one DB on separate physical hosts before returning.
* I'm worried about my datacenter blowing up: Transactions have to be committed to more than one DB in more than one DC before returning.
At any given point in time, we retain 7 years worth of files in S3. That's approx. 2275 files for under $10/month. Anything older, is archived into AWS Glacier...all while the data is still accessible within Clickhouse. As of right now, we have 12 years worth of data. Hope it helps!
p.s., I run the SF Bay Area ClickHouse meetup. Sounds like an interesting topic for a future meeting. https://www.meetup.com/San-Francisco-Bay-Area-ClickHouse-Mee...
Oh, but most companies need this!
The biggest feat of microservices (which require ways to manage them, like k8s) was to provide the ability for companies to ship their organizational chart to production.
If you don't need to ship your org chart and you can focus on designing a product, then you can go a long way without overly complicating your architecture.
If you have two services that are completely orthogonal, then combining them into a single application for deployment and operation purposes can be limiting. Developers of all people should understand the benefits of decoupling.
There are a lot of downsides that come with jamming a whole lot of unrelated functionality into the same deployment unit. It's similar to the problems created by global variables. Once you start to depend on your services operating in a monolith, you can easily create rigidity that can be difficult to roll back.
It's not that you can't design a monolith well, but microservices force you to consider important boundaries as opposed to simply violating them for the sake of convenience. It's another "human" issue, but it's unrelated to org charts.
This doesn't mean that everything should be a microservice, though. Good architecures address the requirements of the systems being developed.
This is an absolutely enormous trade-off between logical modularity and run time operational complexity.
If your devs aren't good enough to enforce modular design in a single application, what makes you think they're good enough to handle complex distributed systems?
That's a simplistic take on the reasons that monoliths tend towards breaking modularity.
What makes me think managing microservices is perfectly viable is that the last three companies I've been at have been able to do it successfully, with a typical mix of good and less good developers, including cheap offshore devs.
As for handling "complex distributed systems," in many cases all that's really needed is something like a managed container platform. Services like Fargate or Cloud Run, or managed Kubernetes, can do a good job of this. Developers can deploy and publish new services with minimal effort, and most of the operational complexity is managed by the platform.
You do ideally want someone paying attention to overall architecture to avoid obvious pitfalls, such as effectively doing distributed joins via REST calls, and so on. This isn't that hard to understand, though, and teams that don't do the necessary architecture upfront tend to figure it out once they run into those problems themselves.
https://github.com/sql-js/sql.js - SQL.js lets you run SQLite within a Web page as it's just SQLite compiled to JS with Emscripten.
https://litestream.io/blog/why-i-built-litestream/ - Litestream is a SQLite-powered streaming replication system.
https://sqlite.org/lang_with.html#rcex3 - you can do graph-style queries against SQLite too (briefly mentioned in the article).
https://github.com/aergoio/aergolite - AergoLite is replicated SQLite but secured by a blockchain.
https://github.com/simonw/datasette - Datasette (mentioned at the very end of the OP article) is a tool for offering up an SQLite database as a Web accessible service - you can do queries, data analysis, etc. on top of it. I believe Simon, the creator, frequents HN too and is a true SQLite power user :-)
https://dogsheep.github.io/ - Dogsheep is a whole roster of tools for doing personal analytics (e.g. analyzing your GitHub or Twitter use, say) using SQLite and Datasette.
However it can only store strings so it's pretty taxing.
Chrome and Safari actually embed SQLite directly as WebSQL, however it's not becoming standardized because Mozilla doesnt consider "add SQLite" as a sensible web standard.
Can you write an offline HTML5 webapp with any of these libraries such that it can cerealize the entire database into a string and then store that to localStorage and reload the next time?
It's an honest question and not sure why people are downvoting without giving reasons.
You'd have to handle loading/saving though
Something simple so that the front-end app developer can just focus on implementing business requirements.
You either need to find a solution that has a sync system built in (such as <https://dexie.org/> or <https://pouchdb.com/> ) or you'd roll your own atop whatever you were using.
oooh, thank you! I'm starting to see SQLite the same way!
EDIT: It's actually 1.2MB. Thanks for pointing it out :)
I initially had low expectations because it's such a weird use case, but it's been totally reliable. We did have to ignore a few types of errors from old browsers that don't support wasm properly, but we've never had a bug in current browsers caused by sql.js.
I fail to understand why have a blog at all if its author don't like people linking to it.
More interesting commentary here https://softwareengineering.stackexchange.com/questions/2202...
1. SQLite doesn't really enforce column types[0], the choice is really puzzling to me. Since schema enforced type check is one of the strong suit of SQL/RDMBS based data solution.
2. Whole database lock on write, this make it unsuitable to high write usages like logging and metric recording. WAL mode will help but it will only alleviate the issue, you will need row based lock solution eventually.
Just like the offical FAQ said, SQLite competes with fopen[1] instead of RDBMS systems.
--
This is acknowledged as a likely mistake, but one that will never be fixed due to backward compatibility:
> Flexible typing is considered a feature of SQLite, not a bug. Nevertheless, we recognize that this feature does sometimes cause confusion and pain for developers who are acustomed to working with other databases that are more judgmental with regard to data types. In retrospect, perhaps it would have been better if SQLite had merely implemented an ANY datatype so that developers could explicitly state when they wanted to use flexible typing, rather than making flexible typing the default. But that is not something that can be changed now without breaking the millions of applications and trillions of database files that already use SQLite's flexible typing feature.
Having a tool like this would made my life whole lot easier, well, one can dream.
I wonder if a fork/"new version" could address this. Like, sqlite2 (v1.0, etc.).
But IMO that would just warrant an entirely separate software package. Shoving it inside sqlite3 is likely to make it much more complex.
> But lesser known is that there is a branch of SQLite that has page locking, which enables for fantastic concurrent write performance. Reach out to the SQLite folks and I’m sure they’ll tell you more
Other language bindings do things like this also, and makes it pretty idiot proof. you `create table test (mydict dict);` so your tables know their types, and then at bind time you say a sqlite column type of dict == a python dictionary.
Obviously python is sort of a terrible example, because python typing is somewhat non-existent in many ways, but you see the point here.
2: There are definitely cases where it won't work out well, high-concurrent write load is definitely it's big weak spot, but those are usually fairly rare use cases.
If the speed of your write-only workload is limited by whole file locks rather than by raw I/O speed, you can probably consolidate your writes into fewer transactions (i.e. fewer disk accesses, amortizing lock cost over more data) and write to several databases in parallel according to any suitable sharding criteria. Which is what any RDBMS would have to to anyway.
It's not the worst thing in the world; you're validating on data ingest anyways to prevent sqli, for example, right?
Think of SQLie as having a weird dialect where `Col1 INTEGER` is spelled `Col1 INTEGER CHECK (typeof(Col1) IN ('integer', 'null'))`. Ideal? No, but also, not a showstopper.
There are a few good reasons not to use SQLite. Your point 2 is one of them, running the client on one machine and accessing the database file via a network file system is another. Although I've pushed write-heavy workloads pretty hard with some care, it's easy to create a situation where contention becomes untenable. You can really pound on it with one client, but with several it gets dicey.
https://www.sqlite.org/appfunc.html
We just started enhancing our SQL dialect with new functions which are implemented in C# code. One of them is an aggregate and it is really incredible to see how it simplifies projections involving multiple rows.
One huge benefit of SQLite's idea of UDFs is that you can actually set breakpoints and debug them as SQL is executing.
One of my company's applications is already designed to work with different SQL systems and a new customer desperately wanted SQLite for a very special use case. As SQLite is quite simple and doesn't support many functions that are standard in SQL Server, MySQL, Oracle, etc., we used application-defined functions to implement all functions the application needs in C#. It's not very fast but also not slow and the customer is happy, which is what really counts.
See: https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite...
One common use case is REGEXP. SQLite has a keyword for regular expression matching, but it has no implementation for it. What your application needs to do is to take whatever regex library it's using, make (through whatever FFI method it's using) a C function that interfaces with it, and register it with SQLite.
A more advanced feature of this binding mechanism is that, if you provide a bunch of specific callbacks to SQLite, you can expose anything you like as a virtual table, that can be queried and operated on as if it was just another SQLite table.
See: https://www.sqlite.org/vtab.html. The plugin for full-text search is implemented in terms of this mechanism.
Edit: Sometimes you have to lie and lead people down the wrong path to enlightenment... ;)
;)
Nobody said you have to use a single database/file. Obviously, you are going to want to spend a couple minutes thinking about referential integrity. But how often do you delete records in your web app?
If your deployment environment has serious constraints, I'm sure you could make it work. The product you deliver would be SQLite + custom DBI layer to hide SQLite's limitations.
It would be a lot more work, and not be as robust or scalable, compared to a more traditional selection. But I can imagine cases where it would be appropriate.
Per user.
While reads are more like once every day per user.
All of those connections might need to write, and this is where SQLite gets tricky to implement at scale.
I love SQLite! It's perfect for many use cases, but not all. Fortunately, Postgres is also excellent.
And that's assuming you're making queries every time an event occurs versus persisting data at particular points in time.
That number will obviously depend on the read/write ratio of any given website; but it's hard to imagine any website where [EDIT the number maximum number of concurrent users] is actually "1". And for many, that will be in the thousands or hundreds of thousands.
FWIW the webapp I use to help organize my community's conference has almost 0 cpu utilization with 50 users. Using sqlite rather than a separate database greatly simplifies administration and deployment.
I can imagine a static website where the content is read-only for users and is only editable by admins/developers/content managers through some CMS.
Suppose, on the other hand, that a single user generated around a 1% write utilization when they were actively using the website (which still seems pretty high to me). You could probably go up to 120 concurrent users quite easily. And given that not all of your users are going to be online at exactly the same time, you could probably handle 500 or 1000 total users.
Of course applications like a blog will have far more reads than writes. It just really varies depending on the type of application.
Remember, the person I was replying to claimed SQLite was "not useable outside of the model... where you have a single user". Yes, if you need 100k concurrent users doing a 1ms transaction every second, SQLite isn't for you. But 1000 concurrent users is a lot more than 1.
Although on reflection, what they may have meant for "single user" is a single process (perhaps with multiple threads). That sounds reasonable to me: people just don't realize how much you can actually do with a single multi-threaded process running on a modern server.
Only if your readers and writers are cleanly segregated.
Most languages and web frameworks don't have SQLite drivers out of box (or have extremely bad ones). Unlike SQLite, most databases don't really care about distinction between read-only and writable connections. So there is a good chance, that you will always open writable connection by default, because this is what your framework/ORM does. Furthermore, seemingly read-only web middleware often ends up writing to database on each request for one reason or another. If you try to reuse/pool connections (which is also important under high load), you need to be wary of keeping open writable connections in cache — again, something that does not matter to all major databases other than SQLite.
I was involved in maintenance of a small web app (db size < 5 Mb), written in Django, that had to serve ~1000 dynamic requests per second (the contents of each response were dependent on IP address of caller). The app worked with PostgreSQL, albeit poorly, but immediately ground to halt under load with SQLite — which was our default database choice for historical reason.
We ended up briefly caching results of most database queries in memory, which removed most of load from database (we also did a lot of other optimizations, but this was the decisive one). Eventually the app was able to withstand up to 9000 requests per second, but none of that was an achievement of SQLite — we just evaded database, Django and Python altogether on majority of requests.
While we are on this topic, the most widespread OS in the world, Android, also has extremely low-quality SQLite drivers — despite shipping SQLite as default database for many years. Android has a broken-by-design Cursor implementation (the devs admitted it themselves [1]), that always tries to count query results, even if you don't call getCount(). And a broken connection cache, that does not support read-only connections [2] (that method used to have a "TODO", but eventually they forgot, why they wanted it, so they removed it).
1: https://medium.com/androiddevelopers/large-database-queries-...
2: https://android.googlesource.com/platform/frameworks/base/+/...
Also, for your small app were you using WAL mode? Multiple readers only works in WAL journaling mode.
Our Django setup needs multiple processes to work around the Grand Interpreter Lock. Disabling connection reuse in Django config slightly changed the behavior we observed, but didn't solve the performance problem.
SQLite is perfectly capable of supporting multiple, parallel reads.
SQLite must serialize writes, which makes a highly parallel write-heavy workload not good for it. However, with WAL enabled writes do not block reads.
Basically highly-parallel read loads with low write counts (low-enough that serializing them doesn't lead to unacceptable slow down of writes) or with loads where latency is acceptable in writes (but not reads) is a perfect use case for SQLite. And it turns out that a lot of web services are heavily asymmetrically biased towards reads.
Note that WAL and synchronous flags must be set appropriately. Out of the box and using the standard "one connection per query" meme will handicap you to <10k inserts per second even on the fastest hardware.
The trick for extracting performance from SQLite is to use a single connection object for all operations, and to serialize transactions using your application's logic rather than depending on the database to do this for you.
The whole point of an embedded database is that the application should have exclusive control over it, so you don't have to worry about the kinds of things that SQL Server needs to worry about.
SQLite is not a direct replacement for SQL Server, but with enough effort it can theoretically handle even more traffic in your traditional one-database-per-app setup, because it's not worrying about multiple users, replication, et. al.
It's not the right approach because it's hard to get right, you want to offload that to the DB.
Also, the only reason we ever want to lock a SQLiteConnection is to obtain a consistent LastInsertRowId. With the latest changes to SQLite, we don't even have to do this anymore as we can return the value as part of a single invocation.
Thats... amazing! What is your setup like?
I've always been surprised WordPress didn't go with SQLite though - it'd have made deployment so much easier for 99% of users running a small, simple blog.
Most these sites are running a homepage, an about page, a contact page, and maybe one or two misc pages. They might have a blog that has two blog posts on it from nine years ago. But that is really it. MySQL is really overkill considering the scenario. SQLite's big "limitation" is non-concurrent writes. But this is rarely a problem with most Wordpress sites because they are single-author and they aren't updated very often. SQLite can handle plenty of reads to support even heavily trafficked websites.
Not to mention, SQLite's greatest advantage is portability. A single file contains your entire database. A non-technical user could transfer hosts or backup their data by copying their database file like it was a photo or an excel document. That's pretty incredible when you think about it.
I was a casual developer (i.e. not for work, just for personal) deep into the PicoCMS ecosystem for a couple years, a few years ago. I both started a site and helped a family convert an old static site to PicoCMS and really had no complaints. Re: frontend for the owner, I started with Pico Admin and made a bunch of modifications to it (including an image uploader) and the non-tech owner has no complaints and it's been working well since.
Nowadays for my own blog I'm into the whole SSG/JAM trend, but I'd still run a PicoCMS site any time, if the use case is right.
Perhaps not great for production since Wordpress automatically updates itself, and you would have to keep up with any changes. And not just for wordpress, but for any other plugins that use the database.
Edit: A single file fork (albeit 5k lines of PHP) of the plugin that looks interesting: https://github.com/aaemnnosttv/wp-sqlite-db
If my goal is building a website, I don't necessarily want to experiment with different technologies if I already know Postgres will work perfectly fine without adding much operational overhead and covering use cases I don't have yet, vs the unknown unknowns of using SQLite and maintaining it over time. Again, this is not about some problem with SQLite, but just me not having experience using it this way. Same reason why I wouldn't just add any database system I haven't used before, even if on paper it would be the "better tool for the job" for a particular use case, and sure would be an interesting learning experience.
In my opinion practicality and prior experience often beats what is strictly necessary or "best".
But not wanting to get out of your comfort zone is a completely valid stance to take. We do this for money after all.
E.g. I got interested SvelteJS exactly because I got jaded of how ridiculously complex many front end applications have gotten, albeit they are doing just barely more than fetching a JSON from a server and turning it into html. Then I got a really good use case for using it in production because the low end mobile phones and bad internet connections of our users were struggling with the very heavy SPA the company started off with.
For another project in the future, it might well be SQLite that is the more experimental part, but in the end it comes down to managing risks and benefits.
While one part of me would love to experiment with everything all the time, the other part likes to finish the work day on time to be able to have plenty time dedicated to non tech related things and that sleeps well at night being fairly sure that stuff is running smoothly
You can only register one callback per table tho, although you could from this callback fire other functions... All in all it's an awesome tool for a project like tailscape, but I think the hackers there went for a flat file.
Personally I'd would love to see in process postgres; a build of postgres that is geared for integrating a set of your threads, and builds the whole of postgres with your app on all major OSs, only listening to the inside by default. For the same reason I'm using nodejs; to be able to run the same code anywhere. I think bundle size would be a minor issue, really, I downloaded Sage9.2 yesterday, it's 2GB! VSCode is 100MB download, and they refer to it as a small download...
cheers! happy coding,
It describes how access patterns that would be bad practices with networked databases are actually appropriate for SQLite as an in-memory DB.
Just write the code to create an empty database if the db file does not exist, move your code elsewhere, and the database will be created on the first run. No usernames, passwords, firewall rules, IP addresses, no nothing... just a single file, with all the data inside.
Miration? Copy the whole folder, code and the database. Clean install.. copy the folder, delete the database. Testing in production? Just backup the file, do whatever, then overwrite the file.
Combine with a Golang AST lib and you might be able to make a CLI translate tool like `fix`.
I had to work on a data import/export tool some time ago and SQLite has simplified the design a lot.
[0] https://media.ccc.de/v/36c3-10701-select_code_execution_from...
In my use case, files are shared between different instances of the application, usually without user intervention, but there's an attack vector to be addressed here.
Let me say that again: SQLite does NOT execute arbitrary code that it finds in the database file. To suggestion that it does is nonsense.
See https://www.sqlite.org/security.html for additional discussion of security precautions you can take when using SQLite with potentially hostile files. The latest SQLite's should be safe right out of the box, without having to do anything mentioned on that page. But defense in depth never hurts.
The documentation on the fts3_tokenizer function merely states that
Prior to SQLite version 3.11.0 (2016-02-15), the
arguments to fts3_tokenzer() could be
literal strings or BLOBs. They did not have to be bound
parameters. But that could lead to security
problems in the event of an SQL injection. Hence, the
legacy behavior is now disabled by default.
However, it does not discuss any mitigations for the case where SQL injections are not needed, because the attacker controls the database file.Since 2016, arguments to the fts3_tokenizer() function must be variables (ex: ? or :var or @var or $var) to which values are supplied by the application at run-time using sqlite3_bind_pointer(). There is no way to do this from pure SQL script. Nor is there any way to do this from within a view or trigger. There is no way to invoke fts3_tokenizer() from a maliciously corrupted schema or database.
E: oh whoops missed the dev post above mine
the example query given:
select
json_extract(value, '$.iso.code') as code,
json_extract(value, '$.iso.number') as num,
json_extract(value, '$.name') as name,
json_extract(value, '$.units.major.name') as unit
from
json_each(readfile('currency.sample.json'))
;
that sure looks fun to type into a repl!nothing against sqlite, which I like and use, just found the idea of that query being convenient for one-off analysis to be off.
Personally I love jq[0] for this purpose. I haven't really used SQLite for working with JSON, but the examples given are very verbose.
echo '' | fzf --print-query --preview 'jq {q} input.json'
[1] https://en.wikipedia.org/wiki/C4_(conference)#C4[2]
[2] https://medium.com/devseed/portable-map-tiles-format-release...
I like SQLite a lot, but I found SQLite's recursive CTE implementation to be somewhat limited: https://dercuano.github.io/notes/why-html-is-not-a-programmi...
Has that improved?
(sorry about the embarrassing arrogant pedant attitude in that note, but it's too late to fix it now)
The 3.35 release added more good stuff for CTEs, as well as a RETURNING clause and a much more flexible UPSERT.
I think it's still the case that SQLite doesn't permit the use of recursive CTEs in subqueries.
I have make a copy of the Collatz Conjecture CTE that you linked to and was going to see if I could get it to work in SQLite for the next release cycle. I don't (yet) see any reason why it shouldn't work to have the recursive reference down inside a subquery, as long as there is only one recursive reference. No promises. We'll see how it goes.
Have you thought about offering an interface with an intentionally-Turing-incomplete subset of SQL in SQLite, for making queries that can be guaranteed to terminate? Because it doesn't sit right with me that SQL is Turing-complete now. https://news.ycombinator.com/item?id=26529789 goes into more detail on decidable query languages that can still accommodate transitive closure.
https://blog.expensify.com/2018/01/08/scaling-sqlite-to-4m-q...
Sqlite is OLTP though, for analytical purposes I'd use an OLAP DB like DuckDB.
IMO dumping workload in DB is nice, specially when your dataset doesn't fit in RAM.
It has some plotting built in. Not sure if it is sophisticated enough for what you need but it might be worth a try.
Another advantage you get is if you wanted to look at these intermediate data, you don't need to run your code in debug mode and view the dataframe at a breakpoint - you can use something like datasette[1] or a standard SQLite DB viewer.
So a function that does complex data processing has approximately this structure my code:
(1) <non-pandas python code to fetch data, other "normal" stuff>
(2) <pandas code to do basic navigation, filtering, grouping etc>
(3) <dump to SQLite, followed by SQL queries for the really heavy stuff>
(4) <back to python, possibly pandas, to return results, write to a file, plot etc>
Step (3) used to be pandas for me before, but depending on how complex your operations are this can become hard to read and/or review.[1] https://github.com/simonw/datasette - datasette doesnt replace a standard DB IDE, but is a very good lightweight alternative to one if don't intend to perform updates/inserts directly on a table.
Situations Where A Client/Server RDBMS May Work Better Client/Server Applications
If there are many client programs sending SQL to the same database over a network, then use a client/server database engine instead of SQLite. SQLite will work over a network filesystem, but because of the latency associated with most network filesystems, performance will not be great. Also, file locking logic is buggy in many network filesystem implementations (on both Unix and Windows). If file locking does not work correctly, two or more clients might try to modify the same part of the same database at the same time, resulting in corruption. Because this problem results from bugs in the underlying filesystem implementation, there is nothing SQLite can do to prevent it.
A good rule of thumb is to avoid using SQLite in situations where the same database will be accessed directly (without an intervening application server) and simultaneously from many computers over a network.
High-volume Websites
SQLite will normally work fine as the database backend to a website. But if the website is write-intensive or is so busy that it requires multiple servers, then consider using an enterprise-class client/server database engine instead of SQLite.
Very large datasets
An SQLite database is limited in size to 281 terabytes (248 bytes, 256 tibibytes). And even if it could handle larger databases, SQLite stores the entire database in a single disk file and many filesystems limit the maximum size of files to something less than this. So if you are contemplating databases of this magnitude, you would do well to consider using a client/server database engine that spreads its content across multiple disk files, and perhaps across multiple volumes.
High Concurrency
SQLite supports an unlimited number of simultaneous readers, but it will only allow one writer at any instant in time. For many situations, this is not a problem. Writers queue up. Each application does its database work quickly and moves on, and no lock lasts for more than a few dozen milliseconds. But there are some applications that require more concurrency, and those applications may need to seek a different solution.
Last time I heard about this, it wasn't looking very good.
I keep thinking it would be sort of fun to do a tk app again; is there a good model CRUD app for tcl/tk/sqlite out there? Something like the Northwind thing for Access?
It's commonly used in applications that use it as a convenient container to store data (the "file format" scenario) or as a domain specific database. One of the better examples of the latter is Calibre, an e-book library manager, since it exposes some of the database functionality to the end user. For example: the end user can add columns to store custom data for each book.
There are quite a few mature embedded DB options for Java actively developed, for example Apache Derby/HSQLDB/H2. The SQLite fileformat is very portable, which is very handy and useful, the SQLite C Library is very powerful. It'd be nice for the Java community to tap into that power.
1) https://www.sqlite.org/java/file?name=doc/overview.html&ci=t...
2) https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite...
[1] https://docs.bareos.org/bareos-18.2/IntroductionAndTutorial/...
[2] https://docs.bareos.org/bareos-18.2/DeveloperGuide/catalog.h...
There's three major things I'm still struggling with though:
- Should I switch from using an ORM (gorm) to using more raw SQL with some utilities to reduce tedium and boilerplate?
- Versioning; users will want to be able to revert to a previous version of a configuration (not just a single table, a set of tables)
- Redundancy; if the primary node fails, it should switch over to a secondary node, ideally in another location. The existing app copies the .db file to a `sync` folder after certain operations, which is then sent to the secondary server via rsync. It's actually pretty elegant if you think about it?
Anyway, none of these are really SQLite specific issues.
I would think this would be one of the easier features to add.
Of course, I'm sure the people working on MySQL or SQLite would say "Patches welcome!"
Maybe if you use SQLite as a file format. But if you use it like an actual database (e.g. in a web application), I find that one is best off setting up a daemon thread to queue/batch transactions.
[I assume if you are using this pattern successfully in production you are already aware of and taking proper steps to avoid them, but the pedant in me takes issue with "without any issue", since once you have multiple resources being locked in multiple threads, you need to be careful to acquire and release locks in such a way that deadlocks do not occur, either never acquiring more than one lock at a time, acquiring locks in specific orders, or acquiring batches of locks at a time.]
It's easier than you think to corrupt SQLite if you access from multiple threads and especially from multiple processes (yup, I've done this before).
Also, there are no concurrent transactions in SQLite. The entire db file gets locked (using POSIX locking, which is known to be broken [0]). Better to queue/batch transactions on a single connection. If your web server consists of multiple processes, then this requires a separate daemon.
I miss the days when databases were included in office suites or could be purchased as relatively inexpensive standalone applications since it was easy to create a database and associated forms, rather than depending upon domain specific applications that use a database as a file format but may be poorly suited for a particular scenario.
(Yes, the phrasing "file format" verses "actual database" rubbed me the wrong way.)
Edit: I realized that my post comes off as dismissive of SQLite, which is not entirely true. I use it as a database and appreciate it's role, but embedding it in an application is frequently more than a given application requires.
As for Access, it's access is limited due to the much higher price point of Business/Professional versions of Office. In terms of office suites, that version is about two to three times more expensive than an equivalent office suite from the mid-1990's (adjusted to 2021 dollars). In other words, people will only have Access if they feel that the additional cost is justified.
That additional cost may be fine for business use, yet it also means that people have less exposure to databases to start with. With respect to exposure, there also appears to be an absence of general purpose databases for home users these days. (By that I mean in terms of cost and ease of use.)
I thought that they dropped JRE requirement when the engine was switched to Firebird? Googling around apparently the transition wasn't quite successful :(
https://ask.libreoffice.org/en/question/279711/firebird-dead...
Firebase is still available as an experimental option as another embedded database, just confirmed on my installation. Also while I was at it, tested it with Java disabled and HSQLDB predictably did not work, but Firebird did continue to work. Apparently it also opens an pre-existing Firebase odb file even without experimental flag being on.
Knowing that I can do anything and the change hits 3 hard drives in a span of few seconds on various machnies is nice. Tolerance for arbitrary down time of master/slave clusters is also nice.
But yeah, for quick one-off data munging, sqlite looks quite nice.
It's being done at cluster level, it's one time setup and I can forget about it.
I recommend getting a book on it. I have the O’Reilly one. (https://sqlite.org/books.html). I’ve used it so much in so many different scenarios. Such a great tool to have on the toolbelt.
What amount of SQL queries per page render is considered sensible?
When I run more than 20 queries per request in my Rails apps (smallish internal tools for different companies) I get uneasy. I usually deploy the app on the same machine where the DB (not SQLite) runs, but I imagine if that weren't the case the app-DB roundtrips could soon dominate the whole thing.
and they do. We have a GraphQL API backed by multiple microservices at a place I work. You become increasingly paranoid at every call you need to make.
It's also where ORMs completely fall over. ORMs are designed for, generally, one record to one query mapping. Which is why they are always leaky abstractions which need heroic acts of clever hacking to overcome. You know, rather than doing the sensible thing and just writing a single query that fetches data from multiple tables.
SQLite sort of encourages many queries, though, due to their design. I recall their docs even mention this point. But I've seen a few people in various threads that get bitten by this when they switch to a client/server DB.
I think it's perfectly fine to design your app around the characteristics of your database. Otherwise, you're missing out on optimizations and features you could be using and what would even be the point in favoring one DB over another if it's all the same to your app? You should pick the DB that matches the characteristics of your app anyway.
> N+1 Queries Are Not A Problem With SQLite
>
> The SQLite database runs in the same process address space as the application. Queries do not involve message round-trips, only a function call. The latency of a single SQL query is far less in SQLite. Hence, using a large number of queries with SQLite is not the problem.
I'm genuinely curious if there are any real use-cases for this behavior.
Less than ideal, sure. But typing is there when you want to reach for it.
[0] https://www.sqlite.org/copyright.html
I've been toying around with it locally for playing with big data sets (50-100s GB) uncompressed and it works pretty well. It's much easier than postgres and mysql which have a lot more knobs and tuning required)
More of sqlite's sloppyness is detailed here https://sqlite.org/src/wiki?name=StrictMode
Honestly, SQLite is a wonderful tool if properly used. Calling it "sloppy" is a disservice.
Funnily enough, this is my favorite feature of sqlite that I wish other RDBMSs had :))
There is no easy equivalent to `SELECT order_id, status AS latest_status, MAX(updated_at) AS updated_at FROM orders GROUP BY order_id`. Well, it's possible, but for example in Postgres you need some silliness with nested queries, `PARTITION BY`, and `ROW_NUMBER()`.