Live data from Hacker News

How We Designed for Performance and Scale

nginx.com

31–40 of 63 posts

Re: How We Designed for Performance and Scale

#31
post #4

Btw there is a new feature in the kernel to help avoid using the accept shared memory mutex -- EPOLLEXCLUSIVE and EPOLLROUNDROBIN This should round robin accept in the kernel, and not wake up all the epoll listeners. https://lwn.net/Articles/632590/

Isn't this exactly what EPOLLET (edge triggering) does?

Re: How We Designed for Performance and Scale

#32
post #20

This is a lovely article, but: > The fundamental basis of any Unix application is the thread or process. (From the Linux OS perspective, threads and processes are mostly identical; the major difference is the degree to which they share memory.) It's better to be specific in performance discussions, rather than use 'thread' and 'process' interchangeably. As well as the article mentioned about memory sharing, threads (…

In Linux, they are both just entries in the process table. They are created by the `clone` syscall, and the "normal" way of creating them share different amounts of resources by _default_. You're right to say that treating them differently can be beneficial in some situations, but it really depends.

Yep. I guess the most basic case is: "is this single process multithreaded, as I have a multicore machine and there's only one PID"

Re: How We Designed for Performance and Scale

#33
post #3

"NGINX’s binary upgrade process achieves the holy grail of high-availability; you can upgrade the software on the fly, without any dropped connections, downtime or interruption in service." Is this really true? I remember seeing an article[1] recently on using an iptables hack to prevent dropping connections when reloading haproxy. Does nginx actually provide zero-downtime configuration reloads? [1] https://medium.co…

If it's the holy grail, then erlang has had it for a couple decades... and whether it actually works in nginx I don't know, but it does work in erlang. The way it's handled is that the process where the previous socket was connected remains in place until it terminates but all new sockets connect to the new code.

This is known to me as a "lame duck" mode, and I thought it was a standard thing to do!

Re: How We Designed for Performance and Scale

#34
post #30
post #24

Earlier quoted context omitted.

Coroutines have the distinct disadvantage of needing a stack, much like threads. So-called 'stackless' coroutines aren't really so different to computed gotos in a state machine

For parsing html, you wouldn't need that large a stack. Further, you could make the stack grow dynamically at a very small cost. Also, I bet that even if stackless coroutines are close to FSMs, they are much easier to program.

You need green/user-space threads if you want to use co-routines effectively, because if a coroutine blocks, it would block off the entire OS thread from doing anything else.

Re: How We Designed for Performance and Scale

#35
post #24
post #21

One thread per CPU, and non-blocking I/O, that's sounds like the usual way to approach the problem. I'm surprised it uses state machines to handle the non-blocking I/O, because modern software engineering provides much more pleasant approaches such as using coroutines.

Coroutines have the distinct disadvantage of needing a stack, much like threads. So-called 'stackless' coroutines aren't really so different to computed gotos in a state machine

What are stackless coroutines? What do they look like?

Re: How We Designed for Performance and Scale

#36
post #31
post #4

Btw there is a new feature in the kernel to help avoid using the accept shared memory mutex -- EPOLLEXCLUSIVE and EPOLLROUNDROBIN This should round robin accept in the kernel, and not wake up all the epoll listeners. https://lwn.net/Articles/632590/

Isn't this exactly what EPOLLET (edge triggering) does?

Edge vs level triggered refers to how a change in an fd's state is reflected in userspace: level triggering will cause repeated wakeups while the condition remains true, whereas edge triggering will cause exactly one, until the monitored state flips from true to false and back again.

The new flags relate to what happens when multiple threads are waiting on the same set of file descriptors (i.e. sleeping on the same epoll FD passed as the first parameter to epoll_wait(), or having the same client FD in multiple epoll sets -- sorry I'm not sure which way around it is).

Previously the kernel had no support for waking exactly one thread to handle one event, so if there was a single set shared among a bunch of sleeping tasks, all tasks would be scheduled, causing (presumably) synchronization contention on the kernel's internal structures. The new mode ensures only a single task is woken up for an event on a single FD, even when multiple tasks are waiting on it.

Re: How We Designed for Performance and Scale

#37

"NGINX’s binary upgrade process achieves the holy grail of high-availability; you can upgrade the software on the fly, without any dropped connections, downtime or interruption in service." Is this really true? I remember seeing an article[1] recently on using an iptables hack to prevent dropping connections when reloading haproxy. Does nginx actually provide zero-downtime configuration reloads? [1] https://medium.co…

The holy grail of high-availability isn't upgrading stateless software. It's upgrading stateful ones.

Like upgrading when a data structure changes between versions. The HTTP protocol nginx serves is stateless and by comparison far simpler. Same goes for Erlang. It offers nothing more than simple function replacement, and that's not enough to handle data structure changes either.

Re: How We Designed for Performance and Scale

#38
post #29

I did as they said at the bottom and gave them my e-mail and other personal details so I could download the eBook that they were giving free preview copies of - "Building Microservices". Unfortunately, they sent link to PDF only so it's not usable to me. Just a heads up to others so you save yourself the time of discovering that. (I'll just wait for when the book is finished and then I'll buy it so I get ePub. I like…

The book is out now from O-Reilly - its a good book.

Re: How We Designed for Performance and Scale

#39
post #37

"NGINX’s binary upgrade process achieves the holy grail of high-availability; you can upgrade the software on the fly, without any dropped connections, downtime or interruption in service." Is this really true? I remember seeing an article[1] recently on using an iptables hack to prevent dropping connections when reloading haproxy. Does nginx actually provide zero-downtime configuration reloads? [1] https://medium.co…

The holy grail of high-availability isn't upgrading stateless software. It's upgrading stateful ones. Like upgrading when a data structure changes between versions. The HTTP protocol nginx serves is stateless and by comparison far simpler. Same goes for Erlang. It offers nothing more than simple function replacement, and that's not enough to handle data structure changes either.

Synchronize to a checkpoint. Serialize all state. Run data structure upgrading code. Deserialize upgraded state.

Of course possible to do in-place, but I'd imagine serialization makes testing it easier. Not to mention sending error reports if something goes wrong. Having all that state in a bug report could help a bit!

Re: How We Designed for Performance and Scale

#40
post #21

One thread per CPU, and non-blocking I/O, that's sounds like the usual way to approach the problem. I'm surprised it uses state machines to handle the non-blocking I/O, because modern software engineering provides much more pleasant approaches such as using coroutines.

nginx is written in C though - I know it's possible to do coroutine-type stuff with setjmp/longjmp, but isn't that considered risky?
Post reply on HN