I run multiple $10K MRR companies on a $20/month tech stack
stevehanov.ca700 points by tradertef 14 hours ago
700 points by tradertef 14 hours ago
> The enterprise mindset dictates that you need an out-of-process database server. But the truth is, a local SQLite file communicating over the C-interface or memory is orders of magnitude faster than making a TCP network hop to a remote Postgres server.
I don't want to diss SQLite because it is awesome and more than adequate for many/most web apps but you can connect to Postgres (or any DB really) on localhost over a Unix domain socket and avoid nearly all of the overhead.
It's not much harder to use than SQLite, you get all of the Postgres features, it's easier to run reports or whatever on the live db from a different box, and much easier if it comes time to setup a read replica, HA, or run the DB on a different box from the app.
I don't think running Postgres on the same box as your app is the same class of optimistic over provisioning as setting up a kubernetes cluster.
Sqlite smokes postgres on the same machine even with domain sockets [1]. This is before you get into using multiple sqlite database.
What features postgres offers over sqlite in the context of running on a single machine with a monolithic app? Application functions [2] means you can extend it however you need with the same language you use to build your application. It also has a much better backup and replication story thanks to litestream [3].
- [1] https://andersmurphy.com/2025/12/02/100000-tps-over-a-billio...
- [2] https://sqlite.org/appfunc.html
- [3] https://litestream.io/
The main problem with sqlite is the defaults are not great and you should really use it with separate read and write connections where the application manages the write queue rather than letting sqlite handle it.
> Sqlite smokes postgres on the same machine even with domain sockets [1]
for inserts only into singe table with no indexes.
Also, I didn't get why sqlite was allowed to do batching and pgsql was not.
> for inserts only into singe table with
Actually, there are no inserts in this example each transaction in 2 updates with a logical transaction that can be rolled back (savepoint). So in raw terms you are talking 200k updates per second and 600k reads per second (as there's a 75%/25% read/write mix in that example). Also worth keeping in mind updates are slower than inserts.
> no indexes.
The tables have an index on the primary key with a billion rows. More indexes would add write amplification which would affect both databases negatively (likely PG more).
> Also, I didn't get why sqlite was allowed to do batching and pgsql was not.
Interactive transactions [1] are very hard to batch over a network. To get the same effect you'd have to limit PG to a single connection (deafeating the point of MVCC).
- [1] An interactive transaction is a transaction where you intermingle database queries and application logic (running on the application).
Thank you for clarification, I was wrong in my prev comment.
> - [1] An interactive transaction is a transaction where you intermingle database queries and application logic (running on the application).
could you give specific example why do you think SQlite can do batching and PG not?
Thing is though - either of those options is still multiple orders of magnitude faster than running on a remote host. Either will work, either will scale way farther than you reasonably expect it to.
> What features postgres offers over sqlite in the context of running on a single machine with a monolithic app
The same thing SQL itself buys you: flexibility for unforeseen use cases and growth.
Your SQLite benchmark is based in having just one write connection for SQLite but all eight writable connections for Postgres. Even in the context of a single app, not everyone wants to be tied down that way, particularly when thinking how it might evolve.
If we know our app would not need to evolve we could really maximize performance and use a bespoke database instead of an rdbms.
It seems a little aggressive for you to jump on a comment about how it’s reasonable to run Postgres sometimes with “SQLite smokes it in performance.” That’s true, when you can accept its serious constraints.
As a wise man once said, “Postgres is great and there's nothing wrong with using it!”
> Sqlite smokes postgres on the same machine even with domain sockets [1].
SQLite on the same machine is akin to calling fwrite. That's fine. This is also a system constraint as it forces a one-database-per-instance design, with no data shared across nodes. This is fine if you're putting together a site for your neighborhood's mom and pop shop, but once you need to handle a request baseline beyond a few hundreds TPS and you need to serve traffic beyond your local region then you have no alternative other than to have more than one instance of your service running in parallel. You can continue to shoehorn your one-database-per-service pattern onto the design, but you're now compelled to find "clever" strategies to sync state across nodes.
Those who know better to not do "clever" simply slap a Postgres node and call it a day.
> SQLite on the same machine is akin to calling fwrite.
Actually 35% faster than fwrite [1].
> This is also a system constraint as it forces a one-database-per-instance design
You can scale incredibly far on a single node and have much better up time than github or anthropic. At this rate maybe even AWS/cloudflare.
> you need to serve traffic beyond your local region
Postgres still has a single node that can write. So most of the time you end up region sharding anyway. Sharding SQLite is straight forward.
> This is fine if you're putting together a site for your neighborhood's mom and pop shop, but once you need to handle a request baseline beyond a few hundreds TPS
It's actually pretty good for running a real time multiplayer app with a billion datapoints on a 5$ VPS [2]. There's nothing clever going on here, all the state is on the server and the backend is fast.
> but you're now compelled to find "clever" strategies to sync state across nodes.
That's the neat part you don't. Because, for most things that are not uplink limited (being a CDN, Netflix, Dropbox) a single node is all you need.
May be an "out" there question, but any tech book suggestions you'd recommend that can teach an average dev on how to build highly performant software with minimal systems?
I feel like the advice from people with your experience is worth way way way way more than what you'd hear from big tech. Like what you said yourself, big tech tends to recommend extremely complicated systems that only seem worth maintaining if you have a trillion dollar monopoly behind it.
How do you manage HA?
Backups, litestream gives you streaming replication to the second.
Deployment, caddy holds open incoming connections whilst your app drains the current request queue and restarts. This is all sub second and imperceptible. You can do fancier things than this with two version of the app running on the same box if that's your thing. In my case I can also hot patch the running app as it's the JVM.
Server hard drive failing etc you have a few options:
1. Spin up a new server/VPS and litestream the backup (the application automatically does this on start).
2. If your data is truly colossal have a warm backup VPS with a snapshot of the data so litestream has to stream less data.
Pretty easy to have 3 to 4 9s of availability this way (which is more than github, anthropic etc).
My understanding is litestream can lose data if a crash occurs before the backup replication to object storage. This makes it an unfair comparison to a Postgres in RDS for example?
Last I checked RDS uploads transaction logs for DB instances to Amazon S3 every five minutes. Litestream by default does it every second (you can go sub second with litestream if you want).
your understanding is very wrong. please read the docs or better yet the actual code.
> Backups, litestream gives you streaming replication to the second.
You seem terribly confused. Backups don't buy you high availability. At best, they buy you disaster recovery. If your node goes down in flames, your users don't continue to get service because you have an external HD with last week's db snapshots.
If anything backups are the key to high availability.
Streaming replication lets you spin up new nodes quickly with sub second dataloss in the event of anything happening to your server. It makes having a warm standby/failover trivial (if your dataset is large enough to warrant it).
If your backups are a week old snapshots, you have bigger problems to worry about than HA.
No offense, you wait. Like everyone's been doing for years in the internet and still do
- When AWS/GCP goes down, how do most handle HA?
- When a database server goes down, how do most handle HA?
- When Cloudflare goes down, how do most handle HA?
The down time here is the server crashed, routing failed or some other issue with the host. You wait.
One may run pingdom or something to alert you.
> When AWS/GCP goes down, how do most handle HA?
This is a disingenuous scenario. SQLite doesn't buy you uptime if you deploy your app to AWS/GCP, and you can just as easily deploy a proper RDBMS such as postgres to a small provider/self-host.
Do you actually have any concrete scenario that supports your belief?
> SQLite doesn't buy you uptime if you deploy your app to AWS/GCP
This is...not true of many hyperscaler outages? Frequently, outages will leave individual VMs running but affect only higher-order services typically used in more complex architectures. Folks running an SQLite on a EC2 often will not be affected.
And obviously, don't use us-east-1. This One Simple Trick can improve your HA story.
> You can scale incredibly far on a single node
Nonsense. You can't outrun physics. The latency across the Atlantic is already ~100ms, and from the US to Asia Pacific can be ~300ms. If you are interested in performance and you need to shave off ~200ms in latency, you deploy an instance closer to your users. It makes absolutely no sense to frame the rationale around performance if your systems architecture imposes a massive performance penalty in networking just to shave a couple of ms in roundtrips to a data store. Absurd.
You need regional state, or you're still back hauling to the db with all the lag.
That only solves read latency not write latency. Unless you don't care about consistency.
https://antonz.org/sqlite-is-not-a-toy-database/ — 240K inserts per second on a single machine in 2021. The problem you describe is real, but the TPS ceiling is wrong by three orders of magnitude on modern hardware.
Do you know why it is a toy? Because in a real prod environment after inserting 240k rows per second for a while you have to deal with the fact that schema evolution is required. Good luck migrating those huge tables with Sqlite ALTER table implementation
This doesn't seem like a toy but you know... realizing different systems will have different constraints.
Not everyone needs monopolistic tech to do their work. There's probably less than 10,000 companies on earth that truly need to write 240k rows/second. For everyone else, we can focus on better things.
Try doing that on a “real” DB with hundreds of millions of rows too. Anything more than adding a column is a massive risk, especially once you’ve started sharding.
Yes it might be risky. But most schema evolution changes can be done with no or minimal downtime even if you have to do then in multiple steps. When is a simple ALTER going to be totally unacetable if youare using Sqlite?
I wonder what percentage of services run on the Internet exceed a few hundred transactions per second.
I’ve seen multimillion dollar “enterprise” projects get no where close to that. Of course, they all run on scalable, cloud native infrastructure costing at least a few grand a month.
I think the better question to ask is what services peak at a few hundred transactions per second?
I mean, your "This is fine for" is almost literally the whole point of TFA, that you can go a long way, MRR-wise, with a simpler architecture.
FYI, the color gradient on your website is an easy tell that it was vibe coded: https://prg.sh/ramblings/Why-Your-AI-Keeps-Building-the-Same...
A blog that's 11 years old and uses a minimalist CSS framework https://picocss.com ?
It's a static blog that renders markdown... there's literally nothing to code, let alone vibe code.
Looks like the overhead is not insignificant:
Running 100,000 `SELECT 1` queries:
PostgreSQL (localhost): 2.77 seconds
SQLite (in-memory): 0.07 seconds
(https://gist.github.com/leifkb/1ad16a741fd061216f074aedf1eca...)I love them both too but that might not be the best metric unless you’re planning to run lots of little read queries. If you’re doing CRUD, simulating that workflow may favor Postgres given the transactional read/write work that needs to take place across multiple concurrent connections.
> I love them both too but that might not be the best metric unless you’re planning to run lots of little read queries.
Exactly. Back in the real world,anyone who is faced with that sort of usecase will simply add memory cache and not bother with the persistence layer.
Not sure that’s always right either though. For example Mapbox used to use an SQLite database as the disk cache for map tile info. You cannot possibly store that amount of data in memory, so it’s a great use case.
This is mostly about thread communication. With SQLite you can guarantee no context switching. Postgres running on the same box gets you close but not all the way. It's still in a different process.
This. Run an app on the same box as PG and you can easily be plagued by out of memory etc (as there's memory contention between the two processes).
Most important is that that local SQLite gets proper backups, so a restore goes without issues
Gets proper backups if you back it up the right way https://sqlite.org/backup.html
A total performance delta of <3s on ~300k transactions is indeed the definition of irrelevant.
Also:
> PostgreSQL (localhost): (. .) SQLite (in-memory):
This is a rather silly example. What do you expect to happen to your data when your node restarts?
Your example makes as much sense as comparing Valkey with Postgres and proceed to proclaim that the performance difference is not insignificant.
Why are you comparing PostgreSQL to an in-memory SQLite instead of a file-based one? Wow, memory is faster than disk, who would have thought?
Because it doesn't make a difference, because `SELECT 1` doesn't need to touch the database:
Running 100,000 `SELECT 1` queries:
PostgreSQL (localhost): 2.71 seconds
SQLite (in-memory): 0.07 seconds
SQLite (tempfile): 0.07 seconds
(https://gist.github.com/leifkb/d8778422d450d9a3f103ed43258cc...)Queries for small SaaS are usually in the thousands of records, if not hundreds.
Why are you doing meaningless microbenchmarks?
Are you claiming that this does not show the speed difference between socket vs in process communication?
> Because it doesn't make a difference, because `SELECT 1` doesn't need to touch the database:
I hope you understand that your claim boils down to stating that SQLite is faster at doing nothing at all, which is a silly case to make.
The original claim being discussed is about the overhead of an in-process database vs. a database server in a separate process, not about whether SQLite or PostgreSQL have a faster database engine.
How about pg on Unix socket?
Running 100,000 `SELECT 1` queries:
PostgreSQL (localhost): 2.84 seconds
PostgreSQL (Unix socket): 1.93 seconds
SQLite (in-memory): 0.07 seconds
SQLite (tempfile): 0.06 seconds
(https://gist.github.com/leifkb/b940b8cdd8e0432cc58670bbc0c33...)It is insignificant if you're doing 100k queries per day, and you gain a lot for your 3 extra seconds a day.
What a useful "my hello-world script is faster than your hello-world script" example.
You can't simply copy/paste a Postgres database though...also you'd be surprised how fast SQLite can be...I've used SQLite for projects where I just couldn't get the performance elsewhere. For example, I had a names database with over 100 million rows in it for converting names to diminutives (e.g. David to Dave) and the inverse...after I precomputed a metric ton of indices it went like a rocket. Sure the file was quite big but oh boy was it quick.
I have used SQLite with extensions in extreme throughput scenarios. We’re talking running through it millions of documents per second in order to do disambiguation. I won’t say this wouldn’t have been possible with a remote server, but it would have been a significant technical challenge. Instead we packed up the database on S3, and each instance got a fresh copy and hammered away at the task. SQLite is the time tested alternative for when you need performance, not features
> It's not much harder to use than SQLite, you get all of the Postgres features [..]
More features is a net negative if you don't need those features. Ideally you want your DB to support exactly what you need and nothing more. Not typically realistic but the closer you can get the better.
A feature you don't think you need today, might be one you actually need tomorrow. It would be short-sighted to choose some tech based only on what you need today. If the extra features don't cost you anything, I can't see that as a "net negative".
> It's not much harder to use than SQLite, you get all of the Postgres features, it's easier to run reports or whatever on the live db from a different box, and much easier if it comes time to setup a read replica, HA, or run the DB on a different box from the app.
Isn't this idea to spend a bit more effort and overhead to get YAGNI features exactly what TFA argues against?
As someone who sets up a k3s cluster for a single user project I feel called out.
The thing is one you learn the technology, everything else seems more work than the "easy way".
I've been doing that for decades.. People seem to simply not know about unix architecture.
What I like about sqlite is that it's simply one file
Thats just swapping another enterprise focused concern into the mix. Your database connection latency is absolutely not a concerning part of your system.
Its not a significant concern because we've learned the hacks to work around it, but it is pretty freeing to not have to put hacks into your app.
I mean, you’re not wrong about the facts, but it’s also pretty trivial to migrate the data from SQLite into a separate Postgres server later, if it turns out you do need those features after all. But most of the time, you don’t.
I bet that takes more time than the 5 extra minutes you take to setup Postgres in the same box upfront.
To export a database? Probably even faster. And that's ignoring the difference in performance.
So you are migrating from Sqlite to Postgres because you need it. What is the state of your product when you need to do this migration? Is your product non trivial? Are you now dependent on particular performance characteristics of Sqlite? Do you now need to keep your service running 24/7? Accounting for all of that takes way more than 5 minutes. The only way to beat that is if you still have a toy product and you can just export the database and import it and pray that it all works as a migration strategy.
you also get a much better query execution engine, so if you need to run reports or analytics they will be faster
IIRC TCP/IP through localhost actually benchmarked faster than Unix sockets because it was optimized harder. Might've been fixed now. Unix sockets gives you the advantage of authentication based on the user ID of who's connecting.
My experience with sqlite for server-based apps has been that as your app grows, you almost always eventually need something bigger than sqlite and need to migrate anyway. For a server-based app, where minimizing deployment complexity isn't an extremely important concern, and with mixed reads and writes, it's rarely a bad idea to use Postgres or MariaDB from the start. Yes there are niche scenarios where sqlite on the server might be better, but they're niche.
If this sounds like basic advice, consider there are a lot of people out there that believe they have to start with serverless, kubernetes, fleets of servers, planet-scale databases, multi-zone high-availability setups, and many other "best practices".
Saying "you can just run things on a cheap VPS" sounds amateurish: people are immediately out with "Yeah but scaling", "Yeah but high availability", "Yeah but backups", "Yeah but now you have to maintain it" arguments, that are basically regurgitated sales pitches for various cloud platforms. It's learned helplessness.
When I was a consultant we would plan out 25 piece cloud deployments for little pie in the sky apps that would never see more than 200 users. Everyone has been trained that 'cloud' means a lot of expensive moving parts and doesn't stop to plan their deployments beyond that.
Digital ocean has Kubernetes ffs.
It's all of five minutes to write a deployment yaml and ingress and have literally anything on the web for a handful of dollars a month.
I've written rust services doing 5k QPS on DO's cheapest kube setup.
It's not rocket science.
Serverless node buns with vite reacts are more complicated than this.
Ten lines of static, repeatable, versioned yaml config vs a web based click by click deploy installer with JavaScript build pipelines and magical well wishes that the pathing and vendor specific config are correct.
And don't tell me VPS FTP PHP or sshing into a box to special snowflake your own process runner are better than simple vanilla managed kube.
You can be live on the web from zero in 5 minutes with Digital Ocean kube, and that's counting their onboarding.
Don’t forget that people involved in information technology procurement will pay very large sums of the company’s money to not have to understand anything.
“Cloud-native natives” had so much free plans that had no need to understand what a basic app really needs.
Hmm backups seems like an important one.
Litestream [1] is quick to set up and has point in time backup to the second.
- [1] https://litestream.io
Yes, and is super easy.
I do like this: cron to run the backup and then rsync to https://www.rsync.net, then an after script that check it was run and post to my telegram the analysis.
That is.
Another good option is Restic, since snapshots let you go back in time. That is useful in case you accidentally delete/break something and you're not quite fast enough to restore from backup before the next cron runs.
“Guys, we need to postpone our beta launch! We need another week to implement a backup strategy with point in time recovery!”
You don’t need backups until you have customers.
So go live without testing the backup in the beta at all?
Yes! Why build a backup process before you know you have data worth backing up.
The data recovery process needs to be validated too, preferably before customer data actually needs to be recovered.
Why go live if you don't have a reasonable expectation of users?
Worrying about HA when you don't have customers that need it is one thing, but I wouldn't want to be in a place where I have to put a banner on the website asking users to please make a new account because we had an oopsie.
And also incredibly trivial to fix. Most VPS providers include their own backup services, and for the rest there's rsnapshot and some other cheaper VPS somewhere else to keep it "off site."
Too many have forgotten what it means to administrate a single system. You can do a lot with very simple tooling.
And now big tech often doesn't even have the high availability to show for all that complexity.
The better availability and scalability of “the cloud” always relied on so many things being done and maintained just right by just the right people that I don’t think it’s ever been broadly true.
You get such a large performance malus and increase in complexity right from the start with The Cloud that it’ starts at a serious deficit, and only eventually maybe overcomes that to be overall beneficial with the right workload, people, and processes. Most companies are lacking minimum two of those to justify “the cloud”.
And that’s without even considering the cost.
What I think it actually is, is a way for companies that can’t competently (I mean at an organizational/managerial level) maintain and adequately make-available computing resources, to pay someone else to do it. They’re so bad at that, that they’re willing to pay large costs in money, performance, and maybe uptime to get it.
Remember if you ever feel disappointed, the king of scale Google playstore updates stats once a day
Not just stats. Configuration changes take around a day to take effect as well. Figuring out how to do authentication and permissions was such a pain. A half-assed integration with google cloud doesn't quite behave like the normal google cloud. Vague error messages. And every time you changed something you couldn't be certain your new setting was incorrect until you waited for an approximate day.
Apparently the phrase cargo cult software engineering is not common anymore. Explains these things perfectly.
I end up explaining this term to every junior developer that doesn't know it sooner or later, the same way I explain bike shedding to all PMs that don't know it... often sooner, rather than later.
It seems to really help if you can put a term to it.
Heh, I was gonna say cargo cult might mean something different in today’s programming landscape but then I thought about it for a second and it actually reinforces th meaning.
I don't know what to say. People keep saying these engineers exist and here I am not having seen a single, and I follow many indie hackers communities.
A devops coworker found my blog and asked me how I host it, is it Kubernetes. I told him it's a dedicated server and he seemed amazed. And this was just a blog. It's real
I heard the same story many times before.
Devops engineers did not know 101 of cable management or what even a cage nut is and being amazed to see a small office running 3 used dell servers bought dirt cheap, and shocked when it sounded like a air raid when they booted up, thought hot swapping was just magic.
It is always the case - earlier in the 80s-90s programmers were shaking their heads when people stopped learning assembly and trusted the compilers fully
This is nothing and hardly is shocking? new skills are learnt only if valuable otherwise one layer below seems like magic.
Does your coworker run a blog on k8s?
None of them self host anything at all. It's like that skill was totally skipped. But they advise and consult on infra
Well, by the time you are hiring a dedicated infra role, you should be past the single VPS stage.
My point is that none of these coworkers have ever been at that stage. He was surprised about me hosting something because he seems to think hosting is expensive and for companies. Straight in at the top end of k8s and microservices
There's plenty of people that got a CS degree and went to work and this is only a job for them, they have no interest outside of work. Unfortunately I'm not one of those people so I get off work troubleshooting issues to troubleshoot issues at home lol though there aren't that many just my choice to self host cameras through HomeKit sometimes falls apart somehow but im also squeezing every KB or RAM out of that beelink I can.
Don't get me wrong I don't think a homelab is necessary, but I think people who have only done this in a big corporate environment are doing themselves a disservice - either a small company or a homelab can fix that itch, but like you say a lot of people don't have the interest