How to use Postgres for everything
github.com
github.com
Now if you have the actual technical leadership [1] to scale your systems by drawing logical and physical boundaries so that each unit has its own Postgres? Yeah Postgres for everything is solid.
[1] Surprisingly rare I've found. Lots of "successful" CTOs who don't do this hard part.
I think that's actually the point of "postgres for everything"?
Not to mention that a random team writing a migration that locks a key shared table (or otherwise chokes resources) now causes outages for everyone.
- Docs and guidelines on migrations would have been written
- Some level of approval and review is required before execution
These are things that isn't really postgres specific, any company that doesn't have those is going to be a nightmare.
e.g. User management deploy has somehow taken down core payment processing.
Overengineering is a plague amongst SWEs, and almost as dangerous as failing to sell the product in the market.
Sorry if this comes off as brusque, but I've seen good engineers want to use Elasticache for the right reasons and seen "leaders" tell them no because "caching is one of the hard problems in computer science."
Leadership-by-folksy-saying is sadly a real thing.
Not dealing with loads of technology choices in the early days is a boon.
Once we hit more than $100m in rev it made sense to allow a bit more optimisation for purpose - but only when we had cash flow to pay for it. Otherwise all these fancy-shmancy choices are just dressed-up tech debt.
Yeah my original comment was about experiences working at places with this kind of monetary success (if not more) and stubbornly not evolving. Low trust eng leadership philosophies will do that though.
As I see it the only way to look at scaling issues is that you’ve made it. Almost no user facing software will ever reach a point where something like Django couldn’t run it perfectly fine. It’s when you do, you build solutions, which sometimes means forking and beating the hell out of Django (Instagram), sometimes going into Java or whatever, spreading out to multiple data bases and so on. Of course once you actually hit the scaling issues you’ll also have enough engineers to actually do it.
You can't just keep "doing things that don't scale" forever. If you have 100+ engineers [1], you aren't a startup anymore no matter your roots.
[1] and remember, my comment that is the ultimate parent of this conversation is about not continuing to do this at 100+. I said nothing about scaling up to that point.
In my experience performance is often directly related to the ratio (total RAM available to Postgresql, shared buffer and buffercache included / total size of tables and indices ), save any weird usage pattern.
If you don't have schema cache reloading enabled (i.e. you're running it in prod), you can linearly scale postgrest instances pretty much infinitely without impact to the db. Each new instance just costs you a handful of connections and a small amount of memory.
Even if you try to draw boundaries between different bits of the system, you are unlikely to end up with 40 servers, not even close. The average system wouldn't even have 40 separate use cases for PostgreSQL or even the need for 40 different dependencies.
However, if you do split it up early, you more realistically would have something along the lines of:
* your main database - the main business domain stuff goes here
* key-value storage/cache - decoupled from the main instance, because of the decoupling nobody will be tempted to put business domain specific columns here but keep it generic
* message queue - for when processing some data takes a bunch of resources but during peak load you need to register a bunch of stuff quickly and process it when you get the capacity
* blob storage - to not make the main database bloat a whole bunch, but to keep any binary stuff in a separate instance, provided you don't need S3 compatibility
* auth - the actual user data, assuming you use Keycloak or something like it
* metrics - all of the APM stuff, like from Apache Skywalking or PostgreSQL
Give or take 2-3 services, all of which can run off of a Docker Compose stack locally or with the container management platform of your choice (not even Kubernetes necessarily, but simpler ones like Hashicorp Nomad or even Docker Swarm, using the Compose format). All of which can have their backups be treated similarly, similar approaches to clustering, all of which have similar applicable tools, all of which have similar libraries for integration with any apps and all of which can be granularly inspected in regards to how they perform and scaled as needed.It's arguably better than a single large instance that ends up with 300 tables eventually, has 100 GB of data in the shared test environment and you wouldn't know where to start in regards to making a working local environment if you join a legacy org that isn't using OCI containers and giving each dev a local environment. The single large deployment will rot faster than multiple ones. How many you actually need? Depends on what you do, it wasn't that long ago that GitLab decided to split their singular DB into multiple parts: https://about.gitlab.com/blog/2022/06/02/splitting-database-... (which also shows that you can get pretty far with a single schema as well, to anyone who wants a counter argument, though they did split in the end)
Realistically, if you're doing a personal or small project, everything can be in the same instance because it'll probably never go that far, but I've also seen both monolithic codebases (that are deployed as singleton apps, e.g. not even horizontal scaling) and what shoving all of the data in a single database leads to (regardless of whether it's PostgreSQL or Oracle), it's never pleasant. I'd at the very least look in the direction of splitting out bits of a larger system based on the use case, not even DDD, just like "here's the API and DB instance that process file uploads, they are somewhat decoupled from our business logic in regards to products and shopping carts and all that".
Now, would I personally always use PostgreSQL? Not necessarily, since there are also benefits to Redis/Valkey, MinIO, RabbitMQ and others, alongside good integrations with lots of frameworks/libraries that just work out of the box, as opposed to you needing to write a bunch of arguably awkward SQL for PostgreSQL. But the idea of using fewer tools like PostgreSQL for different use cases (that they are still good for) seems sound to me as well.
And you are lucky to not see an org where everything is a microservice that uses some unusual database, because the poeple responsible wanted to use some fancy new technology. Also seems you were lucky to not see messy development where some data is in a legacy system, some in new (which doesnt quite work), some in "cool" mongoDB that uses math.random to report just 10% of errors and rest is plugged via CSV files coming from ERP edited in Excel...
I've been unlucky to see an org where everything is a monolith (not horizontally scalabe due to a plethora of design choices along the way) that uses Oracle, including plenty of stored procedures and DB links along the way.
Honestly, I'm starting to think that you can't win with these things and that there will be projects that suck to work with regardless of the tech stack or regardless of how much you try to make them not suck.
That said, in general, you could probably do worse than PostgreSQL or even MariaDB (because at least with that one you can still run it in a container locally, much like MySQL, instead of running into Oracle XE or Oracle free version, whatever they called the latest one, limitations where you can't even bring the shared environment schema over to local containers).
> you can't win
Is there anything good about Oracle in 2024? Their business model seems to be make products that require expensive consultants and are difficult to migrate-out.
I still have some optimism in me and think that you can win - if you use the correct technologies.
Every now and then there are multiple blog posts here about "choosing boring technology". For example the blog about 7 databases in 7 weeks linked to this version: https://boringtechnology.club/
- marking some columns as NOT NULL.
- referential integrity means you can't accidentally have dangling pointers to non-existant dat.
- mutually exclusive columns let's the database enforce things like "at least one of A and B needs to be Nonzero, and both cannot be Nonzero at the same time.
- create a type that allows only values matching a specific regex.
Seriously, if you want strong typing across composite data, there isn't a language invented yet that comes even close to a RDBMS.
Of course the majority of Devs don't know any of this because their ORM doesn't expose any of this; it gives them a way to store and query tabular data, and nothing else.
I didn't claim that they are not compatible with ORMS. I said the majority of developers have no clue just how much of value they can get out of their database using types and constraints because the only interface they have every used to the RDBMS is the ORM, and the ORM doesn't expose any of this.
I've commonly seen developers put in things like `if ((!A && B) || (A && !B)) { /* updateDbWithOneOf(A,B) */ }` in their code rather than use the constraints provided by the RDBMS.
Postgres for everything is pretty neat in that you can take expertise from one place, and use it somewhere else (or just not have to learn a zillion tools)
The same database for everything is a really good way to have a tangled mess (are people still using the word complected?) where nobody knows which parts are depended on by what.
I wrote about this topic before and haven't changed my opinion much. You don't want to have that tight coupling: https://wundergraph.com/blog/six-year-graphql-recap#generate...
2. Your data structures should not dictate the shape of your api, usage patterns should (e.g. the user always needs records a,b,c together or they have a and want c but don’t care about b)
3. It stops you changing any implementation details later and/or means any change is definitely a breaking change for someone.
And in 30 odd years, everything will be different again, but your company's main data store will not have moved as fast.
To additionally answer you with an analogy: when you have a problem with a company, you call the call center, not Jenny from accounting in particular. Jenny might have helped you twice or thrice but she might leave the company next year and now you have no idea how to solve your problem. Have call centers to dispatch your requests wherever it's applicable in the given day and leave Jenny alone.
As Joel Spolsky put it: ”the cost of software is the cost of its coupling”.
More specifically the cost of making changes when “if I change this thing I have to change that thing”. But if there’s no attention paid to coupling, then it’s not just the two things you gave to change, but “if I change this thing I have to change those 40 things”.
[1] https://wiki.postgresql.org/wiki/Loose_indexscan
[2] https://stackoverflow.com/questions/28813409/are-null-bytes-...
I am also a bit annoyed by cache-like uses not being first-class. Unlogged tables get you far, temporary tables are nice, but still all this feels like a hurdle, awkward and not what you actually need.
Since what happened recently with Redis[1] the first thing I thought about was Postgre, but the performance[2] difference is too noticeable, so one have to look for other alternatives, and not very confident due thinking such alternatives may follow the same "Redi's attitude" ( ValKey, DragonflyDB, KeyDB, Kvrocks, MinIO, RabbitMQ, etc etc^2 ).
It would be nice if these cache-like uses within Postgre had a tinny push.
[1] https://news.ycombinator.com/item?id=42239607
[2] https://medium.com/redis-with-raphael-de-lio/can-postgres-re...
XXXXX achieves a latency of 0.095 ms, which is approximately 85% faster than the 0.679 ms latency observed for Postgres’ unlogged table.
It also handles a much higher request rate, with 892.857,12 requests per second compared to Postgres’ 15.946,02 transactions per second.That said, I’m with you. And if someone wants nulls inside their “strings” then they probably want blobs.
There are two types of programmers, those that are wrong and those that are very wrong
Funny but not entirely true. I had cases when we had to urgently store a firehose of data and figure out the right string encoding later. Just dumping the strings with uncertain encoding in `bytea` columns helped us there.
Plus for some fields it helps with auditability f.ex. when you get raw binary-encoded telemetry from devices in the field, you should store their raw payloads _and_ the parsed data structures that you got from them. Being this paranoid has saved my neck a few times.
The secret is to accept you are not without fault and take measures to be able to correct yourself in the future.
There's a lot to complain about with nul-terminated strings, but not being able to store arbitrary bytes ain't one of them.
Let me introduce you to blob…
If you’re already using Postgres and want a Python-native way to manage background jobs without adding extra infrastructure, PGQueuer might be worth a look: GitHub - https://github.com/janbjorge/pgqueuer
Simple example: if you have a mixture of very short jobs and longer duration jobs, then there might be hundreds or thousands of short jobs executed for each longer job. In such a case the rows in the jobs table for the longer jobs will be skipped over hundreds of times. The more long-running jobs running concurrently, the more wasted work as locked rows get skipped again and again. It wouldn't be a huge issue if load is low, but surely a case where rows get moved to a separate "running" table would be more efficient. I can think of several other scenarios where SKIP LOCKED would lead to lots of wasted work.
This way you won't need to skip over the long jobs that are in state "processing".
While a separate "running" table reduces skips, it adds complexity. SKIP LOCKED strikes a good balance for simplicity and performance in many use cases.
One known issue is that vacuum will become an issue if the load is persistent for longer periods leading to bloat.
Generally what you need to do there is have some column that can be sorted on that you can use as a high watermark. This is often an id (PK) that you either track in a central service or periodically recalculate. I've worked at places where this was a timestamp as well. Perhaps not as clean as an id but it allowed us to schedule when the item was executed. As a queue feature this is somewhat of an antipattern but did make it clean to implement exponential backoff within the framework itself.
Check out the Similar Projects section in the docs for a whole bunch of Postgres-backed task queues. Haven't heard of pgqueuer before, another one to add!
LISTEN/NOTIFY type functionality was sort of missing but otherwise it was surprising how it is keeping up also, while little of that is probably being used by many legacy apps.
https://github.com/jankovicsandras/plpgsql_bm25
Opensource BM25 search in PL/pgSQL (for example where you can't use Rust extensions), and hybrid search with pgvector and Reciprocal Rank Fusion.
We discussed this very thing on supabase
https://github.com/orgs/supabase/discussions/18061#discussio...
Getting up in the morning, seeing an article that references you, bliss!
pg fulltext search is very limited and user unfriendly, not a great suggestion here
For example instead of integrating with a message queue I can just do an INSERT this is great. It lowers the friction.
Vector search is a no brainer too. Why would I have 2 databases when 1 can do it all.
Using Postgres to generate HTML is questionable though. I haven't tried it but I can't image its a viable way to create user interfaces.
Check https://apex.oracle.com/ which does that.
[0] https://youtube.com/playlist?list=PLBrWqg4Ny6vVwwrxjgEtJgdre...
On promise: Use containers but the data folder should be mounted volume
On cloud/k8s: Just use a managed DB, setting up a DB in k8s is hard because the filesystem
I found https://www.thenile.dev/pricing which supports which apparently supports unlimited databases.
Postgres major version upgrades are the main reason I don’t self host it, though maybe I should rethink my position on that!
For tuning, postgresqlco.nf[1] is great.
Now hoping for better results with DGraph, but it seems that graph databases are living a precarious existence.
Subjectively I’d prefer neo4j. I have been following the company since its inception.
I could imagine there to be a few (that I can't think of and haven't seen). I have seen graph databases used where they didn't make sense though.
IMHO the true limitations of RDBMS are not about usage, but scaling: Multi master across simple zones, High availability, Partitioning.
(IMHO it comes from ACID compliance, so I don't know if it's even solveable natively)
That was the whole marketing spiel of Cassandra DB in 2012+ and the source of the CAP theorem
I strongly believe that picking some tech stack you know when you are starting out is always the right decision, until it's not. Only then do you pick a different solution.
Better to move fast leveraging what you know, until you need something else.
Easily the most stable part of the stack (100% uptime on RDS since 2021/02/01).
I want to get something simple setup but couldn't get it to work.
I want to match substrings like "/r/chatgpt" (sub reddits) in url links, but couldn't get it to match.
Tried a few types of queries like phrase, plain, default, simple, english. All have some weird issues, either not matching special characters, or not matching substrings (partial match). Also I'm somewhat limited on the syntax side by what can be done with drizzle ORM.
This likely means tokenization striping out special characters. Try ngram search methods. They should work out of box.
With txtai (https://github.com/neuml/txtai), I've went all in with Postgres + pgvector. Projects can start small with a SQLite backend then switch the persistence to Postgres. With this, you get all the years of battle-tested production experience from Postgres built-in for free.
Taking graph databases, for example, Postgres (via Apache AGE) stores graph data in relational tables with O(log(n)) index lookups and O(k ⋅ log(n)) traversal*
Whereas a true graph database like Neo4J stores graph data in adjacency lists, which means O(1) index lookups and O() traversal.
That's a massive difference in traversal complexity for most graphs.
*k is the degree of the node (number of edges connected to a node).
[1] https://www.postgresql.org/docs/current/datatype-binary.html
``` #!/usr/bin/env sh
set -e
PGUSER=miniflux PGPASSWORD=... pg_dump -F t -h 127.0.0.1 miniflux | gzip > /backups/miniflux_db_temp.tar.gz mv /backups/miniflux_db{_temp,}.tar.gz ```
Created a PR to update some of the vectordb links and also fixed the markdown: https://github.com/Olshansk/postgres_for_everything/pull/1
Second best, how would you implement this in Postgres? I’m tempted to give it a go but I haven’t a fully-baked plan yet.
You could, but it is going to force technical compromises. Even tools that can do everything aren't the best at everything.
macOS, Safari and Firefox.
I'm wondering, any approach for bitemporal dbs, like xtdb?
It scales just fine depending of course on the usual stuff - load pattern etc. If you want really high scalability you should be using something like clickhouse anyway.
Edit to add: the rationale behind having separate intraday and daily time series tables in those systems is the type of the value and as of timestamps is different (in one its a date, in one its a datetime), and storing dates as datetimes is a rich source of bugs.
I agree a special database shouldn't be necessary at all, and instead, convenient syntax for immutable DML and temporal support should be built into Postgres already. But short of a miracle it will probably take a new ('special') database in order for Postgres to evolve in response. Therefore, in the meantime, we believe there's a gap in the market for organisations who value the 'safety' (foolproof complexity reduction) that native bitemporality in a database can offer above the raw query performance offered by existing update-in-place databases: https://xtdb.com/blog/but-bitemporality-always-introduces-co...
I was appointed in a company of 10 dev that did just that. All backend code was PostgreSQL functions, event queue was using Postgres, security was done with rls, frontend was using posgtraphile using graphql to expose these functions, triggers were being used to validate information on insert/update.
It was a mess. Postgres is a wonderful database, use it as a database. But don't do anything else with it.
Before some people come and say "things were not done the right way, people didn't know what they were doing". The dev were all fan of Postgres contributing to the projects around, there was a big review culture so people were really trying to the best.
The queue system was locking all the time between concurrent requests => so queue system with postgres works for a pet project
All the requests were 3 or 4 times longer due to fact that you have to check the rls on each row. We have also all pour API migrated now and each time the sql duration decrease by that factor ( and it is the exact same sql request ). And the db was locking all the time because of that as it feels likes rls breaks the deadlock detection Postgres algorithm
SQL is super verbose a language, you spend your time repeating the same line of code , it makes basic function about 100 lines long when they are 4-5 lines in nodes js
It is impossible to log things inside these functions to have to make sure things will work and if it doesn't you have no way to know where the code did go through
You can't make external API call, so you have to use a queue system to make any basic things there
There are not real lib , so everything need to be reimplemeted
It is absolutely not performant to code inside the db, you can't do a map so you O(n2) code all the time
API were needed for the external world , so there was actually another service in front of the database for some case and a lot of logic were reimplemeted inside it
There was a downtime at each deployment as we had to remove all the rls and recreate them ( despite the fact that all code was in insert if not update clauses) it worked at the beginning but at some point in time it stopped working and there was no way to find why, so drop all rls and recreate them
It is impossible to hire dev that wants to work on that stack and be business oriented , you would attract only purely tech people that care only about doing there own technical stuff
We are almost out of it now after 1 year of migration work and I don't see anything positive about this Postgres do everything culture compared to a regular node js + Postgres as a database + sqs stack
So to conclude, as a pet project it can be great to use Postgres like that, in a professional context you are going to kill the company with this technical choice
Also, what's Clickhouse for? Logs, observability data?
We use ClickHouse for time series data. Postgres was ok up to low billions of points. Despite trying to use timescale for this purpose it did not fit our use case.
If you don't mind one final question: can you ACK a message in NATS without it being bound to offset that makes it impossible to _not_ ACK a message without ruining the ACKs of the previous messages?
To clarify: I often found myself in situations when I was fetching batches of stuff from Kafka, say, 50 at a time, and then hand them off to 50 parallel agents to process. However, f.ex. messages 17, 31 and 47 failed processing and I could not not ACK them as that would not allow us to ACK those that succeeded before. So I ended up pushing them to another Kafka queue / topic that specifically deals with retries. That's IMO a hack, as most apps out there surely don't need the monstrous speed that Kafka can provide. I am OK with something (not much) slower where I have the freedom to ACK or not-ACK any particular event/message regardless of its position.
Does NATS allow for it?
For the first question, I'd definitely recommend using ClickHouse for 10B - 1T points.
1. ok
2. error
3. ok
4. ok
I cannot not-ACK message#2 because that means message#1 is not ACK-ed as well.
Does NATS solve this? F.ex. can I get a reference to each message in my parallel workers for them to also say "I am not ACK-ing this because I failed processing it, let the next batch include it again"?
But if NATS supports that use case then great, I'll migrate to it for that reason alone.
You can also 'negative ack' messages, specify a back-off period before the message is re-delivered (because NATS automatically re-delivers un-acked (or nacked) messages) when you can't temporarily process it, or 'term' a message (don't try to re-deliver it, e.g. because the payload is bad), or even 'ask for more time before needing to ack the message (if you are temporarily too slow at processing the message).
I like everything about this: the ability to NACK individual messages, the specifying of a backoff period, _and_ to just discard a message f.ex. if you really cannot do anything about it. Super nice. I am grateful.
https://www.powersync.com/ is a similar approach, but using sqlite on the client.
https://zero.rocicorp.dev/ also deserves a mention, but I believe it’s more of a read cache with writes going directly to the server. I’m not sure if it supports SQL client-side or has its own ORM.
Postgres was designed as a row-based OLTP database, with over 30 years of effort dedicated to making it robust for that use case.I know there are many extensions attempting to make Postgres support other use cases, such as analytics, queues, and more. Keep in mind that these extensions are relatively recent and aim to retrofit new capabilities onto a database primarily designed for transactional workloads. It’s like adding an F1 car engine to a Toyota Camry — will that work?
Extensions also have many issues—they are not fully Postgres-compatible. In Citus, for example, we added support for the COPY command four years into the company, and chasing SQL coverage was a daily challenge for 10 years. Being unable to use the full capabilities of Postgres and having to work around numerous unsupported features defeats the purpose of being a Postgres extension.
On the other hand, you have purpose-built alternatives like ClickHouse and Snowflake for analytics, Redis for caching, and Kafka for queues. These technologies have benefited from decades of development, laser-focused on supporting specific use cases. As a result, they are robust and highly efficient for their intended purposes.
I often hear that these Postgres extensions are expanding the boundaries of what Postgres can do. While I partly agree, I also question the extent to which these boundaries are truly being expanded. In this era of AI, where data is growing exponentially, handling scale is critical for any technology. These boundaries will likely be broken very quickly.
Take queues as an example: you have a purpose-built technology like Kafka or a Postgres extension that supports queues. For an early-stage startup, adopting a less optimized Postgres-based solution may (not a guarantee) save a few weeks of initial CapEx costs compared to using an optimized solution like Kafka. However, 6 to 12 months later, you may find yourself back to square one when the Postgres-based queue fails to scale. At that point, migrating to a purpose-built technology becomes an arduous task—your system has grown, and now it may take months of effort and a larger team to make the switch.
Ultimately, this approach can cost more time and money than starting with a purpose-built solution from the beginning, which might have only required a few extra weeks of CapEx. I’ve seen this firsthand at Citus, where customers like Cloudflare and Heap eventually migrated to purpose-built databases like ClickHouse and SingleStore respectively. While these migrations happened a few years later, times have changed — data grows faster now, and the need for a purpose-built database arises much sooner. It’s also worth noting that Citus was an incredible piece of technology that required years of development before it could start making a real impact.
TL;DR: Please think carefully before choosing the right technology as you scale. Cramming everything into Postgres might not be the best approach for scaling your business.
- the entire repository is 1 file
- the file is titled "read me"
- but you didn't read it (it's not proofread)
why do you want me to read something you did not read?