Hey author here. Wasn't expecting to see this up.
To concisely give an overview of the project, I've been experimenting with using LLMs to build a better version of Postgres. Postgres is 30 years old and we've learned a lot about databases since hten. A lot of the techniques that work for doing a rewrite are also useful for doing a rearchitecture.
I'm now working on a new, not yet published version of pgrust that incorporates a lot of techniques. Currently the new version:
- Passes 100% of Postgres regression suite
- Implements a thread per connection model instead of the process per connection model Postgres does
- Is 50% faster than Postgres on transaction workloads
- Is ~300x faster than Postgres on analytical workloads. Right now it's 2x slower than Clickhouse on clickbench and I think it's possible to get faster than Clickhouse
If you have any questions, I'm happy to answer them.
I don't want to knock you down as most have already did. In-fact it's a useful exercise going forward in exploring how to work with AI. It's here, we're all going to use it one way or the other. Zero issues with that, in-fact kudos to going through the pain of it all.
Now, having gone through several such endeavors originally myself, albeit with internal tools and systems (as an exercise), I've noticed that while all my tests passed with flying colors the rewrite itself was broken even on basic functionality or missed a ton of details. It was in effect useless when I dived into it. Initial tests also showed massive gain in performance, and I know people who were involved aren't really dumb so something smelled funny. Turns out all those things left out and honestly... moments were the key ingredients.
What I did learn from those beginning explorations though was that one-shotting, grand architecture or source up-front, master plans up-front.. all these do not yield good results - YET. Who know what we'll see in few years though. What I did found that works (FOR ME, nota bene) is to keep the design and checklists for myself, written by myself and then do a small piece by piece.. as if you would if you were coding alone or if you would waterfalling a small team of talented juniors. Then, suddenly super happy results come out, but then it's mostly you driving all the way where llm writes code and offers advice (which for the most part you ignore). It's a happy place for myself at least. It's then truly unlocking yourself to the mythical 10x.
Rewriting a large proven system with decades of ultra expertise behind it, which I don't have, is guaranteed not to end up the same 1:1 replacement. If you found a recipe for that - please do share.
I'm curious. Do you attribute this to weak and/or incomplete tests? How granular should tests be to have complete coverage so that an AI won't create a converted codebase that "passes tests" but is still functionally inaccurate?
There is no such thing as a complete test suite, there will always be some possible bug that it doesn't catch.
In particular, if you put an LLM in an automated loop of "this test fails, please fix it", there is a pretty good chance that it will simply special case all of the tests, possibly in some contrived way that makes it not at all obvious when you read the code.
That's the million dollar question. Do you/we/us have tests that cover everything which covers QA as well? If such a mythical beast exists, maybe from remnants of ye olde TDD past and hasn't been modified as such.. then maybe this would be possible to do as such.
what model did you try it with? I agree and also push back a bit: How will we know when the LLMs reach the point of handling it if no one takes the leap? I applaud more people sludging through the slop and hauling their slop buckets around.
An example is Fable being released. I felt like the most complex thing I was willing to sludge through was having it clone llama-server's web UI with my own opinions (I really like the original, kudos to them). And the initial skeleton was working so well I felt like I had sunk the tokens and committed to getting it the rest of the way: https://inkcap.click
Last time it was Opus 4.8 to no avail. Fable is too expen..precious to try that yet :)
A thread per connection is a almost always the correct decision for performance, but by choosing a process per connection, postgres is able to let you load whatever sketchy extensions you want. Worst case you crash the process, not the database. It would be nice if you could strike a balance so a segfaul in the extension only crashes a small percentage of connections, not the whole thing.
That’s not true for Postgres however: due to its usage of a shared memory pool, whenever a subprocess is terminated unexpectedly, Postgres will kill all other processes and enter recovery mode, replaying the WAL, during which time it will not accept connection requests.
It does this because it can’t possibly know whether the dying process did bad things to the shared memory pool.
You are correct! TIL, and thank you. Connection processes get a SIGQUIT, shared buffers cleared, and WAL replayed, but postmaster stays alive. It's effectively an online restart.
I’d be very curious to hear what an online restart approach for sketchy extensions crashing a shared thread per connection process might look like. This is more a question for the author of the project but I’m curious if they have plans in that direction.
systems, upstart or even simple primitive script wrapper.
Built-in is also possible: just fork once after start, and you have parent as watchdog and restarter.
What's online about this restart? Just that the tcp port keeps listening? I assume all running transaction will have to be aborted, right? And connections dropped?
The postmaster is effectively your supervisor. It could see the child segfault and abort the whole DB, but that would be deferring supervision to your init system. Not ideal.
If it's a choice between performance and being able to "safely" run sketchy extensions, I'd rather have performance.
A mixture of threads and processes that can be used to match processors, disk I/O, and network interfaces.
A very long time ago, there was once a feature called "Data Blades" which tanked a commercial database vendor. A badly behaving blade could bring down the entire database. Most anyone who has been working on databases for a few decades remembers this and makes a point of either not introducing these sorts of features or making use of processes over threads.
I have not looked at the code referenced in the mentioned project, but thus far I haven't seen a model that could craft a complete SQL parser on its own.
There are a number of problems, and design decisions, that a developer decides on when writing a database that I don't see any current models… just because you have the ingredients does not mean that the stew is edible.
Informix. Michael Stonebraker, to his credit, learned a lot along the way and revised his thinking about database technology and capability after believing that a single engine could be good at everything.
> A very long time ago, there was once a feature called "Data Blades" which tanked a commercial database vendor.
I have no idea what this is and a web search turned up Harbor Freight woodworking tools.
My first web search (data blades) turned up harbor freight woodworking tools.
Then I made a second web search: data blades database.
That turned up some ibm database software module technology which I assume is what's being discussed.
I performed both searches on Kagi and didn't see anything about IBM. I do see results for "DataBlade" and "Data blade database" but I didn't try those specific variations after my first two attempts returned nothing of interest. That's too much effort to decipher a HN post.
> That's too much effort to decipher a HN post.
It would have been less effort than it took you to write this comment. Perhaps next time that you can’t be bothered, just ignore the comment and move on to another thread that meets your required spoonfeeding levels.
Yeah you're absolutely correct. Too late to edit/delete.
ignoring the part where you tell others how to live, I believe the effort estimates you used suggest you may struggle typing and project it on others.
Doing a few searches and reviewing their results is effort.
Google doesn't really say what went wrong if it's about Informix.
people complain when not spoonfed; however there seems to be no issues about spending effort to complain.
I think it's this: https://www.ibm.com/docs/en/informix-servers/15.0.x?topic=co...
And what you found seems to be "Dado", not "Data".
Yes, Kagi was sufficiently confused as to what "Data blades" are that it actually thought I was looking for replacement woodworking blades. "DataBlade" finds the IBM Informix product.
While PG's behavior doesn't guarantee a lack of data corruption, "an extension crashed, all bets are off, tear everything down" is going to give you a much better fighting chance against data corruption vs the alternative.
In the age of vibe-generated code, I promise you're gonna want the safety.
What about just not installing sketchy extensions?
I know right? Postgres is not firefox, we don't operate with 25 extensions on all the time. At most we have 1 and normally we have 0.
So load them up in read replicas
Or use webassembly to sandbox them
You have to have a write path. You're gonna want what to be non-sketchy and performant.
Especially since all those sketchy extensions can be rewritten in rust over a weekend and have their bugs fixed as well.
You could fix probably the sketchy extension issue with WASM.
Limit, not fix.
Do you mean that you don't run sketchy extensions and therefore this doesn't affect you, or that you're ok with data loss due to extension failures?
What about extensions that are not sketchy? Lots of good ones out there.
People are assuming the extensions can't also be rewritten to be good.
Presumably they'd be fine running in a threaded context.
An extension written for a single threaded host system might not work in a multi-threaded context. For example if has global or shared state that isn't protected with locks or similar (which is unfortunately fairly common in c code)
Extensions like pgvector, TimescaleDB would probably need to be ported tho, not sure how much but there are footguns.
Not needed in many (most?) cases.
But it isn't.
Threads does not offer any major performance advantage, performance of processes vs threads is virtually the same. The reason the PostgreSQL project is moving towards threads is to make development easier.
> Threads does not offer any major performance advantage
This is very not true. When it comes to parallel queries, a process model adds a ton of overhead. You can't pass pointers between processes because the address space is different. This adds a ton of overhead in a bunch of different places. For example when doing a parallel hash join, Postgres will have each worker build a local hash table. Then it will take all the tuples out of the local hash table and copy them through shared memory to the leader who will then construct a new hash table. This duplicates a lot of work as you have to hash the tuples multiple times.
A lot of getting to Clickhouse level performance was making better use of parallelism.
Passing pointers is not significantly faster than passing offsets into a shared memory pool.
Sorry, what? Passing a pointer is a matter of wrapping the value into the CPU register. OTOH passing an offset into a shared memory is a write to main memory so several magnitudes slower.
Ok ... you know PostgreSQL supports hash tables in shared memory, right? PostgreSQL could in theory share those if we wanted to. The issue is just that coding anything which uses shared memory is a lot of work.
Additionally the reasons PostgreSQL does not offer Clickhouse performance has very little to do with parallelism. PostgreSQL plans to move to threading but the efforts around imporving OLAP performance are almost entirely unrelated.
> The issue is just that coding anything which uses shared memory is a lot of work.
Doesn’t that kind of prove the parent’s point though? In theory shared memory can do anything that threads can do. But if in practice some feature doesn’t get implemented in the multi-process design (because shared memory is hard), when it likely would have been implemented in a threaded design, then that’s still an advantage for threads.
Apart from being a lot of work are you really gaining much at that point? Memory corruption can still take down both sides...
i may be missing context, but shared memory across processes, without ipc?
There's nothing special about threads vs processes in Linux. mmap works the same, the challenge is to map the same file. You can share a path, pass a file descriptor via fork or unix domain socket, among other techniques.
That induces disk I/O overhead (even if it somehow doesn't impact IPC performance)
It doesnt. Processes can share memory
The file doesn’t have to be disk-backed.
Don't you need something mounted for that?
No, you can use a memfd.
And `/dev/shm/` (which postgres uses by default on most Unix platforms)
Being really pedantic here, shared memory is considered IPC, but not the kind you're thinking of. Shared address space, no overhead.
As long as we're pedantic ... the subject is shared memory. Unless you specify the same, non-null, target address in the call to mmap (and the kernel happens to grant you that mapping on all calling sites), the addresses will be different; the address space is not shared (each mapping might also have different access permissions).
That distinction is important as pointers generally cannot be shared (a problem which can of course be solved with one more indirection ;-) .
MSSQL can handle 32k open connections no need to run a pooler in front of it, can PG do 32k connections and a process for each?
MSSQL shares cached query plans between connections including jitted code, PG cannot do that and the changes needed to make the plans cross process portable would be extensive while sharing between threads is just normal code sharing between threads.
unless you're spawning them for new connections.
Some, but not that much. Switching PostgreSQL to a threaded model will not magically make spawning connections fast. PostgreSQL connections are quite heavyweight.
The reason to use threads is almost entirely about ease of development, not about performance. If you use shrared memory like PostgreSQL does you need to write your own allocators, etc. So much you get for free if you use threads.
Doesn't postgres (rightly) have a cow if a process has a disorderly shutdown (at least while in a write transaction) because there's shared memory between the processes?
Some see a 30 year old system and think "outdated", I see a 30 year old system and think "time tested."
Clearly a process per connection is more stable and that's what I'm using.
It's unclear what problem such optimizations are solving anyway, with the old way you could only support a million concurrent users with a single server? Are we missing out on supporting ten million concurrent users with 2 servers instead of 10? Ostensibly reducing the minimum db hardware opex for a 10B$ company from 10k$/month to 2k$/month?
A million users? Hell, I'd bet 99.999% of live postgres databases in existence serve less than 5 users on average. Even among products that actually make a profit, I bet 99.9% of them serve less than 100 customers a day. We hooligans on hacker news manage the 0.1% of databases, and in my newfound consulting life, I'm hoping to never support one of those again.
> Clearly a process per connection is more stable
Even if those processes share most of their memory, and are written in a notoriously memory-unsafe language?
A significant performance improvement can well be the difference between being able to run the entire database on one beefy server, and having to shard. And that has a huge cost in terms of complexity and thus reliability and development time.
Is that a serious issue? Wouldn't it just restart the split second later? Or does it take a long time to start?
(Or I guess it would get stuck in a doom loop or something?)
I'm not informed of the Postgres's internals, but, maybe, that can be solved by grouping threads into different processes depending on which set of extensions they request.
A more modern way to do this might be to support WebAssembly plugins.
The extensions might need to be rewritten, but hey, we have AI for that now, so why not :-)
An OS thread per connection can be fine for performance if you don't have to scale your connections, but if you don't need to scale connections why have connections at all? Databases are even more performant when you eliminate connection overhead entirely.
Thread pools
Ouf. I don't know. I don't want to call you out without evidence -- I myself make benchmark claims all the time -- but 50% improvement in OLTP seems suspicious. I get that you used a standard benchmark, and I don't even know what it entails, but my spidey sense is going off. Perhaps, some trade off somewhere that won't make it to prod because it breaks MVCC -- and yes, I saw that it passes regression tests.
Just checking, is fsync on? :) Regression tests don't catch bad IO patterns afaik.
Anyway... sounds like a fun project to work on!
Yeah, claims that it can be faster than CH are very suspicious. CH guys are very good at their craft, they spend hundreds of hours optimizing one single small detail.
Yeah. I don't think PG could even come close. Column-oriented is fundamentally different, and pairs well with all the SIMD acceleration ClickHouse is also doing. There's just no comparison. If a Postgres rewrite came close to that, it must've sacrificed something else.
Remember when databases were faster to run in virtualbox rather than bare metal? (because virtual box was completely ignoring all the instructions to flush the data on the disks)
It'd be very unfortunate if Postgres didn't have regression tests for data loss due to bad io patterns. Should be possible to do some checks against those in an appropriate test harness. Which might mean "have qemu run something we can kill off and examine the results".
If those don't exist, I hope folks recognize how useful they are and add them.
What's your actual background and expertise with Postgres and databases more broadly? Basically, do you actually know what you're doing, or is there likely a massive footgun you don't know or haven't shared with us?
I spent a couple years managing a Postgres cluster with a petabyte of data. I wrote a couple blog posts from my work then[0][1]. I also wrote dozens of posts on the Postgres internals[2]. I've also given talks on how to generate fractals with SQL[3] and how to write a lisp interpreter in SQL[4].
[0] https://www.heap.io/blog/testing-database-changes-right-way
[1] https://www.heap.io/blog/analyzing-performance-millions-sql-...
[2] https://malisper.me/table-of-contents/
[3] https://www.youtube.com/watch?v=xKoYIvMFnoQ
[4] https://www.youtube.com/watch?v=MPSMH8w7nfw
It doesn't sound like you were trying to launch a product, but doing an experiment and someone threw you under the HN-spotlight-bus :) Is this a "see what I can achieve with LLM coding" or is this "build this and see how much of the coding can be accepted from LLMs"?
> - Is ~300x faster than Postgres on analytical workloads. Right now it's 2x slower than Clickhouse on clickbench and I think it's possible to get faster than Clickhouse
That sounds like you are storing the data in a columnar format? Or do you do both row and columnar?
In a somewhat similar (yet also quite different) effort, I've been working on δx, a Postgres extension that compresses the data in a columnar format stored in normal Postgres tables (so replication, crash recovery, pg_dump, etc. still work normally). https://github.com/xataio/deltax
It is currently about 30-40% slower than ClickHouse (single node, ofc). The PR to add it to clickbench was just accepted, so you can see the comparison here: https://benchmark.clickhouse.com/#system=+liH|_etx|gQ|saB&ty...
Yep! The new version of pgrust supports batch based execution and a columnar format. I'm curious how you got δx to perform that well? From what I've seen a columnar layout only gets you part of the way and really good parallelism and really fast hash tables seem to make up a significant portion of why Clickhouse is faster.
Yeah, spent a lot of time on parallelism, vectorizing, pipelining, filter push-downs, bloom filters, all the tricks out there. It's really fun to make pretty steady progress on this.
pg_mooncake (now effectively abandoned due to being acquired by Databricks, but still up at https://github.com/Mooncake-Labs/pg_mooncake) pulled the DuckDB engine into Postgres wholesale, if I remember right.
pg_lake also uses DuckDB but keeps it external, routing through Postgres and managing Iceberg tables (but not the data itself) there (https://github.com/Snowflake-Labs/pg_lake).
Both of these were neck and neck with ClickHouse last time I tried them.
Actually δx is faster than the "duckdb embedded in postgres" options: https://benchmark.clickhouse.com/#system=+_etx|_b|_i)|dula|pnc&type=-&machine=-6t|ca2|6ax|g4e|6ale|3al&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=-
Plus all the normal Postgres features work as expected: physical/logical replication, crash recovery, pg_dump/pg_restore, etc.
That isn't what the data shows, but we don't need to discuss it further. My reply was to the person interested in learning from other Postgres OLAP designs. This stuff is all pretty immature though and I wouldn't actually build on it outside of some very narrow, well-understood workloads.
What was your methodology and structure in making the prompts for the rewrite? Did you let the LLM roam in all of the codebase and tests from the beginning, or revealed things to it gradually in some way?
> build a better version of Postgres
Faster is quantifiable. How do you measure better?
[flagged]
Are you fixing the heap and table management ?. Postgres does not use an undo log and manages all table updates directly in table storage which slows MVCC.
also have you told Ben Dicken ? https://x.com/BenjDicken/status/2074326407795417435
That's something I eventually want to fix. The challenge is the storage format is so integral to Postgres that it's going to be a huge PITA to come up with a novel design.
Right now OrioleDB is in beta. Once that becomes production ready, I'll evaluate incorporating it into pgrust.
For Ben Dicken, he has seen the project: https://x.com/BenjDicken/status/2074512043462603236. We're still working on all the novel features so I don't think it meets his bar quite yet.
best of luck to you, it will be great to see a rearchitected postgres.
While a thread-per-connection seems like an improvement, do you have any plans to allow query multiplexing over a single connection? That would be a huge improvement IMO.
Can you elaborate on the use case for query multiplexing? Is it so your client would only need to establish one connection with Postgres and then could run as many queries as it wanted?
Microsoft SQL Server has had a similar feature for a while -- Multiple Active Result Sets aka MARS. I don't have a good read on whether it actually helps any workloads. I've seen adapters that don't support it because of the extra complication.
https://learn.microsoft.com/en-us/sql/relational-databases/n...
Multiplexing would have a number of benefits. As you say, each client would only need a single connection regardless of the number of queries being sent. Resulting in:
On the client side, there is usually a local connection pool. When a burst of traffic comes in, the client needs to either wait for the pool to free up or establish a new connection, which adds latency. This latency hit wouldn’t occur with multiplexing.
With multiplexing, systems like pgbouncer would be unnecessary.
Also, even with a thread-per-connection, you can still quickly exhaust the servers resources when you have lots of connections because threads have a lot of overhead. Reducing the number of connections needed would greatly increase the number of clients that a database can serve.
At that point does it make sense to use http/3 to avoid head-of-line blocking on the network?
Not the OP, but yes - just imagine a web server talking to DB over one connection without any connection pooler
"Is 50% faster than Postgres on transaction workloads" - That is a very big claim! 50% faster on everything? Is it a strict improvement across the board or are there tradeoffs that make some workloads slower?
The 50% is specifically on percona-tpcc[0]. I got there through a mix of batching (postgres processes a row at a time), prefetching, and several handful of other optimizations.
This is great! Those analytical workloads numbers are mad - I'd love to see the benches, and I'm happy to contribute to some of the profiling.
How does your thread-per-connection model compare to Heikki's proposal[0][1] from back in 2023?
[0]: https://www.postgresql.org/message-id/31cc6df9-53fe-3cd9-af5... [1]: https://www.youtube.com/watch?v=xLLakMmVtbY
Rust actually made the change pretty simple. The main changes are:
I've started to see meaningful benefits by changing the parallel algorithms to use a shared memory space. For example parallel hash joins have to copy tuples through shared memory to pass them between workers. That's just not something I have to do.[flagged]
Super impressive! Is it possible for you to share your methodology of using LLMs?
My approach has changed throughout the course of this project. Throughout most of the project, we were working off of a c2rust translation of Postgres to Rust. That gave us a bunch of Rust code that was unsafe but did pass the Postgres test suite and was fast. c2rust had split Postgres into 1000 different crates. We then went through 1 by 1 and rewrote each crate into idiomatic rust.
This naturally lended itself to a suite of skills to describe how to rewrite a crate from unsafe rust to idiomatic rust. The main three skills I had were 1) a skill for identifying the next crates to port 2) a skill for rewriting a crate and 3) a skill for auditing a crate and making sure there weren't any outstanding issues.
My exact approach for managing subagents changed throughout the project. Initially I was doing parallel coding sessions with Conductor. After dynamic workflows came out, I used that as it was really easy to spin up dozens of parallel subagents and manage it from a single orchestrator. Over time I switched from using dynamic workflows to manually spinning up subagents from a central agent. The issue with dynamic workflows is they waterfall. Each step needs to finish before the next one starts. By manually spinning up subagents, I could have claude start porting a new crate as soon as a prior subagent finished.
I think I would be horrified looking at your Claude API bill.
Thanks for the insights, are the skills available anywhere?
Did you hand write the skills or did you have an agent audit your work and infer patterns?
Actually the inverse. I initially gave claude an outline of what I wanted, had it do some research into how to write idiomatic rust, and then had it draft a series of skills to do the work. I would then try out the skills, audit the results, and then give claude feedback based on what I was seeing. Once I started getting runs where the results were working, I would start to scale things up and audit things with an exponential backoff.
Thanks for sharing.
Is it being used in production anywhere, even if only a toy app?
I know you say it's not production ready and not optimized yet, but in the same breath - in your comment here - you say it's already faster.
It's not used in production. I've been using different benchmarks to compare the performance vs other systems. Namely sysbench-tpcc[0] and clickbench[1]
[0] https://github.com/Percona-Lab/sysbench-tpcc
[1] https://github.com/ClickHouse/ClickBench
I am super curious how you went about the port using LLMs. At $WORK we are looking to port code, preferably with LLMs, and it seems daunting, even with a test suite. Do you have an approach that works well for you?
Bun has an interesting blog post about how it was ported. It did cost a lot, much more than hiring people would cost outside USA.
PG Wire proto 3 is my largest source of frustrations.
I'm playing with a POC for a better wire protocol here: https://github.com/solidcoredata/pgwire4
404 Not Found
Now public.
How much of the performance gain is from using Rust, compared to using optimizations that are not done in the original PostgreSQL code (like using threads instead of processes, etc.)?
I am simply curious what the benefits of using Rust are in this instance.
Just a couple of ideas if you run out of backlog:) - proper versionnumber (64bit) - native json streaming. It would be awesome to get to the point where i could somehow redirect the sql output to the browser directly, but piping will do for now. The idea is to be able to stream rows to client without caching and building json along the way.
It's a completely new era of software production (I will no longer call it development) LLMs give us unlimited manpower, and the language give us constraints to make more modern and safer softwares. Love to see this rewrite in Rust, and expecting much much more in next few month.
Would you like to submit to ClickBench?
I can also do it if you would prefer...
Do you have anything in the regression test suite like jepsen etc?
Nothing major yet. Once I wrap up the performance work I'm doing I'll start looking at the best way to go about testing. I suspect there's a lot of novel things you can do with agents.
when doing rewrites like these, why isn't the first step to instrument the original code so that you would get very good automated test suites to point the LLM toward?
use both synthetic and real data to sample the internals of the original software to duplicate.
locate all the data transformation junctures, sample and then replicate the tranforms 1:1 in the rewrite.
That forces you not only in not the intentional good decisions of the past but also copies too many bad ones.
That…is really impressive. Well done!
i highly doubt you can make it faster than clickhouse, but happy to see it.
Is it your first rewrite/migration (with or without llm) ?
good luck nonetheless
300x is mostly a marketing term, especially without the test description.
BTW, showing no respect to what it is trying to copy looks uncomfortable.
This is how to LLM. Big ups, I wish the whole front page was stuff like this (and I think it'll happen).
Everyone is so worried about the value of commodity software going to zero. It's like, yeah, going into CS for the money always looked dumb to me, it's just not a good career path for that, you have to love it.
I am way more excited about a whole new class of stuff that obliterates the state of the art at every frontier.
Keep doing it legend.
Awesome work. I'd love to see you add something like kusto query language or pql. The autocomple on kusto, (which can be embedded into web apps) is really amazing.