Because Go is ill-suited for projects like these :)
Just like synchronization, Go scales poorly with project complexity and aspirations. It is unable to efficiently take advantage of beefy many-core nodes, its GC is bad at achieving high throughput and the language itself is incapable of providing zero-cost abstractions and non-zero cost abstractions end up being more expensive than in other compiled languages.
Should have picked .NET for scalability and low-level tuning. Or Rust if higher engineering cost is acceptable.
> Just like synchronization, Go scales poorly with project complexity and aspirations.
I've had the opposite experience. Compared to C++ codebases I've worked on, Golang has way less "complexity per feature".
> It is unable to efficiently take advantage of beefy many-core nodes
Go had the best built-in concurrency primitives of any language I've used (python, java, rust, c++/c).
> its GC is bad at achieving high throughput
Isn't this just the nature of using GC? You give up control over when some work gets done, of course it affects throughput.
> incapable of providing zero-cost abstractions
ime, bad abstractions slow systems of any appreciable complexity a lot more than the cost of the abstractions in latency.
> non-zero cost abstractions end up being more expensive than in other compiled languages.
Do you consider Java a compiled language? I mean, of course its slower than rust and c/++, as it has a more complex/abstracted runtime. But last I heard, it was on par, or even faster, than java.
This is rarely the case outside of very simplified cases.
Golang prioritises compilation speed over optimisation. Unlike Rust/C/C++ which either use one of the heavily optimising IR compilers (gcc or LLVM) Golang uses neither, preferring to implement piecemeal the optimisations that work well on relatively simple code - in order to keep the compiler very fast and simple.
Java is the opposite of this. It has multiple compilation stages, the first of which is the bytecode compiler javac but then also the runtime JIT compiler, both of which favor peak performance (i.e optimisation).
Golang can appear faster on many micro-benchmarks but for any highly numeric load or remotely generic code using interfaces etc become involved then Java is going to come out on top and it won't be particularly close.
This isn't to say you can't write very fast Golang code, you can. If you understand (or simply inspect generated code/profile) what the compiler can and can't do for you you can coax it into making fast machine code but you need to be vigilant and modifying tight loops can be more troublesome than equivalent fast Java code.
I have written very fast stuff in both languages and if I needed to pick a language for peak runtime perf it would be Java out of those two every time.
Note this is completely ignoring the very weak throughput of the Golang GC which is another easy way to get into very bad performance problems with Golang that can be hard to resolve.
The JVM provides a lot more flexible concurrency abstractions, but they're now appearing in the standard library instead of hardwired into the language. This lets us extend or reimplement them.
(Synchronized blocks/methods are primitive and haven't been deprecated, but they recommend using java.util.concurrent.locks instead.)
Plus the usual mistake of assuming (re: GC) that the system (de)allocators are free. They weren’t. I strongly suspect that GCs are extremely competitive in most non-trivial applications.
Don’t judge every GC based on experience with the Sun JRE 20 years ago.
They are, but not so much in the case of Go - it is optimized for small containers with few CPU cores, that do not need to sustain (inevitable) high allocation throughput.
This is what happens when it's in different conditions, in a test that is still very friendly to Go using the stack which it's supposed to be very strong at: https://github.com/LesnyRumcajs/grpc_bench/discussions/441 Note the CPU % usage. The numbers only get more interesting once you start using 64 core hosts.
You could also look at write barrier/synchronization/allocation sensitive microbenchmark instead which paints way less positive picture: https://benchmarksgame-team.pages.debian.net/benchmarksgame/... (special props to OpenJDK's write barrier elision, this is something .NET needs to do better at still)
These are a lot of extraordinary claims. I would love to learn more about the evidence and what make you think this way. There are many successful companies that built scalable services with golang, so the burden of proof for the above claims are high.
"Set 50 instead of 4 to replica count" and "continues scaling when you give it something that isn't anemic 2CPU 512Mi container" are two very different things :)
In either case, seeing undeserved praise of Go is the most expected outcome from the audience that used to praise e.g. Elizabeth Holmes.
Please do look at the way its internals work, and compare the compiler output (and I mean not the useless ASM that Go's disasm outputs but what is shown by e.g. Ghidra) and primitive overhead with Rust, C# and even Kotlin.
Also hearing about Go's concurrent primitives makes me laugh. CSP is a 40 years old concept, and Go bolted itself to it, while also learning nothing from other languages to be modern, therefore can't be effectively used for more advanced concurrency scenarios nor enables you to remove overhead when needed.
Try writing a concurrent data structure in Go that performs as fast as in C++. C# let's you do that. Go - not so much.
A language that officially recommends "don't communicate by sharing memory; share memory by communicating" is obviously not meant to implement concurrent data structures well. This means, in some cases, it is indeed the wrong language to use. Go is no longer positioned at the low-level "systems programming" segment and hasn't been since not long after its release. It's better to think of it as "compiled concurrent Python" than "a competitor to Rust".
Just like synchronization, Go scales poorly with project complexity and aspirations. It is unable to efficiently take advantage of beefy many-core nodes, its GC is bad at achieving high throughput and the language itself is incapable of providing zero-cost abstractions and non-zero cost abstractions end up being more expensive than in other compiled languages.
Should have picked .NET for scalability and low-level tuning. Or Rust if higher engineering cost is acceptable.