Hacker News new | ask | show | jobs
by devjam 908 days ago
I’m bemused by these sorts of disparaging comments that come from people who seem to have no real world experience running Go applications at scale.

It’s entirely possible to run a server that will handle tens of thousands of req/sec on hardware with comparatively small amounts (by todays standards) of RAM e.g. 512MB

3 comments

I have written a bunch of tiny Go apps, and agree with you. In general, 64MB is where I set the limit on these things and there are no problems. I have some big applications that literally allocate 1GB at once and they still run OK within 2GB memory limits. (Obviously, you can't do two of these operations at once, though.)
I don't see how you think my comments are "disparaging," this is just code doing what it is programmed to do.

> It’s entirely possible to run a server that will handle tens of thousands of req/sec

Indeed, you can do this. I'm talking about what happens once the Go starts getting memory pressure and the GC starts dominating the CPU. This is what it is designed to do. If your code can prevent that from happening, you are correct then, as it won't be an issue.

I’ve done well over 100k req/s and I’d have to ask former coworkers but I’m pretty sure we were using under 2Gb of ram.
> Go starts getting memory pressure and the GC starts dominating the CPU

That does not happen. You must be thinking of Java 15 years ago.

Prior to GOMEMLIMIT being a thing, people would get themselves into this situation occasionally. Their steady state working set size is something like 2.1GB, but they want to fit into a 4GB container limit, so GOGC=100 led to OOMs occasionally. They would bring GOGC down to 10 or something (why not 90!?), and then they are running GC every time they allocated 10% or 200MB, and then their CPU profiles would show they were burning a lot on GC, because it's pretty easy for an RPC server to generate 200MB of garbage.

GOMEMLIMIT definitely improves things, taking the very blunt instrument GOGC out of people's hands, and does what they actually want instead, which is to prioritize GC only when you're about to run out of memory.

People dislike tunables like this, and rightfully so, but a similar problem exists somewhat insidiously in non-garbage collected languages. Calling malloc() and free() is not a zero-cost operation, and you can certainly waste CPU by calling free() when nothing actually needs the memory you are freeing. GC lets cases like "this is a CLI app, the system has 128GB of RAM, the working set size is 2GB, and it's over in 1 second" basically never do any memory freeing. You allocate as much as you need, GC never runs, and the app exits. That kind of optimization is actually pretty neat. But, for steady state servers that run for an unbounded period of time, it is inevitable that you are going to spend time trying to reuse memory that you no longer need, because the system only has a finite amount of memory. Then the compromises start to balance throughput with efficiency. gc + GOMEMLIMIT is a very nice compromise that is excellent for many applications, but no heuristic is perfect for every possible computer program.

Even Java 17 years ago is ironic, considering the hardware requirements of J2ME, Java Embedded, Blue Ray players, Ricoh and Xerox copiers, Cisco phones, and so forth.
Is it likely that you would run servers "at scale" on a RPi?
A newer rpi is in fact more than enough scale for most websites in existence.
A RPi is more powerfull than most PCs I owned between 1990 until 2002.
Come to think of it, in terms of computing power, the ESP32 is probably more powerful than what you used before 2000.
I always compare it to a Amstrad PC1512, given its memory.

Still even with limitations, there was a ton of stuff we could do in MS-DOS, let alone a more powerful system with dual core and built-in networking.