SQLite the only database you will ever need in most cases (2021)
unixsheikh.com
unixsheikh.com
If you want to use a PaaS to deploy an app, because you don't want to spend your time learning to be a sysadmin, then all the tutorials are going to put you on the Postgres path, because that's what's supported. (Of course, you'll then end up paying $15+/mo for Postgres, which is hilarious for most hobby projects storing 50MB of data.) But in reality, you could just scale vertically on one machine and be completely fine. No need for "distributed" anything, in theory.
I took a shot at productionizing SQLite here as an experiment: https://cheapo.onrender.com/ But I'm not sure I did it right, because I don't have much experience working below the level of a PaaS. I'm an application developer and I do not want to become a release engineer. I resent even having to learn Docker. :-)
Anyway, if somebody wants to nitpick my Flask+SQLite deployment, I'd really appreciate it, because it seems really silly that people have to keep writing this stuff from scratch when 90% of hobby sites have the exact same needs. And the Fly.io/Render configs would apply just as well to Node, Ruby, etc. https://github.com/irskep/cheapo_website
Edit: Somebody got mad at the Docker bit. I tossed it in for effect, but I promise you I don't hate learning new skills. I wish people would recognize that weekend coding projects should be fun, and sometimes that means avoiding certain kinds of things that a person experiences as difficult or frustrating. Arguing on the internet sucks, who knew?
Sorry, but that's a terrible attitude to have. You don't need to be a full-blown sysadmin to know how to do basic deployments, and learning these things will make you a better developer.
If you just want to do the minimum to run the app, it will make you worse and usually utterly terrible sysadmin, as it usually is going to be "do the minimum required amount of copy-pasting from tutorials to make the stuff running".
Then inevitably don't update shit, nor even set unattended-upgrades package.
And if the server won't get hacked from bad firewall and no updates, the server will ran out of space because dev of course didn't thought about data management and some log file in wrong place without rotation overflowed.
That's from decade+ of fixing shit of developers that think that tutorial on internet made them sysadmin.
It's only going to increase cognitive load, potentially without end (?) which doesn't sound wise long term. :(
I learn new things every day, and one day, Docker was that thing. I am now better at release engineering, cool! But I also lost a day of working on shipping value to users and actually having fun, which is less cool. That's why PaaS has been helpful to certain kinds of people; it makes the curve of progressive disclosure necessary to deploy stuff a little flatter. My hope is that we can make more kinds of things really easy to do the right way, and not yell at people who just want to have some fun.
Sorry, I detest this attitude of: "Well I was hazed so why aren't you hazed too?"
Did I mention that Russ Cox and I used to write C code with pen and paper? https://news.ycombinator.com/item?id=32874759 If you haven't, are you even qualified to discuss what vital skills others are missing?
Or maybe because I learned perl because it was simpler than tr and awk, I should STFU? Give me a break.
Why make Docker and essential part of a system before you actually need it? There’s a reasonable chance that you never need it. I know this, because a lot of business and hobby systems have been in operation since before Docker existed.
Docker has value in many cases, but it certainly doesn’t have to be a necessity.
Go app which is one blob and some static file ? Sure. Java app that just needs JVM in system ? Go ahead, .service systemd file with some limits is all you need.
Ruby on Rails ? Put that radioactive shit in container, else your server will need to have a bunch of -dev packages just to compile gems and make it running (or alternatively you'd have to compile the gems on same environment server is running, which is more work than just making a container)
I've been deploying Rails since v 2, and never with Docker. The only time I ever had trouble was setting up Rails dev on M1 Mac when M1 was new. In Linux production, it just worked; and pulling/building gems was less a drag than what people regularly experience in the NPM world.
To be fair, I don't use a ton of gems because I generally don't like additional dependencies unless the value is really there. So maybe my use cases have been too basic to experience the pain.
You don't need to be a full-blown sysadmin to
know how to do basic deployments, and learning
these things will make you a better developer.
Yes. True. However, it is also true that we have a limited amount of time per week and a limited number of weeks on Earth. Time spent learning sysadmin-y stuff is less time spent mastering developer-y stuff. Think about what it means to be a "full stack" engineer in 2023: - Unix literacy including common tools (awk, sed, grep, etc)
- Docker, etc.
- Relational data stores
- Key/value and document stores
- Base frontend technologies (HTML, CSS)
- Frontend frameworks and the associated language
- Build pipelines to tie it all together
I've never worked with anybody who's deeply knowledgeable at all of them, or even most of them. I would submit that it is practically impossible to master all of them while also holding a full-time job and writing and shipping production code, and staying "current" on all of the above, unless you're willing to go way past 40-hour weeks.I've grown extremely disenchanted with "modern" software development. Your typical full stack engineer is frankly bad at most of the damn stack and they're not great at any of it. Nobody masters anything anymore. You get people with IQs three standard deviations above normal thinking they need to spin up an armada of AWS crap just to render "Hello World" in 2023. And they still manage to make a mess of it. I can't tell how much of this is brain damage and how much of it is a sociopathic commitment to resume-driven development.
In practicality, I'm a fan of "T-shaped" engineers. Master a piece or two of the stack, and have light knowledge of the adjacent layers and what they do. e.g. If you are an application-level coder you should know what Docket/K8s/etc are and when/why you might use it and when you should eschew them. And honestly I think it should be eschewed fairly often.
The places you can go with a bit of good old vertical scaling are pretty impressive in 2023. A single box with e.g. 24 cores, 64GB of RAM, and fast solid-state storage is a god damn supercomputer when it comes to spitting out web pages. Think about how far Stack Overflow got with that model and at which point you might start to outgrow them.
---
edit: By "mastery" I mean "fairly comprehensive competency" and not "be a recognized global authority." You can fluently use 50-75% of what the tool offers and you know what the other 25%-50% is so you can learn it and utilize it when needed. You can ship code using this tool that is maintainable and performant. For example, perhaps you've never utilized partitioning in Postgres but you know what it is and you understand the appropriate use case for it so you can learn employ it when needed.
I also have a wife and a 2.5 year old.
It’s certainly possible if you don’t stop yourself.
It's weird to say that people aren't learning these things because they're "stopping themselves." They're just going in other directions. No one in my life has ever accused me of learning too few things and failing to learn by building things.
Edit: I think I misunderstood the parent comment, sorry for the defensiveness here
Ran a business. Did everything from building the servers, the code, the marketing, the design, community management, merch, and event planning. Call it "ultra full stack," maybe?
So yeah, it's extremely possible.
But I'll also tell you bluntly I was only really good at a few of those things. Most of those things, I was bad at or perhaps more accurately I just acquired the bare minimal knowledge to get by in my specific and limited use cases.
And many of those skills decayed quickly. I knew a thing or two about SEO and front end development in let's say 2000-2012 but those fields change fast and little I knew back then is relevant now.
That's why I say that in 2023, I think the notion of "full stack developer" has grown untenable. You cannot be good (in the sense of shipping actual maintainable and performant production level stuff) at the entire stack simultaneously now that complexity at each level of the stack has multiplied relative to 2005 or 2015. For example, your own db -- is that a fun (and impressive!) hobby project or is that really production level stuff?
To be ultra clear, I am not knocking what you have achieved. It sounds awesome and I suspect you have dived deeper into more things than I. Kudos, and I mean that sincerely.
You did all of these in the last 2.5 years? If not, that part seems a bit irrelevant.
But regardless of the response, some kids are way easier than others (easy kids are those who like to eat and like to sleep, imho), and some parents have more help or stricter separation of parental duties.
The other thing that's going completely over people's heads here is that I made this repo with the intent of helping other people who know less than me. I know I can figure out all the deployment bullshit, but I mentor folks who can't, and also don't have much money, and yet still need to deploy their portfolio projects to the Internet in order to find jobs. I was trying to find a balance of ease-of-maintenance vs monetary cost. If there's an even simpler solution with an even lower cost, that a random bootcamp grad could maintain, I'd adopt it in my template in a heartbeat.
If any of these people claiming it's easy want to work with me on a reboot of the "deploy a web site easy and cheap" template, I'd absolutely collaborate with them on it. I think it's important to democratize all the cheap computing power lying around these days.
Hey, I'm the guy who wrote that original response to you. I apologize if that's how you interpreted my comment, but that's not what I meant by it. I wasn't even trying to say that you're a bad engineer (and certainly not a bad human being), I just wanted to say that I disagree with the attitude of that comment. I'm sure if I said it to you in person, you would have picked up on the non-hostile intent and narrow scope of my criticism, but the internet tends to twist things in very negative ways.
Nobody can know everything, and not knowing something doesn't make someone a bad engineer. Software development in particular is more about learning than about knowing, which is why when I see someone saying that they don't need to learn something because <X>, it bothers me. In your comment, you said that you don't want to be a release engineer, but IMO that's a poor excuse since "release engineer" isn't even a role that exists at many places.
And for the record, I didn't even look at the project you linked to. So if you also thought I was criticizing it, then rest assured that I'm much too lazy for that. When I was still learning, online tutorials and resources like yours were extremely valuable to me, so I know how helpful they can be to people even if they're not "perfect" by some snobby asshole's definition...so keep at it!
maybe that'll make you a worse engineer, but engineers are not human beings.
mastery of a specific technology adds very
little value
I disagree in the strongest possible terms but it's certainly possible that we're working with different definitions of "mastery" so I'll give you mine. My version is perhaps actually closer to "competency" rather than "deep under-the-hood knowledge." I'm talking about somebody who knows how to use a screwdriver without stabbing their own eyeballs out, and when to use a screwdriver, but isn't necessarily like... inventing new types of screwdrivers or innovating in the field.Somebody with Postgres "mastery" should have a strong command of basic normalized table structures, they should know how to construct useful indexes, they should know the downsides of over-indexing, they should know how to find and optimize slow queries, and they should at least know of somewhat advanced topics such as partitioning, materialized views, foreign data wrappers, and so on. It is okay not to have a clue how to set up e.g. partitioning but you should know what it is so that you can learn about it and employ it when needed.
Somebody with "mastery" of an application framework such as Rails should be competent at basic OO design, they should be familiar with Ruby structures and idioms, they should understand what goes where in Rails' MVC paradigm, they should be able to write comprehensive and performant tests, and they should know how Rails' asset pipeline builds and emits front end code. I would not expect them to have mastery of Ruby's metaprogramming, but I would expect them to know what it is and have some idea of how Rails uses it to extend Ruby. (I realize Rails is controversial, but I'm not talking about anything Rails-specific. Feel free to substitute your language+framework of choice)
In short, the sort of working knowledge you might commonly gain with 1-3 years of real-world production experience with a given piece of the stack. Or perhaps less if you were really focused on it and just learning it full time.
Without guidance, folks with less knowledge than this tend to produce absolutely unscalable, unmaintainable spaghetti code for anything larger than a toy project. To return to the screwdriver analogy, they can kind of use a screwdriver but they're constantly stripping screwdriver heads at which point either the project is ruined or they need some help from more practiced craftsperson.
always in danger of becoming more legacy than relevant
I would certainly agree that you can go too deep to be useful as a "full-stack" developer. For example, if you really understand Postgres internals and can write your own extensions and have a bunch of commits in Postgres itself, cool, but what happens when our next client wants us to work on their MySQL-based app? And while Postgres is unlikely to die anytime soon, what happens if you've achieved that level of expertise in a tool that does fall by the wayside? I've certainly "mastered" a few tools that are distant memories.Of webapps. Will make you a better developer of webapps. It may surprise you to learn that there are more kinds of software than that. Sometimes the final product is something like a binary that runs outside the browser, and deployment is no more complex that putting an tar.gz'd executable on a website.
Supabase (https://supabase.com/pricing) has an amazing free tier for PostgreSQL which gives you up to a 500MB database.
Note: I'm not affiliated in any way with supabase.com.
I'm a little skeptical that any given PostgreSQL free tier will stick around indefinitely, after what happened with Heroku. And once you hit 500MB, you jump immediately to $25/mo, so if you're running a hobby project, your choice is either to delete data or start paying $300/year. On the other hand, I'd expect a well-optimized read-heavy SQLite app to scale to 10GB+ without breaking a sweat (speculating wildly) and costing more like $3/mo in storage.
I speak from experience here—until recently, I ran a site that would have been 10x cheaper if it had used pure SQLite instead of managed Postgres.
I'm not sure where the impression that this is some arcane art comes from. For a typical Debian system it is just an apt-get. Yes, the default config has tiny limits for work_mem. Just edit the .conf
Part of this might be that AWS or whichever other proprietary environment has captured developer knowledge at this point. Instead of sane INSTALL files, projects tell users to use a prepacked Docker image.
Fireship 3m video about Pocketbase: https://m.youtube.com/watch?v=Wqy3PBEglXQ
https://www.sqlite.org/whentouse.html
See the "website" section for some benchmark estimates.
I'd argue you should be.
I'm currently learning a low level language. It has nothing to do with my job, will not directly be a benefit to my work, and won't look good on my C.V.
It will (and has) _indirectly_ improved my work, because I have a better understanding of why certain things higher up are the way they are, which allows me to make better decisions.
You can ignore everything that's not "your area", whatever that might be, but I'm my opinion it means you'll be stuck at a certain invisible level because of it.
- Will multithreading make it break? (Not with WAL mode, but you have to set it manually, as this article suggests.)
- When using a PaaS, you need to explicitly add a volume and mount it on your server machine, which you might not think to do if you're a brand new bootcamp grad
- You can't(?) run migrations from another process (or at least, people don't seem to talk about doing this), so you need to prevent your server from writing while you run the migration. Even if I'm wrong, and I would love to be, it's frustrating that people don't talk about the completely ordinary need to run migrations on a database.
- Backups just mean copying the file somewhere, which is nice, but you might need to configure that yourself instead of just using somebody's managed Postgres backup feature
So, there is at least some complexity that comes with managing a "real" SQLite web server.
(I'm probably at least 25% wrong on some details above, but that's kind of the point, it's not always easy to figure this stuff out.)
Ok but the article you're replying in the comments to says "SQLite is all you need for nearly everything", and what the comment you're replying to is describing is, to use their very apt word choice, entirely ordinary.
So how do we square this circle of somebody being told both "SQLite is all you need for everything" and "it is not a good option for your totally ordinary use case"?
I think the answer is that you are a different person than the author of the article and you don't agree with the article's claim. Which, great, that was irskep's entire point!
I tend to agree with irskep that this SQLite architecture is really interesting, but I also don't quite get it. There seem to be a lot of missing details for totally bog standard applications, that people frustratingly seem to just not even mention - like the migrations thing, or how to do zero downtime deploys in general, or a few other things - as if those concepts are irrelevant or uncommon. But they aren't, they're important and typical things to think about.
The way I do it is to check the DB version `PRAGMA user_version;` on application start and run migrations if required, before the app starts taking connections. Yes, this means my app will be unavailable for a few seconds when upgrading. If you want a zero downtime solution then you will have to do it in a different way.
How do you back it up, replicate it, handle two different processes/containers/servers wanting to access the same data.
Using a PAAS solution for a database, you get all that functionality.
If you are trying to replicate or make available to a bunch of machines or similar - it's likely the wrong thing (although there are tools to do this, I've never used them). If you are just trying to back it up there is a pretty simple back up command.
For most of the things I touch, having a single-point-of-failure data store is typically not acceptable for production environments, except perhaps caching.
- how do you back up a JPG? You copy it, cause it's a file
- how do you replicate it? You copy it, cause it's a file. Unless you're talking about fancy DB replication, in which case well, that's not really a thing we do with files much. You'll have to do more research. but that's cause you're trying to do non-file things to a file.
- how do you handle two different processes accessing a JPG? They both open and read the JPG. Same for SQLite. For other concerns, you're trying to do things that don't map well to files, so once again more research needed.
https://www.unixsheikh.com/articles/sqlite-the-only-database...
Yes, technically a SQLite database is just a file on disk. And for the most part, you can treat it like other files on disk. Except for when you can’t. The GP’s questions are valid
- Install and configure Caddy to terminate the SSL. Caddy is great, but still stuff to think about and 20 lines of config to figure out.
- Configure systemd to run Caddy and my Go server. Not rocket science, but required me figuring out systemd for the first time and the appropriate 25-line config file for each server.
- Scripts to upgrade Caddy when a new version comes out (it wasn't in the apt repos when I did this).
- Ansible setup scripts to clone the repo, create users and groups for Caddy and my Go server, copy config files, add cron jobs for backups (150 lines of Ansible YAML).
It looks like you don't need most of this, or get it without additional configuration with Fly.io and Render.
It's kind of like the difference between Dropbox and "you can already build such a system yourself quite trivially by getting an FTP account, mounting it locally with curlftpfs, and then using SVN or CVS on the mounted filesystem. From Windows or Mac, this FTP account could be accessed through built-in software." (https://news.ycombinator.com/item?id=28153080)
It'd be nice if those things were easier, but the SQLite part of it cannot be any simpler or easier than it already is.
Backing up SQLite can be done with a 2 line shell script
ssh vm 'sqlite3 my_database.db ".backup my_database.db.bak"'
scp vm:my_database.db.bak .You can run small project on $5 VPS with Postgres just fine tho...
Managed services in cloud are usually massively overpriced
I have thought about jacking inotify into sqlite_busy_handler() to get the exclusive writers doing better than random waits, but this full api isn't exposed to PHP (where I need it), and doing it at C would suggest alternate approaches, maybe even with xargs at the shell.
Oracle has a DBWR process that manages itself, but a write-heavy app on sqlite must explicitly declare one, and there are many further traps down this path that will trip the unwary.
The sqlite commit is a specific pattern, it is either write or close/write.
Using inotify events can see these faster than random waits.
Would you be able to comment on if Neon is a good fit for having one postgres database per user and how well that would scale? E.g. what if millions of users?
Also with the managed service, is there help with applying migrations or helping manage migrations for such a multitude of postgres databases?
Lastly, is there any way of querying across neon databases, e.g. aggregating data from many user's databases? I saw you partnered with Hasura a bit. Hasura would be a great fit here for aggregating and "federating" each user's postgres database, but with their most recent price change I'm scared of integrating too much with them. Wondering how you think one can best query e.g. a million user's individual postgres databases?
Thank you and feel free to ignore I know it's kinda asking alot
SSHing into a new linux VPS can be intimidating but there a few good digital ocean tutorials that'll take you from new VPS to dokku and once you're there it's very much a PaaS
[0] - It’s for a small business not in the US with no serious requirements on availability etc so free, or close to it, seems achievable.
Where did 99% of the time come from ?
I can only remember 3 companies out of all 40+ I consulted and contracted for that actually needed a separate machine for app and a DB, let alone such that actually truly need several of each.
It's a very non-romantic truth but most projects out there can easily fit in a VPS with 4 CPU cores, 8GB ram and 100-200GB SSD space. App + DB + self-hosted telemetry included.
Specifically: setting up database replication sucks, setting up failover sucks, setting up backups sucks (even with a PaaS you need to do this, but you can use it as your first layer), migrating database clusters to new versions or hosts sucks, keeping your host's software up to date sucks, and setting up alerting and monitoring sucks.
It's easy to get an open port to connect to, but hard to keep it there for five years.
I still self-host databases for personal projects, but I can see why so many people don't want to bother. When I've done it professionally, I was basically working as a sysadmin and managing stuff like database servers was a core job function, not something tacked on to a development job.
For fun, sure, dink around and learn as long as there are no customers to affect.
Not a huge sacrifice. Though I do get the argument of "I want to pay $5 and it to just work" and I've done so part of the times. Just pointing out that the upfront investment in doing it on your own is not so big.
This caveat covers "most cases". If there's only a single machine, then any data stored is not durable.
Additionally, to my knowledge SQLite doesn't have a solution for durability other than asynchronous replication. Arguably, most applications can tolerate this, but I'd rather just use MySQL with semi-sync replication, and not have to think through all of the edge cases about data loss.
do you think ims/db ran on a cluster
the d in acid stands for durability
you're talking about pitr, which is what mysql semi-sync provides (and afaik you are correct that sqlite doesn't offer pitr)
Just having sync replication is enough, doesn't have to be fancy like semi-sync.
i'm correcting their terminology
'durability' already has a well-established, rigorously-defined meaning in this context, which is confusingly similar to pitr but definitely not the same thing
the downside of sync replication, as i understand it, is that although your data will survive any one of your machines being instantly nuked from orbit, your entire service will go down; semi-sync avoids this problem
Other databases have a commit level that makes sure logs have been shipped.
For me this is an edge case in just about everything except financial transactions (the performance penalty of distributed transactions is pretty high!) but it is correct to note it.
We economize by using racks of cheap, flaky commodity hardware, but we have to be ready for one computer to die by failing each application over to another.
even in the 01990s i don't think ibm had such an offering, though tandem did (but it couldn't run ims)
Regression via a code or dependency update? Full disk? DNS is down? Too much load? All of these would bring down both servers in quick succession.
I guess something like a "once every 2 days" race condition could buy you some time if you had a 2nd server. But that's not a common error
I like your statelessness point. I suppose in your view it’s better to have the concentrated stateful core with stateless servers as opposed to just one stateful instance. Two instances mean you can’t easily store foo in memory and hope the server doesn’t die until it’s not needed there anymore. Counterpoint is that the extra layer of indirection is 10x slower and horizontal scaling won’t be needed as much if you don’t pay that price in the first place, but you are right, the temptation to store foo in memory would still be in its prime. The thing is, if one machine can scale, putting foo in memory isn’t actually bad. It’s only when things don’t scale that it’s bad.
It could be bad hardware, it could be bad query (left dangling/canceled on old instance), could be bad statistics and unlocks disk fragmentation etc etc.
Instance can be process, container or machine.
Spinning disks and all, I was terrified to reboot them and have the boot disk fail (which was not on RAID).
The main disks failed once or twice which slowed the servers down considerably until rebuild of the raid finished. Very nervous time.
- Store the date as a huge, wasteful string in ISO8601 format
- Store it as Unix epoch seconds
- Store it as a fractional Julian day
Besides the first one, you have to remember how the date is stored and ensure all client libraries handle the conversion. If you want to view or manipulate the latter 2 formats in SQL, you need to chain a bunch of conversion functions.
This is what we do. Have been storing 100% of our timestamps this way in SQLite for ~8 years now. Using .NET to handle the actual conversion to/from long.
var myTimeUtc = DateTime.UtcNow;
var myTimeUnix = new DateTimeOffset(myTimeUtc).ToUnixTimeSeconds();
var myTimeUtc2 = DateTimeOffset.FromUnixTimeSeconds(myTimeUnix).UtcDateTime;
No drama at all. No weird libraries or utility methods. It's all simple built-ins these days.I am doing something very similar with .NET stack (single file deployments), SQLite and offline scenario...
Please contact me on my username's email (gmail).
Any code (usually one codebase) which looks at dates in a SQLite DB can already easily do these conversions, even at the application-level.
As others have mentioned, SQLite has fairly comprehensive support for JSON. Arrays can be represented as JSON arrays.
Some geometry features are supported through the R*Tree module: https://www.sqlite.org/rtree.html
And I'm not sure what sort of support you'd expect for a UUID type. Depending on how you represent the UUID, it's either a string or a blob -- I can't think of any meaningful operations to perform on a UUID which go beyond basic comparisons.
That's not fair on many levels and reflects harsher on the casual readership hereabouts than yourself.
Your comment is probably rated stellar by the time I hit enter ...
The limited datatypes are so silly, and the limits seem pretty pointless. There's all kinds of weird datatypes, but no unsigned integers?
Of course, SQLite types are a special level of hell, where everything is stringly typed.
Whether the tool fits with your architecture and specific use case is much more important than whether it does the job. You can make most tools work for most use cases, but it might not be a natural fit.
For example, an in-memory database is probably not conducive with a serverless environment and you would prefer to either host your own DB server or use a serverless DB.
Or perhaps there are specific Postgres plugins that enable your use case, or a specific Postgres feature like n-gram search (I don't know if SQLite supports that), etc.
Technical maximalism ("it does all the things!") is great for marketing, but a poor way to choose the appropriate technology for your application.
That's the adoption problem. It has a hard ceiling. Besides embedded, most engineers use a different DB professionally - dare I say pg or mysql? And because of that they'll reach for the tool they already know.
Sure, I get the argument that SQLLite fits "more exactly" for many many applications because the sheer number that never move off of one machine, but it has this hard ceiling of "what if I need more" and well "I could just use this other tool that goes the full distance, in case one day... oh and I already use it at work."
That's why SQLLite excels for embedded applications. It fits perfectly. There is no "what if" and the performance is astounding esp in low power.
The only "more" which pushes a move to client-server DB is a CPU- or memory-limited, not easily sharded, persistent service. But to have such a problem is highly uncommon. Usually optimization will solve such problems easily. I've seen Python services which use 1GB RAM/process, each process can handle 1 concurrent request, and there's only 20GB of RAM per instance. The solution there is to use one of many sane async frameworks to handle more than 1 concurrent request/process. Some problems, such as latency of DB queries/N+1s and the query complexity which ensues, will not even arise in the first place if you were using SQLite.
Am I misunderstanding this or is this not the vast, vast majority of all cases?
edit: This is done not because of any considered technical reasons, but because that's how one learned to deploy apps.
And it costs almost nothing to write it that way from the start, but it's a pain to separate them out later.
I'd call that a considered technical reason.
edit: I feel like I should be more specific here. I would only call it a considered technical reason if it was actually considered. The fact that it is possible to come up with good reasons is not relevant if no thought went into it at the time of design/development.
For blast radius, you have things like patching (you don't want to botch the database accidentally updating a shared dependency with your app), resource management (don't want your DB to eat all the RAM or I/O and kill your app), if someone botches maintenance there's less to break at once
You don't have to use VMs but they're one way to do it. Container orchestrators achieve many of the same goals and automatically restart workloads if a physical machine fails, too
Is there a "using SQLite for a multi-threaded webapp for dummies" package that does all the config I need so I can just drop it in and go and not tune anything?
Paging fly.io founders etc! If I have a persistent volume can my fly.io apps use SQLite? What are other good micro-hosting options?
1) When you open the database:
pragma journal_mode = wal;
pragma synchronous = normal;
2) When you want to do a transaction that does writes, use `BEGIN IMMEDIATE`, not `BEGIN`.3) Don't have long-running transactions.
4) Have some process to do backups.
(3) might be a big ask for some systems. Long-running transactions should be avoided even in systems like Postgres, but on a SQLite system with writers, they're the difference between an amazing experience and a garbage one.
I'm hopeful that Fly can eventually make (4) painless by having super-easy out-of-the-box litestream and S3 backups. Until then, roll your own cron scripts or what-have-you.
I was looking at https://github.com/irskep/cheapo_website from commenter irskep above, and they make a nice point that render.com has automatic daily backups, solving 4)
However, in another comment they mention "You can't(?) run migrations from another process" and that "people don't talk about the completely ordinary need to run migrations on a database".
I guess this is also the piece that I'm missing. How do I run migrations? Do I deploy a new version with the migration and temporarily take down the server? I'm glad to do that.
I guess I'm also walking through this because---as I said---I'd love just to switch to SQLite but I'm still not sure how many simple non-esoteric gotchas will pop up.
A single transaction that does a very large write will likely impact the reader -- the reader will be blocked while the write finishes.
I use SQLite in a web scraper on my laptop. The scraper runs as 16 processes hammering the database, doing about 5,000 write transactions/sec. Occasionally, simple SELECT queries experience high latency (what would be a 1ms query takes 100-200ms), because they're blocked while the WAL gets checkpointed into the main database.
If you have much lower volume of writes, the checkpoint is smaller and so completes much faster, and so the worst case latency is much better. Since crawling is a non-interactive process, I don't care about the worst case latency. If I was writing a website, I'd feel differently--but most websites won't do 5,000 writes/second.
Thank you for explicitly saying this! I'll see if I can update my repo to allow for online migrations. I was trying to be as conservative as possible based on what I knew at the time. It'll be nice to update the migration steps to be simpler.
It is possible to change the schema of a SQLite db as "just a write"?
pragma journal_mode = wal;
pragma synchronous = normal;
I think it's worth noting that this combination loses committed transactions on kernel crash / power failure / etc. That is, your app replied "OK" to the client, but the data still gets lost.> A transaction committed in WAL mode with synchronous=NORMAL might roll back following a power loss or system crash.
> By default, the configuration uses SQLite.
--- 0: https://docs.djangoproject.com/en/4.1/intro/tutorial02/
If you dare combine (properly-configured) SQLite with a local NVMe disk, you will find yourself well beyond what many hosted solutions can provide (clustered or otherwise). To be clear - the biggest reason for this is the incredible latency reduction, not raw IO bandwidth or disk IOPS (although this helps massively too).
Millions of transactions per second doesn't mean much when there exist no dependencies between them. The figure I am more concerned with is serial transactions per second. 1 logical thread blocking on every command.
SQLite is the only database engine I have ever used that can reliably satisfy queries in timeframes that are more conveniently measured in microseconds rather than milliseconds.
There could not be any features of any other database that SQLite might not have, or that an application might need, or want.
We definitely should not, like, read a book on databases, or read the manual of another database, or something else crazy like that. There's no reason we might learn about other databases. They are just "shiny stuff", meaning, there's something going on with them that I can't see, because of all the glare.
Honestly, the existence of all those other databases, and database models, and the billions of dollars spent on them, is a fluke, probably. It's unlikely you will ever in your life see or work on an application that needs a database other than SQLite. Because the only applications you will ever work on won't ever run on more than one virtual host, or be used by more than one application. And definitely you will probably not need a feature that isn't in SQLite.
SQLite is very fast, it is very simple, it is very well written, and it has a lot of tests. Therefore we can conclude that you should never look at or learn about another database, because considering the previously states information, we know that no other database could possibly be desired or needed.
I don't know a lot about databases. And, granted, I only just found out about SQLite. But I am quite sure I am correct that SQLite is the only one you'll ever need in most cases.
Looking back on my long career, I can say that literally every application I've ever worked on-- with one exception-- could have been run on a single modern machine, probably with SQLite as a backing store. I'm not sure, but there's a pretty good case that such a design would have improved things a fair bit.
The network was never our problem. The hardware turned out to not be the cause of any of our outages. Outages were always caused by a software bug or a misconfiguration of our complex applications. These misconfigurations were in large part caused by avoidable complexity. The complexity was often due to us trying to solve for scale and reliability problems that we didn't really have.
You can easily host multiple customers or organizations in any database. That's not something that SQLite or SQL Server or Postgres helps or hinders.
Very few of us ever work on a Google or Facebook scale-- even though we think we do.
Anyway, in my career, the one exception I can think of was a big-data application that really did need many servers just to store the actual petabytes of data. Even there, though, we could probably have partitioned the data across SQLite instead of Elastic and probably done alright.
All of that said, I run Postgres in production like a sane person, because I don't want to have to deal with managing a database. Render manages it for me so that I can focus on reading Hacker News. So, this entire rant is moot.
I agree with you that it is important to realize where a client-server DB may be needed, but the it really almost never is.
"A bicycle is all the transportation you will ever need in most cases.
I bet most non-bicycle drivers have indeed never been anywhere which couldn't have been gotten to by bike. And the bike is in fact more fully featured in some ways than some cars (when will cars shrink down? not require fuel? not pollute? cause less harm?)
I agree it's important to realize when a car may be needed, but in reality it almost never is."
Even if all of that is true... I still want to use a car, for good reasons. I may not need it, and it's more complex to use and maintain. But I get enough value from it that it's worth it.
But seriously, those points exactly.
Have any applications you’ve build needed both of these features?
We're making use of this ourselves in https://blazordiffusion.com which runs entirely on SQLite, using Litestream to replicate it to Cloudflare's R2 object storage which is running on a single Hetzner US Cloud VM at €13 /mo.
As we believe SQLite + Litestream is a very cost effective solution that can support a large number of App's data requirements we've added first-class support to add SQLite + Litestream support in our .NET Project templates [3] which uses GitHub Actions to run Docker compose App deployments along with setting up Litestream replication to AWS S3, Azure Blob Storage and SFTP in a sidecar container that also includes support running DB Migrations on Server with Rollback on failure. If anyone's looking to do something similar, the GitHub Actions + Docker compose configuration that enable this are being maintained at https://github.com/ServiceStack/mix/tree/master/actions
I can also confidently say: a static HTML landing page is the only website you will ever need in most cases. I suffer every time I see a one-page site, hardly ever updated, built with Wordpress. Why hire an 18-wheeler to deliver a pizza?
Is this with nodejs or something single threaded as the webserver?
I would kind of assume you'd run into issues with something like PHP.
It does work better when all of your writes are on a single thread, though. I used Node’s IPC to accomplish this, and was able to get it up to 10k or so writes per second while still having it do tens of thousands of simple reads per second.
For example today I was writing tests for a python application I'm working on that uses SQLite. SQLite is the only option I have for this program as I have literally no way to setup a client server db in it's environment.
I had the tests all configured to write to the same test db file. The program I wrote the tests for had no issues with locking because it doesn't run anything concurrently.
However, when I did the same in my test suite, they ran in parallel, which caused my tests to hang. Of course, I figured this out quickly and made the tests write to their own individual DB. This is a really small program, so cleaning up afterwards is no big deal. (I could also just use an in memory db instance instead of even dealing with files). But, for a program with a LOT of tests, I could see this being an issue.
Last I remember, PHP still runs all inside of a single process, so it still all share the same memory space, and it no longer has the overhead of starting a new process with every request.
The piece of engineering that made node.js so fast back in the day was Libuv, which allowed for non-blocking IO, greatly reducing the number of system calls/context switches.
But I am also going to guess that PHP developers have since caught on to the performance optimizations of non-blocking IO, and integrated the improvements into the runtime. Doing a quick search for "php vs nodejs benchmark" on Google [1], it seems like the performance of Nodejs is comparable to that of HHVM.
So as usual, use the right tool for the job. This is more of a problem of using your tooling correctly, more than choosing the correct language.
[1] https://yuiltripathee.medium.com/node-js-vs-php-comparison-g...
Node.js uses non-blocking IO to achieve massive amounts of concurrency on a single thread, while one-thread-per-request models such as PHP rely on OS threading to do the exact same thing.
If anything, the node.js model is often capable of more concurrency because of the inefficiencies involved in OS threading and context switching.
EDIT: of course, event driven PHP exist. I don't know much about the current state of it.
if SQLite allowed for custom commands, at least there could be ALTER commands that run this process behind the scenes, which the SQLite developers wouldn't have to maintain.
Also, MySQL can't run a migration on a FK-constrant table without downtime. To do this you need an online schema migration tool which generally requires the absence of foreign keys.
adding or dropping any constraints, including nullability, foreign keys, check constraints, changing the structure of the primary key, etc. changing a type also, even given SQLite's squishy typing model.
pretty much anything is not allowed except adding and renaming columns.
> Also, MySQL can't run a migration on a FK-constrant table without downtime.
That's not an issue for the overwhelming vast majority of MySQL databases in production, which are to be clear not running or aspiring to run at Facebook / OLTP-level scales. A table with a few million rows can be migrated in seconds, the table gets locked for a few seconds, everything keeps running after a brief pause. This is not a problem for the "most cases" use case the article refers towards.
> To do this you need an online schema migration tool which generally requires the absence of foreign keys.
if you are running at Facebook / OLTP scales or aspiring to be, then yes. Otherwise, not usually. The article here is referring to SQLite being used for "most cases", not just "running at Facebook / OLTP -level scales". Running at Facebook / OLTP-level scales is still one of the places where you most certainly would *not* be using SQLite for your primary database.
Faire used ghost for MySQL migrations in 2018. We’d run some migrations in a blocking fashion if they were on a smaller table, but the smaller ones still took a few seconds. I suspect the delay would be very noticeable (15s+) for data sizes far less than at FB.
Sure it’s all insane overkill - but I use Linux for the same reasons - I want one API that I can use everywhere, from my toaster to my spaceship, from hobby to enterprise.
The simplicity is not the API. The simplicity is having one API.
Anyways, the question isn't whether client-server databases scale, it's whether SQLite doesn't scale. What were the database size, resource constraints, architecture for the multi-billion $ company? How did this scale over tiem? Do you have any reason to believe that a SQLite architecture couldn't scale to support the service offerings you saw?
But in general I compare the intended scale to successful startups I’ve been at - where several TB of data are being read by hundreds of thousands of users per second. Typically this requires a fleet of the largest instance types most cloud providers offer.
Anyways - you’re not wrong and neither is the author - but if I’m going to choose one tool - I’d rather it work for all intentions and be a bit more complex than the other way around.
That would make sense if you assume that Synapse is only going to be used in instances with hundreds/thousands of users, but plenty of people host their own instances for themselves only. Surely SQLite would be plenty for single-user instances, or family instances?
Can you share any extra info about the table layouts or queries which are slower in SQLite vs Postgres? In particular which postgres-specific optimizations have been made?
It’s not about logic or practicality. It’s that they feel some kind of mental pain using Postgres as it is too “bloated”.
If you have no experience with databases and are just starting out programming, PG is going to steepen the learning curve substantially.
The difference between installation for sqlite and postgres is the difference between typing `apt install sqlite3` and `apt install postgres`.
The actual learning curve for sqlite is more complex because picking random sql queries on the internet will end in misunderstanding in underlying types in sqlite and broken data. Postgres tools ecosystem is much more expressive, because more people think about Postgres as about "real" database.
Since the advent of containers for running software, deploying any DB software is largely as easy as any other with a one line run statement, and if container image is cached you are getting a new DB in seconds.
If just starting out, running PG from a container is exactly how I would tell someone to learn today, and the process is largely identical whether they learn on windows, linux or a mac.
I haven't natively installed a DB since probably 2015 for any kind of local development use, its just a waste of your time more often than not now.
- No N+1 query problems
- Far lower latency
- Easier to install, configure (you can issue a couple of special SQL statements, that's it), and maintain (no db service to monitor!)
- Better security (smaller attack surface with no service and no network access)
Personally I use both PostgreSQL and SQLite. I like SQLite because it's a lot nicer to work with, it's easier to quickly develop for and the ease of deployment and operations can't be beat. I use PostgreSQL when working with other people mostly because it's what everyone else knows and it's great for large systems.
I have a cluster of 20 Windows 7,8,10,11 machines spread across multiple sites that I wanted to run some log analytics. This was for some custom software running on all 20 machines which had a SQlite API.
So I did the simplest and cheapest thing that would work.
I setup sync.com folder on all machines (think dropbox/ onedrive) to write logs to the same log.db file across all machines.
This worked great at first (I could analyze the db remotely).
However you see the potential problem here...
Logging was maybe a few writes an hour PER MACHINE but inevitably you start getting conflicts (since sync takes a 5-30 seconds to actually sync).
Now I am faced with merging 20 conflicting log.db files.
In theory I should have used a server based SQL database. Or perhaps I should have just lived with 20 different log.db files.
In my defense there was only SQlite API so I would have had to write some middleware to transfer to another DB.
Setting up cron and rsync on different Windows machines (WSL, cygwin?) was not something I would have looked forward to.
SQLite the only database you will ever need in most cases - https://news.ycombinator.com/item?id=26816954 - April 2021 (370 comments)
Modern computers are fast enough that in many cases “the only database you will ever need” can be files on the filesystem. For example “1 row = 1 file”.
It brings additional benefits as well: for low-write applications you can use git to get a history (+transactions if you store them in the log), backups are super easy, replication is trivial. For higher-write applications it gets more complex but you can still plan and implement most of the traditional DB scaling techniques (and even implement them one at a time as you go grow).
Computers are “stupid fast” now that we’ve gotten off platters.
> Name: Edward I'm using grep and find and the Unix file system
> Name: Toni Why do you need a database? I'm using CSV files!
> Name: Carlos I came after a long journey to your website to seek enlightenment : am I fucked? And it didn't answer that question clearly. Let me re-phrase it: I first search an LDAP directory, then I remotely execute a quota status, after that I query a PostgreSQL database, and then I generate a .txt file with the timestamp as its name or a .csv file with an hash as its name, and then I look up the files from a web page, load it all to a multi-dimensional array, and generate a nice report, re-loading the entire file every time the user wants to, say, sort by another field. Something this complex can't ever possibly fuck up, can it?
Honestly splitting in to nested subfolders is not big deal anymore, it’s a single function you can write even if you’re having a “.10X” day.
But I think I'm curious either way: how do you index a column?
Not satire, though a bit sensationalistic to argue it’s a solid solution that’s usually overlooked because it’s “too slow”. I’m just pointing out it’s not actually slow any more.
Back in the days of scaled applications running on MySQL, DDR2 was 3200MB/s and people were so happy when their DB was small enough they could fit it in RAM.
I think the problem is that all sorts of utilities and commands break when dealing with hundreds of thousands of files in a single directory.
Also the block size means you'll waste an incredible amount of disk space.
Though, if you are doing a lot of joins your application is probably a better fit for a graph database instead of a “relational” one anyway
You may also need the SQLite command-line tools from the SQLite website; https://www.sqlite.org/download.html
Are you coming from using SQL Server Management Studio on a PC?
If yes, you may be better off using something like PostgreSQL on Mac OS X, and a PostgreSQL GUI browser tool, because it may be harder to use SQLite in this situation as you don't have the benefit of using a computer language to make up for the datatypes that SQLite does not have.
For example SQLite does not have a DateTime datatype. If you need to do a lot of date/time field manipulation in SQLite, this can be made easier when using a programming language like C#, because you can use a SQLite integer datatype to store the DateTime data, and then convert the integer to a C# DateTime datatype and do the DateTime manipulation in C#. But if you aren't a coder, then this option isn't available to you, and you will have to have other ways to manipulate DateTime data in SQLite like using the string datatype to store the date time values and then use corresponding SQL queries to handle the "DateTime stored as a string" situation.
(Maybe I've over-explained here. Sorry)
Here a PostgreSQL Mac OS X GUI browser download page: https://www.pgadmin.org/download/pgadmin-4-macos/
I wish SQLite had GIS support built in.
People don't realize just how big 1MB is if it's not multimedia. If you had 100,000 users consistently writing 1kb to your DB every single day, it would still take a 10,000 days or over 27 YEARS to fill a 1TB harddrive.
Most companies have 1,000 users per day updating mostly existing data or adding small snippets here and there. That 1TB drive would die of old age long before your average company could come close to filling even 10% of the capacity.
Is it possible to offer SQLite as a managed / serverless offering?
A light weight and cheap relational data store that we just consumer using an API
Here's your easy cheap and lightweight relational datastore API:
import sqlite3
conn = sqlite3.connect("db.sqlite3")
cur = conn.cursor()
cur.execute("SELECT * FROM products LIMIT 25")
print(cur.fetchall())We have a bunch of serverless functions that are generating records that fit a relational data structure. (AWS Lambda)
We would like to write these records to a persistent relational data store so another downstream process can read it.
Amazon RDS seems like an overkill.
Amazon DynamoDB (NoSQL) seems like a misfit because we want to execute relational queries and some joins against this data.
We could write to CSV on S3 and query using Athena/Presto. Seems clunky and slow.
Am I missing any obvious solution here OR there is a space here for a service that offers a lightweight relational datastore that multiple loosely coupled readers and writers can use.
The problem with AWS lambda and SQLite are 1) network file systems often don’t support the APIs that SQLite needs to implement concurrent access; SQLite is not a database server 2) local storage for AWS lambda is ephemeral, your DB will be deleted, and if there’s two lambdas, they won’t be using the same DB.
Just use one EC2 instance and SQLite, or lambda with RDS.
I am not familiar with cloudlflare and if it has a free tier like AWS
will explore
In that sense, I have the impression SQLite is different from other database software. You can usually get by with Postgres or MySQL (after they are set up) without looking at their docs.
I spent several hours reading the SQLite docs. It wasn't wasted time: actually learning SQLite made me a better professional. But for those thinking of using it on their side (or main) project, definitely understand how it works and what are the trade-offs involved.
Now, to answer your question specifically: it depends on a few factors. You can have concurrent readers (wal). With newer developments (wal2 + begin concurrent), you can also have concurrent writes as long as they happen at different pages.
If you are blindly doing multi-threaded connections without understanding the implications, you also risk corrupting the database entirely.
If you are blindly doing multi-threaded connections without understanding the implications, you also risk corrupting the database entirely.
This is false? SQLite has always been able to handle multiple processes and threads, reading and writing to the same database?This is true for the vast majority of cases, although there's at least one documented scenario where using modern SQLite coupled with an old threading implementation (the one that predates NPTL in Linux) may lead to database corruption.
I guess that's why I said "blindly": as a way to incentivize OP to look this up. It's not that the SQLite database is fragile, but rather that it expects to be used in a certain way, and if you don't, you risk corrupting it.
https://www.postgresql.org/docs/15/mvcc-serialization-failur...
For data science purposes, it seems to be quite interesting.
- A type for Instants / time handling
- being strict with types. No inserts of ints into a string column
I’m curious why ‘123’ in an INT column is so bad? I suspect the conversion rules are in place because they shouldnt ever cause logical errors.
I personally appreciate using created_at < ‘2021-05-23’ in Postgres queries. The query would only be more verbose if I had to explicitly construct a date object for it.
If I'm passing a string to an int column, there is most likely an issue in my application code. If there currently is none, there might will be.
For example, I might have forgotten to parse the string properly in my application code. If I'm doing '10' * 2 in JS, it returns 20. If I later change it to '10' + 10, it will be '1010'. Say I then save the result of that compilation in the DB. Raising on '1010' would have prevented me from persisting the error and gave me an opportunity to investigate the situation. Without an error, there will be a much harder debugging session.
These coercions were popular back in the 90s/00s, which is why I think most DBMs have them. At least that's why JS has it.
Disagree.
If you think about it from an attack surface perspective, there are numerous advantages to isolating the database. There are performance, availability, sharding, and columnar options out there also that may better meet the use-case (just to name a few). I have ran Postgres on endpoints when developing with performance akin to SQLite. Further, there are numerous ways in which to increase performance, availability, or to pursue some of the more customized versions of Postgres depending on use-case. One of the times I used Postgres was with Oracle DBAs, and they found the transition pretty simple.
Various customizations / extensions / versions of PG
There are security versions e.g. https://www.crunchydata.com/products/hardened-postgres
Columnar / high performance Parallelized extensions e.g. https://www.citusdata.com/product
General Purpose / Oracle transitions e.g. https://www.citusdata.com/product
Yandex even has an embedded Postgres https://github.com/yandex-qatools/postgresql-embedded
If you'd like to see a full list of features see https://www.postgresql.org/about/featurematrix/
More than this though, PG has a really excellent community with a large amount of talented folks, available both individually and through OSS oriented companies https://www.postgresql.org/support/professional_support/ and willing to help out on Libera https://www.postgresql.org/about/news/migration-of-postgresq...
The attack surface on PG or MySQL is a lot larger and there are a lot more moving parts than SQLite (which is just a file). Notably, there is no service exposed to the network that someone can attack, which is a huge attack vector with lots of different types of vulnerabilities that don't exist in SQLite.
It was neat and quick and easy to get running right on your regular windows desktop.
By the criteria of this article, that was apparently the only database I ever needed. It could handle the multiple reads and occasional write of a small scale website. Backing it up consisted of copying the file. It was accessed through a simple standard library (ODBC) and supported SQL.
Since that was the only database we needed, what does SQLite bring to the table?
The cache is implementation defined.
The locking is implementation defined.
The index does not work on foreign keys.
The planner isn't thread safe.
The only reasons for 90% of use cases to use sqlite are that it can be embedded and it has an sql parser.
In a major application written by a company with an actual software engineering department, you would use Postgres or MySQL.
.schema tablenameSample data:
CREATE TABLE users (user_id serial, name text);
CREATE TABLE comments (comment_id serial, user_id int, comment text unique);
CREATE VIEW user_comment_view as select u.user_id, u.name, c.comment from users u, comments c where u.user_id = c.user_id;
INSERT INTO users VALUES (1, 'Bob');
INSERT INTO users VALUES (2, 'Sally');
SQLITE3 OUTPUT sqlite> .schema
CREATE TABLE users (user_id serial, name text);
CREATE TABLE comments (comment_id serial, user_id int, comment text unique);
CREATE VIEW user_comment_view as select u.user_id, u.name, c.comment from users u, comments c where u.user_id = c.user_id
/* user_comment_view(user_id,comment) */;
sqlite> .schema users
CREATE TABLE users (user_id serial, name text);
sqlite> select * from users;
1|Bob
2|Sally
POSTGRESQL OUTPUT test=# \d
List of relations
Schema | Name | Type | Owner
--------+-------------------------+----------+----------
public | comments | table | postgres
public | comments_comment_id_seq | sequence | postgres
public | user_comment_view | view | postgres
public | users | table | postgres
public | users_user_id_seq | sequence | postgres
(5 rows)
test=# \d users
Table "public.users"
Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+----------------------------------------
user_id | integer | | not null | nextval('users_user_id_seq'::regclass)
name | text | | |
test=# select * from users;
user_id | name
---------+-------
1 | Bob
2 | Sally
(2 rows)
Postgres also supports adding + to commands to get additional extended information, eg, \d+. You can also filter by tables (\dt), filter by views (\dv), filter by functions (\df), etc. It's allows much more natural enumeration of the DB which I wish sqlite had as well.Most white collar jobs these days involve software, so most white collar jobs probably would benefit from being able to script.
Not to mention that to really benefit from scripting you need programs that you can actually execute. As far as I know Windows (which most people use) is not very friendly in that regard. Powershell improves things a bit, but I'm pretty sure you can't just manipulate .xlsx files with a simple shell script on Windows, and this is one of the lowest hanging fruits I could imagine for white collar workers.
Windows is a lot more friendly than you realize. Probably powershell is a lot more powerful than you realize.
I'll also make the point that people who are doing OK in excel probably can model things in their head well enough to get into scripting. Powershell also has a one-liner "export to *.csv" cmdlet, which is pretty amazingly handy.