7 ms·
> We were running 4,000 Node containers (or "workers") for our bank integration service. The service was originally designed such that each worker would process
by 7777fps 7y ago
> We were running 4,000 Node containers (or "workers") for our bank integration service. The service was originally designed such that each worker would process only a single request at a time. This design lessened the impact of integrations that accidentally blocked the event loop, and allowed us to ignore the variability in resource usage across different integrations. But since our total capacity was capped at 4,000 concurrent requests, the system did not gracefully scale.
I can't be the only person who reads stories like this and wonders how they arrived at that solution in the first place?
Failing to scale because their previous approach to scaling was a worker per request, a model which was roundly moved away from, because that's how CGI and Apache modules worked and it didn't scale well.
I thought one of the key selling points with Node was an fully async standard library, enabling better scaling in process.
But then you read stories like this, and I find it hard to relate to the original problem.
- rynop 7y agoAgreed. This is the second scratch my head moment from the Plaid engineering team blog recently.
- phoe-krk 7y ago> I thought one of the key selling points with Node was an fully async standard library, enabling better scaling in process. We still have an event loop that is trivially blocked by very simple programmer errors, destroying the whole advantage that you describe here. The fact that Node ships a fully asynchronous standard library doesn't in any way fix the fact that Node is a runtime for a language that itself is a mistake.
- nicoburns 7y ago> We still have an event loop that is trivially blocked by very simple programmer errors, destroying the whole advantage that you describe here. So they fixed the issue that some requests blocked... by making all requests blocking.
- phoe-krk 7y agoThis is the worst kind of software engineering. There is a massive deadlocking design mistake in the centre of the language - literally a huge red button with DO NOT PRESS printed on it. Thousands of programmers pass it by every single day, or hour, or minute, and the creators of the runtime insist that it is impossible to fix that button whatsoever; instead, all users need to work around it by ensuring that their code in no way presses that red button on purpose or even by accident. These people insist that it is impossible to program normally and in a language that is actually sane and does not advertise obvious and gaping design mistakes as "features of the language". These people advertise the analogue of Python's Global Interpreter Lock as the core foundation of their language. These people advertise Node and the language it implements as practical for implementing multithreaded applications. Posts such as this show what sort of bullshit it is; it is only practical to use Node for parallelism if each single Node instance is only ever run single-threaded. You don't parallelize by running multiple threads, you parallelize by running multiple Node runtimes. This is no longer an act against productivity or usability. This is simply insane and shows one of the most basic things that are wrong about Node's language and approach. It is impossible to write a multithreaded program if your language of choice makes it trivial, and practically unavoidable, to globally lock your whole runtime with every single line of code you write and import as your dependencies.
- jononor 7y agoThe blog in question indicates some uncommon and questionable engineering practices with Node.js. There are likely hundreds of success stories for every one like that. The first Node.js service I wrote and maintained, processed thousands of requests in parallel and was successfully in production until the company it was developed for ran out of money.
- x86_64Ubuntu 7y agoBut how many of these "questionable" practices get held up as engineering marvels by the creators of the monstrosity? When you look at this blog post, you can see the author really felt a sense of pride in this Frankenstein and wanted to show the whole village.
- duxup 7y ago>by very simple programmer errors I can't help but also feel that is also an issue and in another given language this issue might not happen ... but they'd hit another. It's so easy to say "don't do X because problem Y won't happen" but hard to predict what happens when you move from (language, platform, or whatever) X to (language, platform, or whatever) Z.... and I suspect people often hit issues and realize that maybe Y wasn't the problem. I see it all the time and I feel like "Wait guies I'm not sure we're fixing the right thing!?!?!" This article raises a lot more questions than answers IMO.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- davedx 7y ago> trivially blocked by very simple programmer errors Can you give an example please? I think it's much easier to block a thread with C#'s async programming model than node's...
- dmitriid 7y agoNode only has one thread. Everything else follows.
- coddle-hark 7y agoNo it doesn’t. We’ve had good models for concurrency in single-threaded systems for a while now.
- PixyMisa 7y agoThe model being "don't do that".
- dmitriid 7y agoYou say no it doesn't and then speak about concurrency models for single-threaded systems. Choose one :) Node can't not be single-threaded. Because Javascript is. Node.js is single threaded. It has a single event loop in a single thread, and all the "concurrency" is simply queued on that loop. It offloads some tasks to libuv for some system-related tasks but that's it. And the thread pool that libuv creates is very limited. Anything that doesn't end up in libuv (that is, probably vast majority of user code) will only ever run in one thread... because Javascript is single-threaded hence V8 is single-threaded hence Node.js you get the gist. And of course, node.js even has a separate documentation section titled "Don't Block the Event Loop (or the Worker Pool)" [1] because it's trivial to block the event loop. [1] https://nodejs.org/en/docs/guides/dont-block-the-event-loop/ https://nodejs.org/en/docs/guides/dont-block-the-event-loop/
- willglynn 7y agoNot OP, but… concurrency is not the same as parallelism. This is an important hair to split: https://stackoverflow.com/a/24684037/1026671 https://stackoverflow.com/a/24684037/1026671 "Concurrency" doesn't warrant scare quotes even when describing a single threaded program. It's true that Node.js allows one task to block many others, but that's an implementation detail of Node.js, not a guaranteed result which necessarily applies to any program using only one OS thread. Other programs suffer from analogous starvation/deadlock/livelock/priority inversion problems, but those would be implementation details too, not guaranteed results from using multiple OS threads.
- earthboundkid 7y agoAsync is just modern cooperative multitasking, and just like the 90s, it's easy to accidentally lock the whole system.
- phoe-krk 7y agoWe are no longer in the 90s. The code has increased in volume a hundredfold and it comes from literally everywhere. You can no longer trust everything on your machine or your network to be bug-free or otherwise non-hostile. Creating a system in the 21st century that tries to follow ideals from the 90s gives us the kind of idiotism that we can witness here.
- jerf 7y agoI think you may have misinterpreted earthboundkid; the claim isn't that it worked in the 90s, the claim is that it was already broken in the 90s. You are otherwise on the right track, though Node does technically have one advantage, which is that it is a cooperatively-scheduled island in a preemptively-scheluded overall OS. In the 1990s, when the cooperatively-scheduled program was not cooperative, you locked the machine, not the process [1]. There is a reason why Apple went very aggressive with the OSX rewrite; the previous Systems had basically written themselves into a corner where they had to use cooperative multitasking because so much code made use of the implicit promises it provides, yet they could no longer afford to compete with Microsoft if they didn't get off it it, because the complexity just kept going up, up, up and the problem was going to continue getting exponentially worse. For a Node program, you only have to account for the Node program itself, not everything running on the computer. Still, you're in the same exponentially-growing-complexity trap (with a very initially-safe-seeming low exponent, but it still gets you in the end), you just reset yourself back to a point earlier on the curve. [1] There are various details, caveats, interrupts, etc, the picture is more complicated than one sentence can convey, but the principle still held and it was still possible to wedge the machine fairly badly for varying periods of time with simple bad code.
- winrid 7y agoI mean, if you write slow code in a high throughput environment you'll just kill the CPU from context switching between threads instead. It doesn't matter if it's in an event loop or thread per request. Architect things correctly.
- Crystalin 7y agoI got the same feeling. We use node and usually split to 500 concurrent requests per process. Still interesting...
- eshyong 7y agoI feel like this article is missing a crucial piece of information: why was integration code was blocking the event loop in the first place?...
- techterrier 7y agoI wonder if all this was at root, pickup up jobs from a message queue and they only wanted each process to only have one job in flight at once.
- Vesuvium 7y agoThe way it happens always: many people working to a solution, not agreeing on one and then comprising on something in the middle, even if it makes no sense.
- api 7y agoI wonder what percentage of the massive compute power of huge cloud data centers is spent just chugging away on ugly clunky hacks to run bad code?
- asdfman123 7y agoQuite a large percentage. I worked on a site that got 1 request per second and they were able to handle it by spinning up like 20 VMs. Turns out they were just using Entity Framework wrong. Whoops. But also, though, you have to consider that most places aren't Plaid, and most places developer time is more expensive than throwing an extra machine at the problem.
- liveoneggs 7y agoPretty sure apache + cgi would scale better :)
- bjacokes 7y agoThere are a couple of reasons that the legacy scaling model was viable for us. As mentioned in the post, only 1/10 of our traffic was from the API, which gave us a roundabout way to scale by diverting resources. And it's only viable to use this model of scaling when the business value of a request is high – we were originally quite happy to spin up more containers when we reached our scaling limit. That's the pragmatic reason why we were processing one request per container. In terms of what issues caused us to move away from parallelism in the first place, it was all the CPU-bound stuff that you might expect: ReDoS-style issues, post-processing arrays in very large edge cases, programmer error, etc.
- abalone 7y ago> In terms of what issues caused us to move away from parallelism in the first place, it was all the CPU-bound stuff that you might expect: ReDoS-style issues, post-processing arrays in very large edge cases, programmer error, etc. But these are not parallelism problems. These are single threading problems, which the core problem with Node.js, not parallelism in general. Hence I think the question stands: why did you choose node for this?
- bjacokes 7y agoIt was chosen about 6 years ago when the product was first being developed, so most of us on the engineering team weren't around when the decision was made. The main choice we're making at this point is: what's the impact and ROI of a language migration vs getting Node to work as well as we can?
- ubu7737 7y ago> what's the impact and ROI of a language migration vs getting Node to work as well as we can? Hire an architect costs what? Putting the genie back in the bottle was a problem Plaid baked into its early success, which is common with startups hiring engineers with zero architecture knowledge.
- ilaksh 7y agoThey didn't actually understand Node very well at first and then later they figured it out.
- sergiotapia 7y agoAt some point you have to take a step back and realize you've grown beyond your tech and reach out for something else. Elixir sounds like a great fit for these problems. For example Discord reached out to Rust and built tiny Rust components that are called from Elixir for their server user list. Some servers have 200,000+ people online, and Elixir wasn't cutting it performance wise. Rust, boom now it works.
- asdfman123 7y ago> I can't be the only person who reads stories like this and wonders how they arrived at that solution in the first place? Here's how it probably worked: they liked Node, they liked containers, they put Node into containers and it worked, and they stuck with it as the user base grew.
- Turbots 7y agoUsing an interpreted language and complaining about performance is always a funny thing to read. Use Java, C++, Rust, Go or C if you want pure performance. Use Java with something like Spring + a Reactive stack to scale up to much higher concurrent numbers of requests. You'll have the same easy programming model that Node provides and you'll have both performance from the language and the more efficient execution. If you're worried about the startup times and memory footprint that comes with a JVM based application (although this has improved substantially the last few years), go to a natively compiled language like C, C++, Rust or Go. Or compile your Java apps using GraalVM native images.
- praseodym 7y agoRelated, from the article: > We hypothesized that increasing the Node maximum heap size from the default 1.7GB may help. To solve this problem, we started running Node with the max heap size set to 6GB [..], which was an arbitrary higher value that still fit within our EC2 instances. Sounds like they were utilizing their EC2 instances very poorly. Why not run more workers per instance, or switch to an instance type with less RAM (or more CPUs)?
- tracker1 7y agoThey were using ECS, it also looks like they had to work through a couple bottlenecks to get multiple requests per node working well... I think they could get further by using the newer workers api, since gRPC doesn't work with the cluster module.
- crazygringo 7y agoYeah, I don't get it either, at all. The original poster wrote below: > In terms of what issues caused us to move away from parallelism in the first place, it was all the CPU-bound stuff that you might expect: ReDoS-style issues, post-processing arrays in very large edge cases, programmer error, etc. But it's trivial (a single line) in Node to place breaks in CPU processing to allow the event loop to fire, and as for "programmer error"... many commenters below are also complaining async programming is too hard or finicky. But that's like complaining about C because pointers are hard, or Java because OOP is hard, or databases because planning indexes is hard. Once you "get" async, pointers, OOP, or indexes, it's easy. And it's part of your job as a professional programmer to get it. Async is no trickier than anything else. The setup in the first place makes absolutely no sense to me, using a language exactly opposite of how it's meant to be.
- markandrewj 7y agoThe issue is that many developers that are coming from synchronous programming don't get asynchronous programming. They could both improve the code by not writing blocking code, and also using something like the cluster module (https://nodejs.org/api/cluster.html https://nodejs.org/api/cluster.html).
- crazygringo 7y agoI get that. What I don't get is how nobody treats it as an issue when developers coming from Python or Java to C don't get pointers. The assumption is that you learn. But for some reason, people think it's "OK" to not get async, that it's the language's fault rather than the programmer's. That's what I don't understand. It's like a different cultural standard gets applied.
- markandrewj 7y agoI agree with you, but I think many developers get reluctant to change when they have been doing something one way for a long time, especially if they feel that one way works fine. I can also understand the position, as sometimes it can be fatiguing when technologies are constantly changing. For this project though, if they are actively going to avoid asynchronous programming, they may have been better off choosing a synchronous language.
- tluyben2 7y ago> I can't be the only person who reads stories like this and wonders how they arrived at that solution in the first place? No you are not. I wonder which CTO would allow this; like everyone here, the exact case is not really clear (or at least why this solution is a great solution for it), but this sounds like a weird solution (and expensive) to some issue. I really don't understand these 'solutions' and I am almost 100% sure I (with a team! but the point that this is not the best solution for the problem) can whip up something far simpler and more efficient for this problem. But ofcourse there are problems that might fit?