brandur.org

Atoms

Multimedia particles in the style of a tweet, also serving as a changelog to consolidate changes elsewhere in the site. Cross-posted to an atom feed. Frequently off topic.

Published fragment Go’s bytes.Buffer vs. strings.Builder, on taking five minutes to write a benchmark so I know which of these I should be reaching for first.


Published my last fragment of the year, Postgres UUIDv7 + per-backend monotonicity, on how Postgres’ v7 UUIDs are made monotonic, and why that’s a great feature.


Published sequence 093, Eighty-Eight.

I’ve been hearing about Eighty-Eight for years, but didn’t visit until yesterday. Named for the year ‘88 when Calgary hosted the Olympic winter games, it’s two levels of taprooms with the brewery on site right next door. An 80s vibe pervades the space, complete with tube TVs (bunny ears open wide), boombox, and retro console. The special holiday theme is Home Alone. See the infamous Wet Bandit duo on screen here, but also with themed menu, Kevin McCallister traps visible around the room, and flying fox line soaring above the brewery’s enormous stainless steel vats.


Published fragment Stripe V2. How Stripe’s API got a new major version, and no one noticed.


I agree with this: AI-Generated Images Discourage Me From Reading Your Blog.

I have a growing hatred for AI-generated images in blogs. It makes me wonder if the text in the blog posts is AI-generated to some extent. It’s always disappointing seeing these images in blogs run by individuals. I expect this from corporate blogs but not indie blogs.

It might be a losing battle, but I don’t read content that I know is AI generated. It’s harder not to look at an AI-generated image, but I try my best there too.


Last week we ran into a product degradation where a user with an enormous number of tables was seeing timeouts trying to fetch a list of their cluster’s databases from one of our API endpoints. The endpoint had been using pg_database_size() to get sizes for each database, with these being used as a proxy for which were most active to determine the one that should take precedence in the UI.

I verified the problem locally by creating a database with a heck of a lot of tables:

$ createdb many_table_test
$ for i in {1..220000}; do
      psql postgres:///many_table_test -c "CREATE TABLE table_$i (id bigserial PRIMARY KEY)";
  done

And watching pg_database_size become degenerately slow:

# select pg_database_size('many_table_test');
  pg_database_size
------------------
        5180509331
(1 row)

Time: 22739.724 ms (00:22.740)

23 seconds to calculate the size of a database on a fast, local disk.

Taking a look at Postgres’ source, the problem is quickly apparent. Calculating database size involves descending through every one of its files on disk and adding them all up:

/* Return physical size of directory contents, or 0 if dir doesn't exist */
static int64
db_dir_size(const char *path)
{
    int64 dirsize = 0;
    ...

    while ((direntry = ReadDir(dirdesc, path)) != NULL)
    {
        if (stat(filename, &fst) < 0)
            ...
        dirsize += fst.st_size;
    }

    return dirsize;
}

Every table (all 200k+ of them) is a separate file:

$ psql river_test
river_test=# SELECT pg_relation_filepath('river_job');
 pg_relation_filepath
----------------------
 base/305728/305756

river_test=# SELECT pg_relation_filepath('river_leader');
 pg_relation_filepath
----------------------
 base/305728/305783

I patched the immediate problem by removing uses of pg_database_size and falling back instead to pg_stat_database, which includes a number of statistics that work as rough proxies for size/activity. I used xact_commit, the number of committed transactions. A call to pg_stat_reset() would reset the number, but in any active database it’d grow quickly again.


River Ruby gets same day support for Ruby 3.4. I like Ruby’s tradition of releasing on Christmas morning. It gives me a ten minute job (and ten minutes only) to feel like I did some coding for the day.


Publish fragment Go’s maximum time.Duration, on Avoiding overflows with Go’s time.Duration in the presence of exponential algorithms.


Published sequence 092, Tori Bar.

This one was new for me, Tori Bar in Calgary’s Inglewood, a tiny place serving Japanese yakitori off a single grill, the skewers cooked right in front of your eyes. We ended up ordering roughly 34 of the menu, I can heartily recommend the tako wasabi (octopus) starter, skin-on-thigh yakitori, pork belly, and shishamo (smelt, a tiny fish served well done).


Published sequence 091, Fair’s Fair.

I dropped by Fair’s Fair in Inglewood (Calgary) yesterday. It’s a large, rustic bookstore with bare bone furnishing, some real vintage items (in the sense of old rather expensive), and a big sci-fi/fantasy section. I’ve been coming to this place since I was a kid (when used book prices were measured in cents rather than dollars), and was glad that it’s the same Fair’s Fair in spirit as it was all the way back then.


Published fragment ERROR: invalid byte sequence for encoding UTF8: 0x00 (and what to do about it), on handling a common programming language/database asymmetry around tolerance of zero bytes.


Truth is treason in an empire of lies.

— Ron Paul


Like a lot of amateur photographers, I’ve had a fascination with fast lenses for quite some time. I only learned recently though that camera companies have been making very fast lenses as early as the 60s. See the Canon 50mm ƒ/0.95 “Dream Lens” for example.

While lenses in this vein don’t compare favorably with modern optics on any objective dimension like sharpness, distortion, vignetting, etc., they produce some really pretty bokeh/out-of-focus effects. These days, more artistic statement than pragmatic utility.

So far I’ve managed to restrain myself and have never bought an L-mount camera, but I’m tempted every time I see this sort of thing.


See: Minimalissimo: 2009 → 2024.

This is one of the first websites I ever added to my RSS reader, and I must’ve done so right around 2009. It was always one of the good netizens: no ads, popovers, or gimmicks, publishing full posts to RSS, and high quality, on topic content.

In a separate post on his blog (a gorgeous website by the way), Minimalissimo’s curator Carl Barenbrug talks about the site’s ascendant trajectory:

Between 2011 and 2015, minimalissimo.com was undoubtedly the most read and respected minimal design blog on the web. Sure, there were other massive publications that dwarfed our little site, but within our niche, no site offered the same level of consistent quality in curation. We were easily hitting over 100k unique visits to the site per month. At this point, we had accumulated over 6 years of posts spanning a wide variety of art, architecture, and design, so it felt like a natural step to evolve Minimalissimo into both a printed and digital publication. Particularly as print was thriving at the time. This then led to a trio of self-published magazines that each sold incredibly well. I’m still massively proud of those volumes and you can still get your hands on the digital versions today.

And in later years, challenges with an internet flooded with cheap content and ever more centralized:

By 2019, the volume of design blogs and magazines on the web was huge. Many began to look the same and we began to notice so much recycled content. Curation was becoming increasingly challenging if we wanted to maintain distinctiveness. On top of this, social media was very quickly eating away at indie websites like a plague. Our readership was declining year after year, and the pressures of pumping more energy into social media platforms to be noticed and relevant was a huge time sink. And it stunk. Much like the algorithms we’d all have to navigate in the years that followed. We were fighting a losing battle.


Published fragment the parallel test bundle, a Go convention that we’ve found effective for making subtests parallel-safe, keeping them DRY, and keeping code readable.

type testBundle struct {
    account *dbsqlc.Account
    svc     *playgroundTutorialService
    team    *dbsqlc.Team
    tx      db.Tx
}