HNHacker News
TopNewBestAskShowJobs

unscaled

2,592 karma · joined January 7, 2015

submissionscomments
unscaled··on Don't couple your Go code to GitHub
> One of the good features of Go is that you namespace your code with the location to fetch the code.

Then the rest of the article explains why this is NOT a good feature in practice.

I don't think it's unworkable either, but this is one of these little thing that Go decided to do different and convinced its fans that this is a great idea and all the other languages where doing it wrong. After a couple of road bumps appeared, instead of admitting there are some advantages to having official package names, we're now told that everybody should just set up their own custom domain with an nginx server or a Go Vanity URLs forwarder to serve traffic for their GitHub-hosted packages.

unscaled··on Go Concurrency Distilled
Managing channels and making sure they are closed just once is quite messy compared to other languages. The Go channel axioms[1] don't make much sense: why does closing a channel multiple times panic, but reading from a closed channel returns a zero value?

Kotlin gets this right. On send/receive, you can use trySend or tryReceive if you want to avoid exceptions. Considering Kotlin also has coroutines and structured concurrency, concurrency in Kotlin feels more ergonomic to me than Go. At least if you want to get concurrent code with least amount of bugs and not just least amount of extra keywords.

[1] https://dave.cheney.net/2014/03/19/channel-axioms

unscaled··on SAML: A fractal of bad design
XML did not become more complex following 2002. DTD, Namespaces, Comments, processing instructions, CData, attribute vs. child dichotomy - all of these things exited from day 0.

XML Schema (just like JSON schema) did not. When we're talking about a "simple format" in terms of security we are talking about parsing footguns like these features, not about complexities that may exist with any wire format such as schema and data validation.

Were there any better formats for simple messages back in 2002?

I would argue that yes.

1. ASN.1 is horribly complex, but PKCS #7 (Cryptographic Message Syntax, a.k.a. CMS) was already established, and it was better than XML Signatures in one regard: it did not try to fuse the signed content with its envelope. But this probably wasn't he best choice.

2. If all you needed is a bag of keys and values, you could easily go with an RFC 822 style header + values format (which worked well for both email protocols and HTML). As a bonus S/MIME was already well established at this point, so you had an obvious way to sign this data.

3. Simple binary formats like XDR (used by NFS) or simple textual formats like LDIF or the RFC 822 header format mentioned above could be freely combined with any existing signature protocol that did not mandate structured data (i.e. every signature protocol in existence before XML Signature came in).

4. An even better approach of course, would have been to say no to fine-grained cryptographic agility[1]. That was a terrible mistake, but it was the default design choice back in 2002, and it's hard to single out OASIS. The JOSE/JWT authors should have known better though. You could have define a couple of ciphersuites/version, and a simple, fixed signature protocol for each version.

5. The best approach would have been to just avoid signatures completely and require TLS, but this wasn't viable back in 2002. Even OAuth 1.0 and OpenID 1.0, which came out several years later, included their own cryptographic signatures, but their schemes were still far simpler than SAML.

I think the key takeaway today is that SAML is no longer necessary today. TLS is not an option for secure web resources. There are no features of SAML that cannot be supported by OAuth or OIDC (only security misfeatures). All IdPs and most products support OIDC. In fact, OIDC is probably more well-supported than SAML.

SAML is only used because of enterprise inertia and self-inflicted FUD. As a professional, we should treat SAML with the the same disdain we've directed we've directed towards Internet Explorer 6. This is an insecure legacy technology that presents a drag on the entire industry.

---

[1] https://www.blockchaincommons.com/musings/musings-agility/

unscaled··on SAML: A fractal of bad design
OIDC spec has a limited third-party initiated flow[1] that redirects to the RP (equivalent to SP in SAML), which then starts a regular OIDC flow that is indistinguishable from an RP-initiated flow.

This flow is safe against CSRF, since no authentication data is carried together with the flow. The only other safety issue I can see is using target_link_uri for CSF. The spec clearly mentions that this URL has to be filtered or ignored by the RP.

Instead of treating the (horrible) technical implementation as a feature, it addresses the real user-facing feature: I want to be able to click on a link on my IdP dashboard and be redirected to the app. The login would still be seamless, since you already have an SSO session active with the IdP.

[1] https://openid.net/specs/openid-connect-core-1_0.html#ThirdP...

unscaled··on SAML: A fractal of bad design
What is prventing OIDC-based IdP from initiating login? You just need to know the login URI:

https://openid.net/specs/openid-connect-core-1_0.html#ThirdP...

unscaled··on SAML: A fractal of bad design
There is nothing left from the OpenID 1.0 and 2.0 protocol in OpenID Connect, except for the core features (identity federation, identity information encoding and updates).

OpenID Connect is basically OpenID reimagined on top of OAuth 2.0, with using JWT for the identity data.

The core OpenID Connect spec is still purely a federated identity standard. There are many other standards pushed by the OpenID foundation, but OpenID Connect itself was never meant to be anything more. The main feature differentiator from the OG OpenID is that it's built on top of OAuth, so you can add other features supported by OAuth (like authorization) ad-hoc.

unscaled··on SAML: A fractal of bad design
JWT has two out of the three issues mentioned above:

1. It has the ill-conceived "alg": "none". But this feature made breaking JWT so easy and low stakes, that many libraries have removed this feature completely, or just disabled it by default.

Modern RFCs that mandate JWT use in servers (e.g. RFC 9068) often explicitly forbid this, and the latest BCP for JWT (RFC 8725) recommends that libraries only accept or generate tokens with "none" when the user _explicitly_ requests that. And yet, we're still seeing "alg": "none" vulnerabilities even to this day. I'm not sure if it made sense to support "alg": "none" in the first place, but if we ended up doing that, the RFC should have been much more strict about this.

2. The other issue is mixing up asymmetric and symmetric encryption. You can't embed the HMAC password directly in the user-generated message; but if the library is not built securely, it would just treat the public key itself as the HMAC key when it gets an HMAC alg in the header. This makes forging tokens quite trivial if the library is misconfigured.

JWT is not nearly as bad SAML and its designers learned some important lessons (simpler base format, no canonicalization or embedded signatures), but this is still a design-by-committee standard that didn't properly involve. The full JOSE standard (including JWA) is even worse, but fortunately JWA doesn't get used a lot.

unscaled··on Java 27
Have you worked on large (>500KLOC) codebases with an agent?

Yes. But keep in mind KLOCs are not easily comparable across languages. Java is notoriously verbose. A 500KLOC codebase in Java would usually be half that size in Rust. If your argument is that large codebases makes life harder for agents, you should go with a less verbose language.

I'm not sure what "manual optimization" means (isn't it a bit of an oxymoron when the agent does it?), but if your agent has the proper tools (e.g. ast-grep, rg, semble) it can deal with large codebases. Would the agent create slop? Yes. But it wouldn't be worse on the slop that humans created on every moderately-sized Java project I've worked on.

> in fact, agent-written code in a low-level language gets pretty slow well below that size

I've never seen this happening. I've seen agents writing suboptimal Rust code (e.g. copies instead of Cow). But while this occassionally happens with Rust, I've never seen an agent optimizing for Java where necessary (e.g. using object pools to avoid GC churn). Java is not magic.

unscaled··on Java 27
I think Java explicitly refused to take some of the best features of Kotlin, like extension methods, context parameters and operator overloading.
unscaled··on Java 27
> Most organizations that use Java tend to be pretty conservative with their technology picks

That part i s true.

> with newer Java versions the only real gap with Kotlin is null-safety

But that part isn't. Kotlin has:

- Structured Concurrency: Coming to Java sometime in the future, but it's been in preview for very long now.

- Standalone functions that don't have to live in classes

- Properties

- Property delegation

- Data classes: more powerful than records. Can be used for large DTOs that you can modify with copy(). Java needs something like Lombok to make records more useful.

- Extension methods

- Context parameters

- Operator overloading

- Implementation delegation

- Inline functions (which can receive returning closures and reified types)

- Block syntax (supports `it` for unnamed arguments)

- Sequence abstractions: more powerful and more efficient than Java streams due to the inlining and block syntax.

This is just a partial list, but Kotlin clearly has a lot of things that Java doesn't. If you only personally care about NPEs that's fine, but that's not the only thing.

unscaled··on Java 27
> Hiring developers is easy

> And that "team" nowadays may also consist of many AI agents.

And that's the part where the hireability arguments collapse. Sure Claude Code works pretty well with Java. It also works well with Typescript, Python, Go and Rust. It would use types on all of these languages, and run a type checker or LSP on the dynamic ones. And while Java is statically typed, Rust has a stricter type system that prevents some types of runtime bugs that Java's type system won't like data races and forgetting to release a resource.

unscaled··on Java 27
Sure it does:

https://github.com/uber-go/nilaway

Not exactly the same solution as JSpecify, since it doesn't rely on annotations, but it's also more ergonomic.

I'm not comparing this to "null-restricted types", since that's a draft JEP that hasn't made it even into a preview feature. Go also had multiple proposals for explicit nilability in types, and while they probably have less prospect of ever seeing the light of day compared to Project Valhalla, as things currently stand, Go is in the same position as Java: They are both extremely prone to NEPs out-of-the-box and they both have external tooling that can help you avoid them.

Java null checkers have more comprehensive coverage potential compared to Go, but Go is the more ergonomic one here. You don't need a single extra annotation on your code.

unscaled··on Java 27
Go panics.
unscaled··on Java 27
Congratulations. You've added another build tool and sprinkled your code with ugly annotations and ifs and Optional Optional.of(x).map(y) all over the place to get the same thing you'd get by moving to Kotlin.

I get it why this seems like a less drastic change, but this saddens me. Kotlin solves more issues with the type system (smart casts, reified types, immutability by default), without sacrificing readability. Unless I can see a solution in Java that makes dealing with NPEs as easier for lazy developers as ignoring them, I don't consider it a solved issue.

unscaled··on Java 27
While Java can outperform Go in some cases, the situation is very much the opposite when it comes to Rust.

I also don't see the case for stability. Yes, if you're still on JDK 8, it would probably chug on for a couple of years. But we were talking about greenfield projects and newer JDK go EOL much faster. If you want patches, you'll have to run your app to a newer JDK, which may break a couple of things. Rust (within the same edition) or Go (within the same major version) break less than that.

As far as runtime compatibility goes, Rust and Go apps ship with the runtime. This can be better or worse for you, depending on what is your upgrade story, but I don't see a clear winner here. What I would give to Java over Rust is that you will have far fewer dependencies to take care of if you need to upgrade. But the same goes for Go.

For observability, I feel that with Rust you have a bit less that you need to observe (no GC to worry about). Tokio tracing is great, but observability requires a bit more effort. The go observability story is far worse. So Java probably has an edge here, but not something that ever felt like a game changer. My impression is that for most of the enterprise shops that love Java, observability means collecting unstructured log files through NFS and trying to find a needle in the haystack with primitive tools, but I've been out of touch with this world for a couple of years.

Productivity is something that is dead if you are AI-heavy. Sure, many shops are still wary about AI, and I totally get why, but this is a battle that's already been lost. Without AI, I would say I was about 3 to 4 times more productive in Rust than I was in Java, but ramping up that productivity took at least 1 year of practice. It's not time most companies are willing to spend. With AI, this doesn't matter anymore, for better or worse.

I'm not arguing that Java is not chosen often for greenfield projects. It's clearly extremely popular in many circles, especially outside startups and big tech. But I think the reason Java is chosen have little to do with the reasons you've mentioned above and more with organizational preferences.

unscaled··on Java 27
I'm not sure what enterprise-level collaboration means. In my experience, "enterprise" usually means: "Let's use tools that are 10 years behind, buggier than average, and have lots of half-baked features, none of which we need".

I'm not sure what kind of tools you mean, but unless you're looking for something that just works exactly the way EJBs do for some mysterious reasons, I don't see why you can't do most "enterprisey" things with Rust or Go. Or Python or TypeScript for that matter.

unscaled··on Java 27
> Go has null pointer dereference problem.

Which Java famously does not have.

> Rust is too low-level for typical enterprise app where requirements changes twice a day. You end up spending time and tokens fighting with borrow checker.

In my experience, you do not spend tokens fighting with the borrow checker anymore, newer models are smarter. But it might not be ideal for a lot of CRUD applications.

> C# is MS product, which is no-go for some folks.

This is 2026, it's not 1996 anymore. .Net works on Linux and Microsoft is as friendly towards open source and open standards as a Big Tech company can be.

If anything, it was Oracle which more recently sued another company for using a JDK alternative. And this was a lawsuit that, if accepted, could have put the entire idea of API compatibility in danger and deal a severe blow to the Open Source movement.

Anyone who is morally bothered by MS but is unfazed by this is probably just mentally stuck in the 1990s.

> Kotlin probably would be the answer.

I love Kotlin, but I'm afraid that's not the case. The conservative organizations that choose Java out of inertia, would keep choosing Java over Kotlin, even if Kotlin is a better JVM language which is facing no downside.

For anyone who doesn't need to be on the JVM or work with JVM tooling, Kotlin doesn't cut it. It doesn't have null pointer dereference problem in theory... Only it does in practice if you're using any Java API that may return null (all these bang-decorated "Platform types"). Generic type erasure can only be overcome in inline functions with reified types. And building and deploying artifacts without docker is still a mess.

I found Kotlin extremely publishing for Java shops in the past, and I've converted multiple departments totaling over hundreds of employees to use Kotlin. But that was before AI. The rationale was simple: Java is an entrenched language that leads to bloated code, slow development cycles and way too many avoidable bugs in productions. Kotlin solves some if these issues, and it's very easy to learn for a Java engineer, while still letting you keep all of your tools and libraries. And as a language (putting ecosystem aside), I find it better than either Go or Typescript, and far more ergonomic than Rust[1].

But all of these arguments die with AI. Rust is just as ergonomic as any other popular language today if you're using an agent, and the fact that an engineer spent their lifetime writing Spring Boot programs in Java you don't have time to let them learn a new stack from scratch doesn't matter anymore.

Sure, there are many companies where letting AI write the code is still not acceptable, but most of these workplaces will accept AI agents sooner than they accept Kotlin.

I feel a bit sad since I like many ideas about Kotlin (especially how amenable it is for making DSLs) but we've lost that opportunity

--

[1] Unless you have to write highly concurrent code without any data races.

unscaled··on So you want to use OpenRouter?
OP already answered that one:

They used a closed list of 3 vendors in prioritized order, and got 429ed out of two of them, while the third one stopped serving the mode.

This is less of a problem if you're running an agent locally and routing your problem to OpenRouter - you can pin to one or two models for consistency and just switch models when something goes bad. But the article is specifically about production traffic.

unscaled··on Simple Is Not Small
I think the article is a bit weak when it's making this point, because it's not about Unix pipelines. It's about POSIX shell utilities being too bare-bones.

But Unix pipelines are not simple too. They have a couple of nitty-gritty details that often come out and bite you.

1. They can only stream raw bytes, so all the programs that deal with lists like sort and uniq have to separate items using a delimiter (usually newline). If you want to process data with that delimiter in it, you're in for a ride. And if you want to write a custom tool, you have to do all the splitting yourselves (luckily it's so common most programming language will provide a ready-made facility for you to do that). This is New Jersey approach again: "I'll make my code (the OS, the shell) easier to write, and in return make life harder for my users (the tool writers)".

2. Error are hidden by default in shell. Nowadays you can explicitly change this behavior, but you have to remember to do `set -o pipefail` and I don't think it was always there.

3. There is no data typing at all. Everything is binary or text. Nowadays a lot of programs just output JSON, and the users (if they even stay inside the shell) almost always reach for jq to parse it. But jq is not a Unix philosophy program: it's an entire streaming functional programing that can do quite a lot. But even jq gets hairy when you have to do a bigger query or transformation. In that case users often reach out to Python or another language and just move the business logic there.

I think this fits well with what the article is trying to say: Unix pipes are pretty easy (small) to implement on your own (compare that to something like Nushell's pipes). But the moment you to do something that's a little different than the happy path it was built for, you need to go for another tool (jq) that has its own built-in pipe and small programming language, because Unix pipes won't cut it. And for more complex (hehe) things, you'll have to reach for a larger (and simpler) tool: a full-fledged programming language.

unscaled··on Japan tried to build an operating system for the world, the US intervened
You can mix Japanese and Chinese with modern document authoring standards or with HTML, but the fact is that most people are too lazy to do that. It's a pet peeve of mine when a document gets displayed with a Japanese font but then falls back to another Chinese font when it encounters characters that are missing in the Japanese font. Even when the fonts look the same (or when you're using a unified CJK font like Noto), things will still look bad if the language is not specified, since some characters are written different in each language (or even different variants of Chinese), but still got unified[1].

I laud what TRON tried to do, but I don't think they had it right. They made the encoding too complex (especially for the 1990s) while Unicode offered a single Basic Multilingual Plane. Unicode quickly got complex too (mostly due to the mistake of adopting UCS2 and then repurposing that as UTF16 and adding surrogates, and later getting a little bit over the top with combining emojis), but TRON was complex in other ways. If you mixed both Chinese and Japanese in a single documents, you had a multiple encodings to care about and control characters to switch between them. Suddenly, the character encoding is not stateless anymore. You can say the same about Unicode, but the statefulness of UTF-8 or UTF-16 is local and easily recoverable. Same goes for the statefulness combining diacritics, and bidirectional markers (which are rarely used) only affect the display. TRON code plane switches, as far as I understand, mean that even a simple text search or regex search would stop working and need to be completely redesigned, since it now has to be aware of which language we're reading right now! That's a big break, as you say.

> It also had a lot more characters. Unicode dropped some of the older or rare characters, meaning some people can't write their last name with the correct character on a computer with Unicode.

Only if they're very old and born before or during the war or its not their legal name. The Japanese government restricts the Kanji that can be used in names to Jōyō kanji (regular use kanji) and Jinmeiyō kanji (personal name kanji). Together they add up to roughly 3000 kanji. The list was back in 1991 when Unicode 1.0 was released, but it did include all of them, and many more, as did previous Japanese standards that predated TRON like JIS X 0208. I feel like the "people can't write their name" was always a bit of cope. Japan is still the country of "the nail that sticks out gets hammered". If your name falls outside of the legally allowed list of Kanji, you'll suffer even if you're running BTRON.

[1] https://en.wikipedia.org/wiki/Han_unification#Examples_of_la...

unscaled··on Japan tried to build an operating system for the world, the US intervened
I don't know how much the USTR campaign against TRON affected the success of BTRON, but I feel like we should be careful about this explanation. This is a classic example of American exceptionalism, but it seems like the USTR mandate just created a certain amount of FUD around TRON at best. You can see that it targets both BTRON (The desktop version TRON) and CTRON (the telco version of TRON), but unlike BTRON, CTRON did see real adoption and even dominance in the 1990s.

I think the main reasons behind BTRON failure like in the domestic market. I really don't know much about this period but here is my understanding. BTRON was never an OS, but rather a specification, a set of standards for how BTRON-compatible operating systems should behave. This is quite similar to POSIX, but the standard covered significantly more than POSIX (e.g. HMI guidelines and character encoding) and the BTRON standard came _before_ the implementations. But just like POSIX, there was no guaranteed ABI compatibility. And to make things worse, every implementation had to be rewritten from scratch, while most Unix distributions before GNU traced their code all the way back to AT&T Unix.

The other issue is that with BTRON1, the OS implementation seem to have been expected to be developed by the hardware vendor - they were not envisioned as highly portable operating systems yet.

The different OSs were supposed to be developed by OEM vendors and interated directly into their hardware (especially with BTRON1). From what I could find, only 3 vendors have signed up for BTRON: Matsushita (Panasonic), Toshiba and OKI[1]. As far as I can tell, out of the three, only Panasonic ended up releasing BTRON-based computers. Panasonic went quite heavy on TRON and released multiple BTRON products, but Panasonic was a minor player in the home PC market in 1990[2].

Another small company that picked up BTRON was Personal Media. They released a sizable variety of BTRON-based hardware during the years and eventually started releasing it as a software package. In fact, they're still active today, rleasing it as chokanji, running inside a VM on Windows[3]. They basically took BTRONs impressive Kanji support and pivoted to an IME allowing inputting Kanji characters outside of Unicode.

The names that are missing from this story are all the big players in the PC industry in Japan in the 1990s. It's obvious why IBM Japan wasn't onboard, but NEC had its own monopoly to protect and Fujitsu and EPSON were clearly not interested[4]. CTRON, on the other hand, was picked up by NTT, which is probably the only company that mattered in Japanese telecommunications back in the early 1990s. BTRON had a window of opportunity in 1990, since Japan (or the rest of the world for that matter) did not have a dominant GUI platform yet. But in a market where the OS came with the hardware, lackluster vendor supported kinda sealed the deal.

[1] https://qiita.com/ko1nksm/items/243865903ae48c2c2c29

[2] AFAIK it was heavily dominated by NEC, with the smaller players bein Toshiba, Fujitsu and Epson. In 1990, when the first BTRON implementations were released, the dominant standard was PC-98, which was developed by NEC, with Toshiba and Epson making PC-98 clones. Even though all these computers were running x86 and usually ran on MS-DOS, they were NOT compatible with IBM PCs. This is mostly due to IBM PC not having enough RAM to display Kanji, so the which Japanese PC manufacturers in the 1980s solved with custom Kanji ROM chips. Getting Kanji support throughout the stack also meant heavily modifying MS-DOS and producing a different software stack that is tied to the custom hardware.

1990 is also the year that both DOS/V (a localized version of DOS that had built-in Kanji support) and Windows 3.0 were released, and both of them eventually lead to the death of domestic Japanese PC standards, since they could sufficiently support Kanji in software. 286 and 386 based PCs with extended memory were also powerful enough now to support Kanji in software, and DOS/V and Windows provided a standard application runtime environment for that. But this eventual standardization took time and back in 1990, PC-98 dominated the home market, while IBM managed to get some corporate market share with its PS/55 (an IBM PC variant with a custom display adapter with its own Kanji ROM).

[3] https://www.chokanji.com/

[4] OP happily mentions NEC, Hitachi, Mitsubishi and Fujitsu, but it fails to mention that they joined other TRON initiatives (ITRON, CTRON and The TRON chip) and explicitly declined to join BTRON.

unscaled··on The August 17 outage
In highly distributed microservice architecture, there's almost never a single upstream. In some cases you may have a couple of customer-facing entry-points (a global API gateway, and a couple of BFFs), but these are not the only paths that need to be protected.

There are client-side retries (which have broken GitHub in this case) and server-side initiated API calls between microservices that don't pass through any of your ingresses (e.g. triggered by an ETL pipeline, or a scheduled job).

With a complex architecture you can't just slap a circuit breaker on a couple of ingresses and call it a day. Don't get me wrong, putting them there does go a long way, but you won't be covering all your bases.

unscaled··on The August 17 outage
> To handle this correctly you need your RPC framework to accurately communicate retryable vs non-retryable failures to clients.

Even this is not enough, since you cannot always reliably know whether service B is dead or suffers an intermittent issue that can be safely retried just from looking at a single failure.

The classic solution, in the monolith/few-services world would be a circuit breaker. High failure rates on any service trigger a circuit breaker in the services calling it, and they'll wait for a cooldown period before trying again.

When you move to a massive microservice architecture with hundreds or thousands of microservices, setting up circuit breakers manually becomes very hard to track and do reliably. Service meshes like Istio make this slightly easier, but they still don't let you verify that all possible paths have circuit breakers and that retries are not excessive etc.

unscaled··on The August 17 outage
I think the "happy path" might be a slightly wrong classification in GP, since the post is in reply to a retry-storm issue and explicitly talks about retry storms and thundering herds.

I've seen many cases where engineers optimize the sad path, but pessimize the wretched path. Or in less flowery language, they cut the occurrence rate of common non-critical failures, but by doing that they introduce code that can make rare failures much worse.

The cases I've seen generally boil down to naive retry logic or poorly tested and poorly maintained fallback paths (such as killswitches that break their environment[1], graceful degradation turned graceless, dormant feature flags that get reactivated).

The case you see with a retry storm here is the most classic one and the one that annoys me the most. I've seen engineers adding aggressive retries even into places where the impact is minor (you could show an error and let the user manually retry instead). Retries that improve user experience can be great if done correctly, but I've never seen the authors of such pull request addressing the risk and mitigation techniques for retry storm or retry amplification.

I've seen cases which had:

1. Retries on the client side (browser or mobile app). 2. Retries on the BFF. 3. Retries on Microservice A used by the BFF. 4. Retries on Microservice B used by Microservice A. 5. Retries on Critical Service C used by Microservice B.

Most of these retries had very short timeouts (e.g. 100ms), in order to keep latency SLOs during normal operations (not a good idea on retries). Every time QA saw a layer without retries, that would be a bug, and adding retries is easy, so we'd get a new retry without much thought. But the first time Critical Service C became overloaded, Microservice B started timing out a couple of times and retrying. This was too much too much for Microservice A that had a short timeout that couldn't hold the 3 retries done by Microservice B, so it making doing its own retries, all of them dropped in the middle of the way. Eventually you'll get a full-blown retry storm where every request from the client side got amplified with 3^5 retries, easily bringing down Critical Service C.

We'd usually introduce a circuit breaker for the particular path that caused the issue, but a variation of this kept happening several times because designing safe retries across a vast collection of microservices takes a lot of effort, and it's always easier to just add a quick-and-dirty retry at any point where you think you might need one and call it a day.

A proper solution (which I've never seen implemented) would be an mandating a corporate-wide inventory of retry-paths, and monitoring it for any path that is at risk of triggering a retry storm, or adding mandatory headers that cross microservices and track the amount of retries done up the chain and the time spent in total waiting for previous retries. You could have a budget for both and automatically stop performing more retries. Both solution require extra effort and a large degree of coordination.

[1] This was the CloudFlare issue mentioned in this thread https://blog.cloudflare.com/5-december-2025-outage/

unscaled··on Go is an ideal language for AI-assisted software engineering
That's my take. The arguments for Go over Rust used to be:

- Better concurrency story

- Native cross-compilation of static binaries (great for CLIs)

- Easier to learn, easier to teach

- Opinionated: You don't have to enforce a single style everywhere

Concurrency died out as an argument when Rust async/await got better. Sure, it has "function colors" and that matters for weird purists who care very much about typing a single "await" in their code, but don't care at all about typing "foo, err := bla(); if err != nil { return err }" all over the place. But it doesn't matter in practice, and tokio has far better concurrency tools: there ares separate channel for mpsc, oneshot, broadcast and watch scenarios, there Streams, JoinSets and a select! macro that can operate more than just channels.

The static compilation argument also died pretty early on when the Rust musl target became more mature. It's still slightly easier to get cross-compilation started with Go, but now that you we have LLMs we wouldn't care.

The learning curve argument is dead. It used to be harder to hire or train Rust programmers and that was a real pain. But LLMs don't care. The same goes for the "Go is built for software engineering" argument, which is a euphemism "Go is our way or the highway level of opinionated". LLMs do not need an opinionated language as much as humans do. If you want all code to follow an arbitrary standard, just ask your LLM to set up one. Engineering teams used to spend years bikeshedding things like brace styles and spaces vs. tabs and Go went ahead stole that opportunity from them. But this is no longer needed.

unscaled··on "Clean" Code, Horrible Performance (2023)
I don't think it was true even in 2023.

This sounds like tackling the problems of C++ in the early 2000s.

1. Casey Muratori also that DRY shouldn't doesn't have to result in non-performant code.

2. Smaller functions, functions that do one-thing: Modern compiler can inline those. There are some edge cases where inlining may make less efficient use of states and loops but I don't think that's a main problem nowadays. I also wouldn't say the extreme version of this idea (very small functions) is still popular. The strongest proponent of this was Uncle Bob, and the last time I've heard him speak about code, he said he now lets the LLM write everything and he only reviews the module hierarchy and maybe the modules' public interfaces.

3. Polymorphism instead of ifs and switches was a big fad in the late 1990s until the late 2000s and had some holdouts in the 2010s. It was only ever popular in the Enterprise Java and C++ world (and maybe in Enterprise Smalltalk, never hard). Overuse of runtime polymorphism widely considered bad form in newer static languages like Go and Rust and in most dynamic languages there was always a tacit understanding of "use mostly conditions, add polymorphism if you need extensibility".

In functional languages (or languages heavily influenced by functional programming like Rust, Swift and Kotlin[1]), the classic approach for the type of scenario in this example is to use a sum type, and run a safe exhaustive match/switch on all the variants.

4. Hiding internals: The sum type example is telling of modern best-practices. Sum type fields are generally made public. Some languages (e.g. Rust and most pure functional languages) do not support private fields in sum types at all! Other languages (e.g. Kotlin) but immutable, so it's easy to maintain invariants without hiding information. Sometimes we do want to hide the type details and wrap it with public-facing type (this is a common pattern with internal error enums in Rust for example). Even in this case, there is no impact since we do not use runtime polymorphism or indirection (that would be Box<T> in Rust).

Due to compiler optimizations, hiding internals has marginal performance cost (if any) unless you require runtime polymorphism to achieve it. But why should you?

I feel like the performance costs lamented in this article mostly have to do with runtime polymorphism in static languages. And I fully agree here: runtime polymorphism is something that should be avoided when you don't need it[2]. But that's the thing: if you're looking at modern static language codebases, runtime polymorphism is not as hyped as it used to be in the past. Some languages still require heavy use of runtime polymorphism (Go is a good example of this), but other languages more often rely on static polymorphism (Rust) or compile time duck-typing (Zig and you could argue C++ template meta-programming used to do that, albeit quite awkwardly).

Even with all the issues you get with polymorphism, I don't think it's the main cause of slow application performance. It be very much the culprit in tight loops inside games, but if you look at the performance issues plaguing everyday apps, I think the two major culprits are endless layers of abstraction (the most quintessential example is basically every sluggish Electron app out there) and blocking the user on slow actions (like network loads).

---

[1] Even Java had sealed record types for a while now, and I'm sure will see Enterprise frameworks encouraging them in 20 years, when the rest of the world has moved on to spacefaring super-intelligent LLMs. But Enterprise frameworks also don't encourage you to write DRY code or keep your functions short.

[2] But do keep in mind that in Java it could be almost zero-cost in many cases. The JIT will monomorphize or bimorphize your classes if you always use the same class at the same callsite. The pointer indirection is not an extra cost, since every non-primitive that doesn't undergo Scalar Replacement[3] lives on the heap, and has a pointer.

[3] https://shipilev.net/jvm/anatomy-quarks/18-scalar-replacemen...

unscaled··on Our position on open-weights models
For this testing to be really effective at stopping "dangerous and misaligned" models from leaking out, you need a mechanism for banning failed models that prevent them from being released in the first place, not just prevent US companies from using them.

The only way to stop this from happening is blocking the model's release at the first place. Which requires China agreeing to the same framework. Dario says exactly the same thing himself.

So if he's being truthful here, he's not advocating for the type of ban people are talking about (usage ban). This kind of ban would be helpful to Anthropic's business in the short term, but it won't prevent Chinese models from improving, and it won't prevent them from getting money selling to other countries.

He is openly advocating for an international effort to enforce tests on public models, but I think this is highly unlikely in the current climate. Even if both the US and China agree that public models should be prevented from being used in designing bioweapons, they need to agree on a test and enforcement framework and that requires a lot of negotiation and trust. I don't see this as likely in the near future.

unscaled··on Kill The Cookie Banner
The law is airtight. Acceptance must be informed and freely given (this includes forcing through dark patterns and annoying banners that force you not to read), and withdrawal should be as simple as acceptance.

GDPR article 7 and its various recital already include that. GDPR wisely doesn't get into technical details like "cookie banners" anywhere, but various national agencies did set guidance and it's usually quite explicit: Rejection must be as simple as acceptance and reject buttons or link must be as prominent as the accept buttons and links.

For example, CNIL, the French data privacy authority, clearly says[1]:

"The CNIL has received complaints about dark patterns on cookie consent banners encouraging data subjects to accept cookies.

As a reminder, with certain exceptions, cookies can only be used with the consent of data subjects. Moreover, rejecting cookies should be just as easy as accepting them."

And gives examples of dark patterns such as different button sizes, multiple accept buttons, hidden reject buttons, etc.

The law and specific guidance is pretty unambiguous. This purely an enforcement problem. The regulatory bodies do not have the resources to go and chase most individual companies, and the non-profit NGOs that go after the violators apparently don't have the budget to make enough impact and scare companies into compliance.

[1] https://www.cnil.fr/en/dark-patterns-cookie-banners-cnil-iss...

unscaled··on Kill The Cookie Banner
Despite this being called a "cookie banner", this is not _just_ about cookie. When you click "Accept all" you are giving your consent to any form of tracking and information sharing mentioned in the details. The site you visit may share everything they know about you with any third party they mentioned. They can even use fingerprinting (if you've agreed to it) to keep tracking you after you've deleted the cookies.
unscaled··on Passkeys were invented by engineers with zero understanding of consumer brain
It doesn't even have to be something as bare-bones as pass. You can have a full-fledged password manager that is open-source and local-first. KeepassXC (and the OG Keepass) were always OSS and local-first. The original version of Keepass 1.0 for Windows was released long before Lastpass or 1Password[1], so we had an open-source local-first password manager before we had commercial cloud-based managers.

[1] To be more accurate, although it was always proprietary, 1Password was also local-only at first, with syncing only supported by putting it on something like Dropbox. They only added native cloud syncing later and eventually made it cloud-first.

Page 1 of 24Next →