HN user

malisper

2,988 karma

Hi! I'm Michael Malis, a founder of freshpaint.io (YC S19). Freshpaint makes your marketing and analytics stack HIPAA compliant.

I previously lead the database team at Heap.

GitHub: https://github.com/malisper/

Blog: http://malisper.me

Email: michaelmalis2@gmail.com

Posts61
Comments418
View on HN
github.com 27d ago

Show HN: Pgrust, Postgres in Rust (passing 100% of Postgres regression tests)

malisper
6pts1
github.com 3y ago

Get Code Reviews from ChatGPT

malisper
11pts1
www.freshpaint.io 4y ago

GitHub Copilot and the Methods of Rationality

malisper
5pts2
www.freshpaint.io 5y ago

What Is Snowflake and Why Is Everyone Excited About It?

malisper
2pts0
www.freshpaint.io 5y ago

What Is Snowflake and Why Is It Exciting?

malisper
6pts1
www.freshpaint.io 5y ago

Web Analytics Company, RudderStack, Accidentally Collecting Passwords

malisper
4pts0
www.freshpaint.io 5y ago

Why is Snowflake so Valuable?

malisper
247pts163
www.freshpaint.io 5y ago

Raising a Seed Round During Covid-19

malisper
2pts0
www.freshpaint.io 5y ago

Anatomy of a Seed Round During Covid-19

malisper
2pts0
www.freshpaint.io 6y ago

Building a Live Event View: In Theory and in Practice

malisper
1pts0
malisper.me 6y ago

An Algorithm for Passing Programming Interviews

malisper
3pts0
news.ycombinator.com 6y ago

Launch HN: Freshpaint (YC S19) – an automated, retroactive Segment alternative

malisper
119pts55
malisper.me 6y ago

Tools in My Toolbox

malisper
1pts0
malisper.me 6y ago

My Approach to Getting Dramatically Better as a Programmer

malisper
2pts0
www.freshpaint.io 6y ago

How to Find Anyone's Email Address in Seconds

malisper
3pts0
www.freshpaint.io 6y ago

How to Find Anyone's Email Address in Seconds

malisper
2pts0
news.ycombinator.com 6y ago

Launch HN: Freshpaint (YC S19) – Automated, Retroactive Alternative to Segment

malisper
57pts44
www.freshpaint.io 6y ago

Reflections on Building Freshpaint

malisper
2pts0
www.youtube.com 7y ago

Writing an Interpreter in SQL for Fun and No Profit [video]

malisper
55pts11
perfalytics.com 7y ago

Show HN: Perfalytics – Postgres Performance Made Easy

malisper
2pts1
malisper.me 7y ago

Generating Fractals with Postgres: Self-Similar Fractals

malisper
1pts0
malisper.me 7y ago

Generating Fractals with Postgres: Escape-Time Fractals

malisper
69pts8
malisper.me 7y ago

Generating Fractals with SQL Queries

malisper
1pts0
stripe.com 7y ago

Bring Your Own Team Update: BYOT Is Being Sunset (2017)

malisper
1pts1
malisper.me 8y ago

My Approach to Getting Dramatically Better as a Programmer

malisper
2pts0
heapanalytics.com 8y ago

Testing Database Changes the Right Way

malisper
1pts0
blog.heapanalytics.com 8y ago

Analyzing Millions of Postgres Query Plans

malisper
1pts0
heap.engineering 8y ago

Analyzing Millions of SQL Queries When Each One Is a Special Snowflake

malisper
1pts0
malisper.me 8y ago

There Is More to Programming Than Programming Languages

malisper
2pts0
malisper.me 8y ago

How to Get a Programming Job Straight Out of High School

malisper
2pts0

When I've dealt with this I've generally made sure the transactions are updating rows in a consistent order. You can do that by sorting the rows before you update them

use set seqscan = off when testing your query plans esp when tables are empty or nearly so so you can see if indexes will be used when seq scans become less cheap

How well does this work for you? I thought if you have _any_ index, Postgres will use it if you disabled sequential scans. Diabling sequential scans won't tell if you if you have the right index

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.

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.

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

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.

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.

The rewrite also could have simpler code in some cases

The Rust code is a literal translation of the Postgres code which returns the value at the end instead of an early return.

I see a lot of MemoryContext

MemoryContext in C is used for multiple reasons: 1) performance 2) keeping track of how much memory has been allocated and where and 3) preventing memory leaks.

Reasons 1 and 2 are still relevant for Rust. The challenge is in C memory contexts are stored in a global variable. Global variables don't work well with the rust borrow checker so I opted for passing memory contexts as function arguments instead.

Rust actually made the change pretty simple. The main changes are:

  - Use thread local variables
  - Move everything from shared memory to process memory
  - Use threads instead of processes
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.

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 can confirm a naive rewrite won't make things faster. I've been working on rewriting Postgres in Rust. I rewrote things function by function similar to how Jarred did. Even though the new Rust code mapped closely with the previous C code, it was 8x slower. This was due to myriad of reasons. For example naively converting a C union into a Rust enum can be slower because Rust stores a tag with the enum, while C unions do not.

I've been working on a new rewrite that's focused on beating Postgres on performance. As of this morning I got to 100% of the tests passing and have meaningful performance gains over Postgres.

Maybe the takeaway is that 20% is about all the LLM can muster

At this point there's a long list of projects that have used LLMs to rewrite a system in Rust including:

  - Bun (https://github.com/oven-sh/bun/pull/30412)
  - Valkey (https://github.com/ianm199/valdr)
  - Git (https://github.com/gitbutlerapp/grit)
  - Postgres (https://github.com/malisper/pgrust)
With the exception of Bun, these projects were done pre-fable too, so I bet Fable will make these types of rewrites even easier.

But LLMs are great at this

Not really. The latency for an llm to make a single change is on the order of seconds. If in order to change a single function from unsafe Rust to safe rust requires thousands of changes, it will take hours to refactor a single function.

I made a comment about this yesterday[0], but there's been a massive increase in people migrating from C to Rust due to LLMs.

In contrast to what the C to Rust migration book is recommending (using FFI to integrate Rust with C), I've found it much easier to start from scratch. I recently finished a project where I rewrote Postgres in Rust[1]. For context, Postgres is about one million lines of C code.

On one attempt, I tried using c2rust to convert Postgres into unsafe Rust code. That attempt succeeded in terms of getting working "Rust" code, but any attempt to change any piece to safe rust, would require thousands of changes across codebase. Even though I had working Rust code, I found it infeasible to get to working idiomatic Rust code.

Instead what I found to be more effective was starting a new codebase and rewrite each file from the Postgres codebase into Rust one at a time. This allowed me to guarantee that at all times the new codebase was idiomatic and simultaneously I could make one pass over the Postgres codebase to get working idiomatic Rust.

YMMV, but I found it way easier to generate a whole new codebase from scratch rather than incrementally rewrite an existing codebase.

[0] https://news.ycombinator.com/item?id=48738985#48739882

[1] https://github.com/malisper/pgrust

A meta-trend I find interesting is there's a lot of projects using AI to rewrite existing systems in new programming languages. Most often in Rust.

  1. Bun rewritten in Rust
  2. Flow rewritten in Rust
  3. The react compiler was rewritten in Rust
  4. Grit is a new implementation of Git in Rust
  5. I've made my own rust rewrite of postgres that passes 100% of the regression and isolation tests[0][1]
I think AI changed the economics of these projects even more than it has the economics for software engineering work in general. Though direct AI code translation is usually slop for me.

One of the many things I did to deal with this was an audit skill that would:

  1. Find a small chunk of code to rewrite
  2. Have a list of things that it was looking for in each piece of code that's being rewritten
  3. Place that next to the code being translated
  4. If that document didn't exist and/or didn't say the code was passing the audit, code wouldn't be merged
  5. As I found problems and anti-patterns I would add those to the skill over time
This by itself still let a lot of slop slip through, but also preemptively caught a ton of issues as part of my overall process.

Complicated old "boring" infra software might actually be the most AI-rewriteable code right now

[0] https://pgrust.com

[1] https://github.com/malisper/pgrust