HN user

brandur

3,354 karma

APIs + engineering @ Snowflake. Ex-Stripe. Ex-Heroku.

Twitter: https://twitter.com/brandur

GitHub: https://github.com/brandur

Website: https://brandur.org

Email: brandur@brandur.org

Posts54
Comments304
View on HN
brandur.org 1mo ago

The minimum viable unit of saleable software

brandur
217pts80
www.robinsloan.com 1mo ago

The fourth law (on AI-generated supercustomized email marketing)

brandur
3pts1
www.stainlessapi.com 2y ago

The Stainless SDK Generator

brandur
216pts63
www.crunchydata.com 2y ago

Postgres Goodies in Ruby on Rails 7.1

brandur
1pts0
www.crunchydata.com 2y ago

Understanding Postgres IOPS

brandur
8pts0
www.craigkerstiens.com 3y ago

Guidance for Scaling: Reversible vs. irreversible decisions (2022)

brandur
39pts4
www.crunchydata.com 3y ago

Will Postgres Use My Index? Hypothetical Indexing for Postgres

brandur
9pts0
docs.crunchybridge.com 4y ago

EIDs (Encoded IDs)

brandur
14pts7
blog.crunchydata.com 5y ago

Control Runaway Postgres Queries with Statement Timeout

brandur
2pts0
blog.crunchydata.com 5y ago

Simulating Update or Delete with Limit in Postgres: CTEs to the Rescue

brandur
3pts0
info.crunchydata.com 5y ago

Deep PostgreSQL Thoughts: Resistance to Containers Is Futile

brandur
3pts0
info.crunchydata.com 5y ago

Deep PostgreSQL Thoughts: The Linux Assassin

brandur
3pts0
info.crunchydata.com 5y ago

The Answer Is Postgres; the Question Is How?

brandur
19pts4
info.crunchydata.com 5y ago

R Predictive Analytics in Data Science Work Using PostgreSQL

brandur
7pts0
info.crunchydata.com 5y ago

Postgres: The Batteries Included Database

brandur
5pts0
info.crunchydata.com 5y ago

Crunchy Bridge: A modern, multi-cloud Postgres as a service

brandur
19pts5
brandur.org 6y ago

Actix Web: Optimization Amongst Optimizations

brandur
120pts14
www.citusdata.com 7y ago

\watch-ing Star Wars in Postgres

brandur
5pts0
www.citusdata.com 8y ago

Scalable incremental data aggregation on Postgres and Citus

brandur
65pts8
www.citusdata.com 8y ago

Fun with SQL: generate_series in Postgres

brandur
1pts0
www.linkedin.com 8y ago

Amazon Aurora Postgres: First Thoughts

brandur
100pts17
www.citusdata.com 8y ago

A step by step guide for building multi-tenant web applications with .NET Core

brandur
2pts0
brandur.org 9y ago

Building robustly with ACID databases, or why Postgres is often the right choice

brandur
3pts0
brandur.org 9y ago

Learning from Terminals to Design the Future of User Interfaces

brandur
211pts82
www.linusakesson.net 9y ago

The TTY demystified (2008)

brandur
2pts0
www.engadget.com 9y ago

2016 was a hard year to be an Apple fan

brandur
8pts1
www.economist.com 9y ago

It’s a-me: How Super Mario became a global cultural icon

brandur
117pts45
www.dnalounge.com 9y ago

Jwz: Webcasting Legally (2007)

brandur
1pts1
github.com 9y ago

Show HN: Redis-cell, an API rate limiter as a Redis module, written in Rust

brandur
15pts1
ticki.github.io 9y ago

Making Terminal Applications in Rust with Termion

brandur
1pts0

Thanks, this is neat. Goodreads seems to be get so little development attention these days that it's a miracle it still runs.

Interesting choice by BookWyrm to hide review ratings by default. I like it — I always felt that on Goodreads literature that's a little more challenging to tackle is reviewed very unfairly compared to easy-to-digest-but-content-light popular books or pulp fantasy. Making the default to have users read the contents of a review instead of glancing at an aggregate number seems like the right answer.

Blake wrote a nice page on the benefits of using transactional-based enqueuing here:

https://riverqueue.com/docs/transactional-enqueueing

It's true that it's not distributed, but there are a lot of benefits to not going distributed immediately, like extremely predictable data consistency. I would hazard to guess that the _vast_ majority of apps that are not built by the superscalers are already using a database like Postgres or SQLite to store their data, and River merely suggests that you hook your job queue into the database that you already have.

There's some similarities with DBOS for sure like using Postgres as a backend. Here's some differences based on my browsing of their docs for a little while:

* River's built around an entirely open core with its bread and butter being background jobs rather than workflows, with basic background jobs being good enough for most apps in most situations. It's true that workflows are gated behind Pro, but a lot of users will find they won't even need them, or won't need them until much later.

* River's aimed more solidly at Go, especially for the running of the background jobs themselves. Blake and I are both experienced Go developers, and we've gone through great pains to make the API as elegant as possible and as easy-to-use as possible, aiming for things like consistency and predictable + well-documented APIs. DBOS supports Go as well, but I believe our API compares very favorably [1], though you can be the judge.

* I might be missing something in the DBOS docs, but especially pertaining to background jobs, I believe River's feature set is quite a lot more comprehensive. e.g. Bulk insertion, unique jobs, periodic/cron jobs, job snoozing, job scheduling, unique jobs, test helpers, etc. We've tried to include everything that people would need when building out with background jobs, including all the edge cases.

Lastly, to be fair, DBOS is price-gated as well [2], and pricing is based on usage whereas River's is not.

[1] https://docs.dbos.dev/golang/programming-guide [2] https://dbos.dev/dbos-pricing

Yes, I roughly agree with all of this. In fact, for most of my existence, I'm been one of those cheap programmers.

The circumstances that led to me trying to push River for the next few months were somewhat accidental, and it felt like a good moment to at least make a go of trying to make it work. I'm not committing the rest of my career/life to any particular decision one way or the other.

I'll reiterate too that I believe we're still quite early in the LLM age and are still waiting for the other shoe to drop. All LLM-generated software feels free at the moment because it's still novel and the exhilaration of accomplishment when you build something complex inside of a few hours is addictive beyond words. However, within a year or two I think we're going to have a lot more software, all of which needs maintaining to some degree, and we're going to become a little more reluctant to generate new projects to add to the heap. This'll cause an adjustment back to a more compromise position.

(Also, could be completely wrong about all of that, so take it for what it is.)

To be fair I guess, the way the article is titled goes out of its way to mislead.

It would be quite easy to say "Added 911,000 fewer jobs from March 2024 to March 2025" or "the year starting in March 2024", but they are clearly aiming to deflect from the Biden admin by implying last year's revisions are the fault of the administration inaugurated in January 2025.

Judging by the comments here, it worked marvelously.

That's absolutely bananas. $3 million for a _report_ about suggestions for possible immigration reform (to speed it up of course). In the hole $62 billion a year, and a sizable chunk is going to overpriced MBA grads right out of school to produce PDF documents. How the hell do we reign these people in.

Are you my doppelganger? I made almost this exact comment word-for-word to a friend of mine a few weeks ago.

Trudeau's immigration video from December [1] was one of the most dishonest, condescending productions that I've ever seen, basically amounting to, "Yes, we destroyed our previous internationally respected immigration system and imported five million low skilled laborers over a couple years without adding any housing or infrastructure. Yes, that's hurt a lot of you and made you angry. No, we don't think that's a problem. But because you're so angsty, we'll throttle it back a tiny wittle bit over the next year or two before throwing the floodgates wide open again."

Not a shred of anything even resembling self-awareness or humility throughout.

---

[1] https://www.youtube.com/watch?v=vOB7-dbYuCc

Also notable that the author didn't link Haje's (the TechCrunch person who railed against Alexander Wang) actual writing because he knew it doesn't make either Haje, or his argument in favor of Haje, look good. So, here it is:

https://web.archive.org/web/20240628212036/https://techcrunc...

The Pulitzer Prize-worthy content in question:

I would invite him — and those supporting them — to fuck all the way off. You misunderstand me. You thought I wanted you to fuck only partially the way off. Please, read my lips. I was perfectly clear: Off you fuck. All the way. Remove head from ignorant ass, then fuck all the way off.

This is the quality of writing whose loss the author is arguing is why TechCrunch has lost its global relevance. A perfect inversion of the truth if I've ever seen one.

I found this by accident on YouTube the other day, and started watching it, not intending at all to sit through the 4+ hour runtime, but by the next day I'd watched the entire thing.

I'm not a Disney Parks person at all, but found the whole thing fascinating. Honestly, at first glance, this seems like such a fun idea, and given the popularity of other parks projects, I couldn't believe it'd failed. But digging in, it all kind of starts to fit together.

I didn't take notes, but off the top of my head:

* The stay was very expensive. You actually had to talk to an agent for specific pricing, but it was around $4k USD for two people for two days, with additional charges if you wanted to add people to your room.

* Despite luxury pricing, the rooms and furnishings are decidedly not. Tiny cabin-style rooms with bunk and pull out beds, and ~no amenities (no gym, no pool, etc.).

* The experience seems to have been a good idea, but executed poorly. This YT creator had trouble accessing any of the theoretically available storylines, and despite an effort to interact as much as possible ended up with results not discernible from random, or having done nothing at all.

* Lots of boring app-driven interactions involving messaging with virtual avatars of the IRL characters. On the day excursion to the rest of the Star Wars world, lots of of scanning QR codes on crates. The whole thing seems to have been very uninspired, and buggy to boot.

* When comparing the final product to concept art and statements from Disney execs, it seems like a lot of corners were cut for the final product and it ended up very underwhelming compared to what it was supposed to be. There seems to be lots of evidence elsewhere that Disney parks have really become cheapskate / penny pinching operations, and a reasonable hypothesis is that there were a lot of big ideas, but they would've been expensive, and they were slowly cut back one by one until the overall product really had nothing special left, and fell flat.

* From Disney's end, it didn't seem to scale very well. The high price tags seems to be related to the fact that the hotel didn't have all that many rooms compared to larger resorts which might have thousands, so they felt that they had to recoup the cost from each one. This didn't sit well with customers though, and after the initial opening period, the hotel seemed to be operating at high vacancy rate because few people were able/willing to pay the very substantial premium.

* Perhaps the biggest reason for its abrupt closure is due to accountants. Disney will be taking up to a $300 million tax write off from the project, so its closure may have been even a neutral or even good thing to its execs. The closure was extremely abrupt, which seems to suggest that they wanted to take the write off for the prior fiscal year specifically.

They'll get lost when using a non-blocking send with select/default:

    messages := make(chan string)
    select {
    case messages <- "hi":
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }
The reason you'd use a non-blocking send is to make sure that in the event of one slow consumer that the entire system doesn't slow down.

Imagine a scaled out version of the notifier in which it's listening on hundreds of topics and receiving thousands of notifications. Each notification is received one-by-one using something like Pgx's `ListenForNotification`, and then distributed via channel to subscriptions that were listening for it.

In the case of a blocking send without `default`, one slow consumer that was taking too much time to receive and process its notifications would cause a build up of all other notifications the notifier's supposed to send, so one bad actor would have the effect of degrading the time-to-receive for all listening components.

With buffered channels, a poorly written consumer could still drop messages for itself, which isn't optimal (it should be fixed), but all other consumers will still receive theirs promptly. Overall preferable to the alternative.

In Postgres listen/notify are inherently lossy channels — if a notification goes out while a listener wasn't around to receive it, it's gone, so they should never be relied upon in cases where data consistency is at stake.

I find that the main thing they're useful for is notifying on particular changes so that components that care about them can decrease the time until they process those changes, and without sitting in a hot loop constantly polling tables.

For example, I wrote a piece here [1] describe how we use the notifier to listen for feature flag changes so that each running program can update its flag cache. Those programs could be sitting in loops reloading flags once a second looking for changes, but it's wasteful and puts unnecessary load on the database. Instead, each listens for notifications indicating that some flag state changed, then reloads its flag cache. They also reload every X seconds so that some periodic synchronization happens in case an update notification was missed (e.g. a notifier temporarily dropped offline).

Job queues are another example. You'll still be using `SKIP LOCKED` to select jobs to work, but listen/notify makes it faster to find out that a new job became available.

[1] https://brandur.org/fragments/instant-feature-flags

Author here. The Go channel send behavior could certainly be altered depending on the particular semantics of the application, but the reason I chose to use a non-blocking buffered channel is so that no particular subcomponent can slow down the distribution of notifications for everybody.

Shouldn't the channel rather block than discard if full?

In Go, a blocking channel is one that's initialized without a size (see [1]). You could have a blocking channel where the sender uses a `select/default` to discard after it's full, but that leaves very little margin of error for the receiver. If it's still processing message 1, and then message 2 comes in and the notifier tries to send it, message 2 is gone.

IMO, better to use a buffered channel with some leeway in terms of size, and then write receivers in such a way that they clear incoming messages as soon as possible. i.e. If messages are expected to take time to process, the receiver spins up a goroutine to do so, or has another internal queue of its own where they're placed so that new messages from the notifier never get dropped.

---

[1] https://gobyexample.com/channels

Author here. The behavior of notify with respect to transactions is indeed notable, and definitely a great feature that makes them distinct from pub/sub in other systems. Notifies fire only when data is ready after the transaction is committed, and they're also deduplicated based on payload so listeners don't have to react to many of the same message unnecessarily.

That said, NOTIFY isn't really what this post is about. It concerns itself with the other half of listen/notify by describing a "notifier" pattern, one which listens via `LISTEN` statements and distributes them application subcomponents to help maximize economy around the use of Postgres connections.

Listen does hold a connection, but that doesn't mean it defeats connection pooling.

That's what I was trying to convey in this blog post: you'll keep a fixed number of connections open for use with listen, but as few as possible by reusing a single connection per program to simultaneously listen on all channels that your application cares about (with the notifier distributing messages to each internal component that subscribed).

With your dedicated listen connections accounted for, the rest of the connection pool can operate normally, with programs checking connections in and out only as long as they need them.

So the net-net is that you have a handful of connections dedicated for listen, and the remaining ~hundreds are part of the connection pool for shared use.

That's interesting — the typed request and response objects that you get with an API SDK are certainly one of the biggest upsides, and I certainly see the benefit of having full control of the transport layer yourself so that you can add any common telemetry / logging / statistics at your leisure.

On the other hand, a benefit of a more complete API is that in typed languages you can tab-complete your way to success. With each endpoint a function and all the requisite configuration (API URL, etc.) bundled in, for basic integrations you may never even have to reference documentation, or if you do, very little of it, as your IDE finds functions/properties for you and you can read documentation right out their docstrings.

I helped maintain the public API language bindings at Stripe for many years, and although I'd defend it to the death as the right DX (SDKs are so much easier to use than raw web APIs), it's hard to appreciate just how onerous of a process this was before Alex pioneered a codegen-based solution.

Some of the language SDKs were dynamic (e.g. Ruby, Python) and could adapt automatically to small API changes like if a new field was added in a response. Some were not (e.g. Java, Go), and for every API change, someone had to manually make the change to that SDK's codebase (add a field, add a struct, add a function for a new endpoint), get it reviewed and merged, and cut a release. As Stripe got bigger and there were API changes all the time, the only way this was even remotely functional was that we had a couple heroic workhorses that'd watch for changes and open hundreds of PRs a year for them. Honestly, in retrospect, it's amazing this even worked.

Getting everything switched over to a generated solution was an arduous process because the new generated code had to be API-compatible with the existing code so that the cutover to generated bindings didn't break every Stripe user under the sun. We eventually got there, but it took a long time.

I like Stainless' mission because after seeing the crazy maintenance hassle that all of this was at Stripe, I think it makes way more sense to save all that engineering time for your more concerns, and outsource this problem to someone else. A plug-and-play way of getting high quality SDKs in all common languages that get pushed to appropriate repositories for distribution and comes with quality companion documentation.

We've actually had pretty good luck at my current job with open source tools like openapi-generator [1], but this sort of codegen is so convoluted that the sustainability of a pure open source solution makes me a little afraid, and you still end up doing a lot of the last mile work yourself.

---

[1] https://github.com/OpenAPITools/openapi-generator

There's a lot to be said about the correctness benefits of a transactional model.

The trouble with hitting an HTTP API to queue a task is: what if it fails, or what if you're not sure about whether it failed? You can continue to retry in-band (although there's a definite latency disadvantage to doing so), but say you eventually give up, you can't be sure that no jobs were queued which you didn't get a proper ack for. In practice, this leads to a lot of uncertainty around the edges, and operators having to reconcile things manually.

There's definite scaling benefits to throwing tasks into Google's limitless compute power, but there's a lot of cases where a smaller, more correct queue is plenty of power, especially where Postgres is already the database of choice.

Author here.

Wanting to offload heavy work to a background job is absolute as old of a best practice as exists in modern software engineering.

This is especially important for the kind of API and/or web development that a large number of people on this site are involved in. By offloading expensive work, you take that work out-of-band of the request that generated it, making that request faster and providing a far superior user experience.

Example: User sign-up where you want to send a verification email. Talking to a foreign API like Mailgun might be a 100 ms to multisecond (worst case scenario) operation — why make the user wait on that? Instead, send it to the background, and give them a tight < 100 ms sign up experience that's so fast that for all intents and purposes, it feels instant.

There's a little bit of editorializing in the title, but Garry's basic contention is that YC is firing on all cylinders, and it's headquartered in SF:

Jamali: It’s kind of interesting — so that narrative is there, but are you saying at the same time that there is this comeback happening, that maybe a lot of people outside of San Francisco haven’t appreciated? A comeback that’s being driven by the tech industry?

Tan: Yeah, at the end of the day, what we want is prosperity. And the coolest thing in the world is that at Y Combinator, we get to see two or three people come together from any background, from any country in the world, and they have a fair shot here. They get half a million dollars, and they can go and try to create something that touches a billion people. And that’s really what we try to do every day. And if they succeed, they’ll have thousands of employees, and these are good, high-paying jobs in tech. And that will actually create so much prosperity for the whole community, that that’s actually why San Francisco is so awesome.

Not so much in the interview, but a common claim is that many of the most important AI companies like OpenAI and Anthropic are located in the city, so there's a bit of an AI boom going on.

For general context though, Garry's part of the crowd that's committed to staying in San Francisco and getting involved in local politics to make the city better. Whether or not you agree, he's definitely going to have an intentional pro-SF bias in general, which he'd acknowledge.

Actually, who am I kidding- some other interest group would probably take their place.

This is it unfortunately.

After observing the SF NIMBY mentality for many, many years, you start to see the matrix. It has nothing to do with gentrification, inclusion, environmentalism, or any of the other purported values. It's about one thing: stasis. Keeping SF the same at all costs, and making sure no new people come here.

San Franciscans favorite subject right now is talking about how downtown commercial space could be converted to housing, but make no mistake, the second anyone actually tried to do that, the backslash would be powerful and instantaneous. (And it's already so impossible to do between zoning and regulation that it doesn't even need backlash to already be infeasible.)

It's also the domain used for releases and other artifacts (after a redirect from github.com). There's going to be a lot of broken builds today:

    $ curl -i -L https://github.com/kyleconroy/sqlc/releases/download/v1.17.0/sqlc_1.17.0_linux_amd64.tar.gz
    HTTP/2 302
    server: GitHub.com
    date: Fri, 24 Mar 2023 20:51:56 GMT
    content-type: text/html; charset=utf-8
    location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/193160679/09048595-c7f4-45b5-858a-7f55baa2fd7d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230324%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230324T205156Z&X-Amz-Expires=300&X-Amz-Signature=772d0aa8c5c19b0a5ef84d718d2faf0d81f24b224a4ef634d2410787e8f50bad&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=193160679&response-content-disposition=attachment%3B%20filename%3Dsqlc_1.17.0_linux_amd64.tar.gz&response-content-type=application%2Foctet-stream

    curl: (60) SSL certificate problem: certificate has expired
    More details here: https://curl.se/docs/sslcerts.html

    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. To learn more about this situation and
    how to fix it, please visit the web page mentioned above.
> What are the odds this happens the same day they rotate their SSH keys?

Definitely a bad for them. When it rains, it pours.

I wonder if they might be amenable on that one. (If any GitHub staff is reading this.)

The proposed scheme is fairly similar to Stripe's, and although Stripe mainly uses the `Stripe-Version` header for versioning, its API has also always allowed you to alternatively send a `?_stripe_version=<version>` query parameter which it'll respect.

Stripe's API implementation definitely had its share of legacy cruft that added up and made things more difficult to maintain, but out of all of it, I don't remember having to support that alternative `_stripe_version` parameter ever really being a maintenance headache. The leading underscore made sure that even as the API expanded, it never accidentally collided with parameters on any other endpoint.

To be fair: (1) it seems like the update landed recently, and (2) the article you linked does cite the same reasoning as the Twitter posts. Even if was a little ahead of the more recent Zero Covid protests, it seems like AirDrop's been a known vector in China to share government criticism, which is why Apple's cracking down.

Let Grow keeps a good mythbusting page on crime stats:

https://letgrow.org/crime-statistics/

The tl;dr is that helicopter parenting is relatively new — only something that's come into play in the last few decades — and in all the time before that kids had far more freedom to roam compared to today. Even factoring in an uptick in the murder rate in the last couple years that came about during Covid policy, it's still half of what is was in the early 90s or prior to that.

It seems like the world is more dangerous now because of the outsized attention crime gets in media, but objectively speaking, it's not.

And keeping kids at home on their screens is having definite negative consequences on their maturation and later ability to succeed in the world. Here's a good article on the subject:

https://reason.com/2017/10/26/the-fragile-generation/