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.

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
}

Hetzner is launching a new Object Storage storage on November 1st, aimed squarely at competing with S3 (and is S3 API compatible). Their pricing page is quite verbose, so here’s a summary by my interpretation:

Storage Transfer out Operations
AWS
  • $0.023/GB (tier up to 50 TB) = $23/TB/mo
  • First 100 GB: Free
  • After 100 GB: $0.09/GB (tier up to 10 TB) = $90/TB (!!!)
  • $0.005 per 1k PUT, COPY, POST, LIST requests
  • $0.0004 per 1k GET, SELECT requests
Hetzner
  • First TB: €5/mo = $5.44/TB/mo
  • After 1 TB: €0.0067/TB-hour = 31 * €0.0067 = €4.9848 =~ $5.42/TB/mo
  • First 1 TB: Free
  • After 1 TB: €1/TB = $1.09/TB
  • Free

Observations:

  • The price difference in storage is night and day, with Hetzner’s service less than 14 of the price of Amazon.
  • It’s even more stark for transfer out. At $90/TB, Amazon basically owns your data once it’s in their data center. Hetzner’s price is 1/90th AWS’ with a 10x more generous free tier.
  • No per-operation cost is huge. While reviewing my own S3 costs earlier this year, I was surprised to find that 85% of my bill was per-operation cost.
  • Hetzner storage price above assumes a 31 day month. It’s a bit cheaper for shorter months.
  • Hetzner storage prices are measured in TB, but fractional, and charged in increments of 100 MB.
  • Hetzner prices don’t include VAT. Hetzner’s VAT logic is incredibly complex, but you aren’t charged VAT in the USA unless you’re in Arizona, Colorado, New Mexico, Texas, or Utah.
  • On the other hand, Hetzner has a single eu-central region, so US-based customers will have to deal with speed-of-light latency.

On the face of it, pretty exciting. An optimistic premise of major cloud providers like AWS is that as they reduced their own operation costs through economies of scale, some of those savings would be passed down to us, but we’ve seen over the years that this rarely happens, even as the cost of hardware storage has steadily decreased.

I have to think that Hetzer’s prices are closer to what S3 would cost, were it to be launched today.


I’m demoing a new version manager. I’ve been on asdf for a few years. I often use IRB (Ruby’s interactive interpreter) for basic calculations since Ruby makes such a good scripting language. When doing so today, for about the 1,000th time, I got this:

$ irb
No version is set for command irb
Consider adding one of the following versions in your config file at
ruby 3.2.1

This sort of lazy error bothers me:

  • In the vast majority of situations, but especially when I’m just trying to Ruby to subtract one number from another, the exact version of Ruby I’m calling into doesn’t matter. I’d accept anything in the range of 2.0 to 3.3. If I have 3.2.1 installed, maybe just use that?
  • There’s nothing actionable. I’m not told what config file it’s talking about, or how to otherwise resolve the problem.

I was told the other week that Mise does a better job of default behavior and descriptive error messages, so I’m giving it a shot.

So far, so good. Easy installation and configuration in my ~/.zshrc. I asked it to install Ruby, and it just did it. No additional plugin needed to be installed, and no specific version was required:

$ mise install ruby
mise ruby@3.3.5 ✓ installed

When entering a directory with .tool-versions, it told me immediately that a tool was missing:

$ cd owl
mise missing: ruby@3.3.4

A simple mise install walked me through installing all dependencies, including some unusual ones like Crystal, and doing so interactively so I could skip any that I didn’t want:

$ mise install
mise ⚠️ crystal is a community-developed plugin – https://github.com/asdf-community/asdf-crystal
Would you like to install crystal? Yes
mise ⚠️ postgres is a community-developed plugin – https://github.com/smashedtoatoms/asdf-postgres
Would you like to install postgres? No
mise ruby@3.3.4 ✓ installed
mise crystal@1.6.2 ✓ installed                                                                                                                                            mise run with --yes to install plugin automatically
mise asdf plugin postgres is not installed
mise Run with --verbose or MISE_VERBOSE=1 for more information

Published fragment Rails World 2024, with a few reflections on this year’s event.


Published sequence 088, royal lion hunt.

I visited the British Museum in London during my stay there last year. The museum has a wealth of ancient artifacts, including some of the most famous ones in history like the Rosetta Stone, but despite having my camera with me, I took few photos while I was there. All I could think of was the tens of thousands of times each of these objects was photographed every day, contributing to an enormous body of billions of photos, 99.9999% would never be glanced again.

This is one of the few artifacts I photographed because I liked it so much. It’s artwork on stone depicting Assyrian royals taking part in a lion hunt, circa 645 BC, right around the period where the civilization would collapse.

At the time I knew almost nothing about Assyria, but a friend sent over the excellent episode “Empire of Iron” from Paul Cooper’s Fall of Civilizations podcast (also you YouTube). It starts describing how the Greek general Xenophon came across the ruins of two colossal cities as he was returning from a battle in 401 BC. We know now that these were the Assyrian cities Kalhu and Nineveh, but by then (about 200 years post-collapse) locals knew nothing about them, despite their far greater scope and sophistication than anything they could build at the time. It would’ve been like living amongst ancient ruins built by giants.



Published fragment TIL: Variables in custom VSCode snippets, on using built-in variables in VSCode snippets to make publishing to this site incrementally faster.


APacIa

Of mild interest, Stripe has announced a new API release process. Two named API versions a year will be released, named after plants (e.g. “acacia”), and presumably following an A-Z scheme similar to Ubuntu naming.

Previously, API changes roughly followed this procedure:

  • Non-breaking changes went out after they’d been reviewed and whenever they became available.
  • A new API version (named with only its date of release) was cut for breaking changes. If multiple breaking changes happily coincided around the same time, they’d be coordinated for simultaneous release.

By my reading, the new scheme seems to be largely the same, except that non-breaking changes would be held for a monthly release on the current version, and breaking changes would be held for up to six months

I suppose the benefit of the new approach is that it gives users a more predictable cadence for breaking changes. Optimistically, maybe it gets them in the habit of updating their API version twice a year. Even more optimistically, maybe it starts to pave the path for a format deprecation lifecycle so that ancient API versions could eventually be retired.


Published fragment A few secure, random bytes without pgcrypto, on avoiding the pgcrypto extension and its OpenSSL dependency by generating cryptographically secure randomness through gen_random_uuid().


Published Real World Performance Gains With Postgres 17 B-tree Bulk Scans, in which we benchmark one of our API endpoints and get a 30% throughput improvement, with 20% drop in response time.

As long as you make heavy use of eager loading (which every serious application does to remove N+1s), Postgres 17 looks to be one of these releases where all you have to do is upgrade, and reap a major performance gain for free.



Published fragment Direnv’s source_env, and how to manage project configuration, on how I accidentally stumbled across the source_env directive and dramatically improved my configuration methodology overnight.