Live data from Hacker News

Node.js in Flame Graphs

techblog.netflix.com

91–100 of 259 posts

Re: Node.js in Flame Graphs

#91
post #71

Earlier quoted context omitted.

What are the arguments against node.js in their use case? Not looking to start any wars, but I was under the impression that if you know what you're doing* node.js is pretty awesome. This particular bug had to do with a misunderstanding regarding the express API. * for the most part: understand async and closures/memory leaks.

I was doing some googling about this earlier, IIRC one of the big ones was "Good when your bottleneck is I/O, not good when your bottleneck is CPU."

> "not good when your bottleneck is CPU"

99% of cases, your bottleneck will be I/O.

In the 1% of cases where your webserver's bottleneck is the CPU, you have much bigger problems than using a hipster ;) language: you're doing it wrong (tm) on an architectural level:

(a) Your processor-intensive/long-running tasks need to be in seperate worker processes and

(b) you need more webserver instances.

I usually try to avoid absolute statements, but I think this may be accurate:

The only CPU-intensive thing your web server code should ever do, is hash passwords.

Re: Node.js in Flame Graphs

#92

Earlier quoted context omitted.

> I can generate the regexp ((^\/foo\/bar$)|(^\/foo\/bar\/\d+$)) And how would you know which one got matched? The regex match isn't going to tell you that. Also, it needs to recognize if multiple were matched, which is definitely not going to be done by the built-in regex matcher. It's certainly possible, but pretending it's trivial isn't helping, either.

First off, you don't need to handle the 'multiple' case, since the "|" has precedence rules applied to it. The leftmost match is the match. Second, you know which one matched based on the index of the capture groups, which is deterministic. See this example: http://rubular.com/r/HiW6gjnURe You could write a simple router based on this in < 50 lines of JS. I'd do it now, but I have work to do.

> First off, you don't need to handle the 'multiple' case, since the "|" has precedence rules applied to it.

Read my top-level post again - multiple routes can be called on the same request, so express has to be able to find all of the matches, not just the first one. This is a mistake in the original article, as the author doesn't appear to understand the power of express routers.

> Second, you know which one matched based on the index of the capture groups, which is deterministic.

Deterministic, yes. But now you're also writing your own regex parser, because you have to know how many capture groups each sub-regex contains in order to figure out the indexing.

Re: Node.js in Flame Graphs

#93
I love these kinds of investigations into problems in production. I mean, you really have to admire their determination in getting to the root of the problem.

In some ways, these engineers are not that different from academic researchers, in that they are devising experiments, verifying techniques, all in the pursuit of the question: why?

Re: Node.js in Flame Graphs

#94

Earlier quoted context omitted.

> I can generate the regexp ((^\/foo\/bar$)|(^\/foo\/bar\/\d+$)) And how would you know which one got matched? The regex match isn't going to tell you that. Also, it needs to recognize if multiple were matched, which is definitely not going to be done by the built-in regex matcher. It's certainly possible, but pretending it's trivial isn't helping, either.

The match object will be of the form [undefined, ..., input_string, undefined, ...], so you can use something like m.indexOf(input_string) to find out the index. Unfortunately that's O(n) again, unless the engine uses a sparse array implementation...

You're right that it's O(n), but it's still faster than express js. It's not iterating through the array that's slow, its evaluating the members of that array. Express must check each route using JS functions that each execute a regexp match per route. Using a single regexp is much faster because 1.) by unioning the regexps you get the correct NFA, which will run much faster 2.) there's less function call overhead.

Iterating through the array looking for the first non-null value, then looking it up in a JS object O(1) matching capture group positions to routes is much faster than having to dispatch a bunch of functions. Additionally, it can be JITed to much faster code I would wager.

So, expressJS = N regexp matches, while this method = 1 regexp match + N null checks in the resulting array + 1 hashmap lookup in an object to return the proper route. The second solution is going to be much faster, even for large routing tables, esp. given how optimizable that array lookup code is.

Re: Node.js in Flame Graphs

#95

Earlier quoted context omitted.

Netflix overhires quite a bit. They also pay their (very good) engineers very high salaries (possibly highest in the valley) which results is very low attrition. The net result is a lot of engineers having a lot of time and all these engineers experiment away on technology. In fact, they have quite a bit of NIH syndrome internally because the engineers have nothing to do.

I don't think you have those reasons in proper order. I think the ability of the engineers to experiment and use new tools is why they have the high level of retention. I've seen plenty of places that pay much higher than the competition yet have very high turnover due to the a conservative culture which dictates the tools and doesn't encourage experimentation.

"An idle mind is a devil's workshop". I have seen this over and over again when you give engineers a free run. They micro-optimize things. They start rewriting javascript to c++ and c++ to c and asm when the speed improvements are at best marginal and the cost of maintenance of produced code is extremely high.

Don't get me wrong, I am not saying that these optimizations are bad. In fact, these engineers measure things properly and then rewrite. It's just that there is no business case for all this time spent. It's like writing web servers in C. Sure, it's possibly the faster than everything else but seriously? Who maintains all this.

Re: Node.js in Flame Graphs

#96
post #66

Earlier quoted context omitted.

Netflix overhires quite a bit. They also pay their (very good) engineers very high salaries (possibly highest in the valley) which results is very low attrition. The net result is a lot of engineers having a lot of time and all these engineers experiment away on technology. In fact, they have quite a bit of NIH syndrome internally because the engineers have nothing to do.

Curious, do you have some anecdotes about that? I've generally heard from coworkers who were at Netflix that they have pretty high attrition at the lower levels, with the "fire the bottom 10% every X months" rules.

The bottom 10% has it hard in any company, let alone netflix. Almost every company I know has stringent rules for the bottom 10% (read: a step by step approach until they get fired). But if you are a decent engineer you have nothing to worry about.

Re: Node.js in Flame Graphs

#97

Earlier quoted context omitted.

The automaton isn't even that big, really. The number of NFA states is roughly proportional to the total number of characters in the regular expressions involved, and NFA to DFA conversion usually expands the automaton only be a factor of two or three. Although the subset construction in theory worst-case exponential, that situation never occurs in practice.

By that logic the app I work on would have a DFA with ~200,000 nodes. That sounds fairly large to me. I suspect this is a slight overestimate, but the point stands - non-trivial apps could quickly build very large state graphs.

Yes, but unless you're running on embedded, and to be honest to some extent even then unless you're way at the bottom of the power scale, you're running on a supercomputer that will laugh at 200,000 nodes in a DFA, and it's very easy for that DFA to run significantly faster than the presumably several tens of thousands of FAs you're running sequentially otherwise.

Re: Node.js in Flame Graphs

#98

Earlier quoted context omitted.

The automaton isn't even that big, really. The number of NFA states is roughly proportional to the total number of characters in the regular expressions involved, and NFA to DFA conversion usually expands the automaton only be a factor of two or three. Although the subset construction in theory worst-case exponential, that situation never occurs in practice.

By that logic the app I work on would have a DFA with ~200,000 nodes. That sounds fairly large to me. I suspect this is a slight overestimate, but the point stands - non-trivial apps could quickly build very large state graphs.

By that logic the app I work on would have a DFA with ~200,000 nodes. That sounds fairly large to me.

That's not very large, since you can store a DFA very compactly in a table (C array with structs). The table stores the transitions, which only have the character and the target state. Then you either keep an array with state offsets in the transition table, or you modify transition tables such that each transition points at the first transition of the target state.

Re: Node.js in Flame Graphs

#100

> It’s unclear why Express.js chose not to use a constant time data structure like a map to store its handlers. Its actually quite clear - most routes are defined by a regex rather than a string, so there is no built-in structure (if there's a way at all) to do O(1) lookups in the routing table. A router that only allowed string route definitions would be faster but far less useful. I can't explain away the recursion…

The other thing you can't properly get using a map is precedence. By building an array you can go through it in order so that middleware is run in the order it was defined in the code.

That way, if you have any route definitions that match early on, especially if they allow execution to continue ( by calling next() ), they will run before any following code does.

This allows your code to be very expressive and intuitive when reading it, as things have a defined order.

I imagine the recursion is also done so that you can use a route object anywhere in that ordered stack and it will apply all its own handlers, in the order they were defined in the code.

Post reply on HN