7 ms·
Silly benchmarks on completely untuned server code
- MapleWalnut 7y agoAnother great post! I'm curious what people would suggest as a tech stack that's not python/gunicorn. Something with a GC, decent ORM, that can replace database-backed services written in Python with similar high level code.
- sytringy05 7y agoYou could have a look at Reactive Java, node, golang, even nginx + openresty. Basically anything with an event loop and good non blocking IO support for client requests and you will get loads of traffic through without too much hassle. Of course a lot of this really depends on what your app needs to do over and above handle HTTP requests and what sort of library support you get from language / framework X. Java, for example, has some great reactive libraries, but any JDBC driver blocks (as it has to, enforced by the JDBC spec). There's a couple of non JDBC drivers that support non blocking IO, but they are still evolving. It's a similar story with other languages, there's always something necessary that isn't quite there yet. That said you can still squeeze vast performance out of the blocking IO things with a solid engineering base (Tomcat, IIS if you can stomach windows) you just hit the thread limits earlier and harder than with the non blocking stuff.
- enitihas 7y agoGolang? The go standard library is very good for writing network servers. It is effortless to write a service speaking HTTP with good enough performance. Doing the right thing is also easy. For example, using io.Copy uses splice or sendfile if possible to transfer the data. This makes writing good enough performance servers very easy
- tinco 7y agoRuby. It's the language with the most intuitive syntax and standard library, the most ergonomic external libraries and an extremely well polished web development ecosystem. Close rivals might be PHP and Node.JS, there's pros and cons to all of them. If something needs to be very performant you pluck it out into a little golang microservice.
- cakoose 7y agoThe standard Ruby VM (MRI) is about the same speed as the standard Python VM (CPython), aka on the slow end. Twitter famously moved from Ruby to Java/Scala, largely for performance reasons. Node.js is much faster because V8 is much faster, but it's still basically single threaded, so you need to run process-per-CPU, which is what the original blog post (to which this post is a follow-up) was complaining about.
- tinco 7y agoYeah, the Ruby VM certainly is slow, but that doesn't really matter for most realistic workloads. In Twitter's case it definitely made sense, but do keep in mind that besides switching to Ruby they also switched their entire architecture around to a paradigm that Ruby had no mature ecosystem for at the time. Node.JS is much faster both because it has a fast VM, and because it has concurrency much better dealt with in its standard library than Ruby does (i.e. a proper event loop is built in and its use is idiomatic). That's not what the original blog post was about though, it was about how bad Gunicorn is. I've written Python, but never web apps (since Ruby's superiority there is obvious), but I don't think it's really fair to judge the whole language based on the use of some popular library that's shitty. I think it's symptomatic of Python that the library is shitty, but that's my dislike of Python shining through. The reality is that there are most likely great Python libraries for dealing with concurrent web requests, and she didn't bother to learn them. On Ruby the community would without hesitation recommend you to run Puma or Passenger or even Iodine or Falcon or whatever fancy stuff you have nowadays. These webservers all deal with concurrency, optimizing memory usage, load balancing and managing queues correctly. They don't have silly things like fork'ing before importing libraries, because good software architecture is something that's highly valued in the Ruby community.
- cakoose 7y ago> That's not what the original blog post was about though, it was about how bad Gunicorn is. Yes, the post starts out describing an issue with how Gunicorn listens for connections. Like you said, there are better libraries than Gunicorn so that's not a reason to jump to Ruby. However, the article goes on to talk about other things, like the lack of real multithreading, import-time code execution, and overall efficiency, all of which also apply to Ruby. I'm not saying Python or Ruby are a bad choice. It's just that MapleWalnut was soliciting an alternative that doesn't have the problems described in the original post, so Ruby doesn't qualify.
- dnautics 7y agoSo I repeated rachel's experiment with the tech stack that I use (elixir/phoenix). It's got a GC, a database adapter that really does a great job with validations and modeling, and at work I've replaced one django stack with it so far. Not sure if my machine is at all comparable (i5-8265U unplugged laptop CPU @ 1.60GHz) but also the phoenix hello world does a hell of a lot more, like setting session cookies and producing a full front page that renders through two templates. They also took a bit longer to get to in developer time. While you go get a website at :4000 with two commands -- five if you have to install elixir from scratch, it's not really representative of performance: I had to go to prod to disable the live code-reloader and debug/info level logs, disable SSL (releasing to prod defaults to SSL-enabled) and perform a release (elixir these days really wants you to have devops hygeine). 1) I was pretty pleased with the performance. (1900 rps in the base case, 2600 rps in the keepalive case) 2) Unlike Rachel's platform, Elixir is better with concurrency out of the gate (went up to 7800 rps with -c 100), which tells me, the erlang VM is really doing something right. 3) Paying for the cost of having correctly implemented, difficult things like sessions and no XSS would be worth a massive performance hit, IMO. 4) Doing the right thing with Elixir is crazy easy, there's way fewer footguns than python, and code is typically extremely well documented and tested, and inspiring enough to make you want to document and test (though there are fewer libraries).
- jacob019 7y agoIn my experience gevent is great, gunicorn not so much. Gevent has a built in HTTP server, it's fantastic. Just take care to avoid CPU bound or blocking code. I use PyMySQL instead of MySQLdb. If speed is a problem then use PyPy. Last summer I spent a month optimizing and updating our public facing django website, getting rid of gunicorn was a big improvement.
- anonymoushn 7y agoDoing great with Openresty over here.
- _bxg1 7y agoI come from outside of the Python ecosystem, so I'm not familiar with the specific frameworks mentioned (I read the original post too). Can someone simplify the argument that she's making about them? Something to do with maintaining separate, blocking threads in anticipation of requests vs spinning things up as you need them? Or is it a criticism of dynamic languages as HTTP servers in general (it's not clear what language was used in this article)?
- hedora 7y agoIf you stand up a python json rpc server (using whatever libraries are mentioned at the top of the article) it will be nowhere near the untuned numbers. I’d guess more than an order of magnitude off, based on experience with similar python stacks. If you tune and use a performance oriented language (on a big multicore machine), you can probably get about 100x better than the untuned not-python throughputs. So, for http hello world, 1000 untuned python servers ~= one beefy server.
- _bxg1 7y agoWhat I don't understand is what the "untuned" case here is. You could stand up a Python JSON RPC server and simply "not tune it". That would be "untuned code". Presumably that isn't what's being described. Is this some C code written by the author? Is it Apache? Something else? Is the point just how much faster native code is than Python?
- wmf 7y agoI'm guessing the "untuned" code here is C or some other statically-typed compiled language and it's not using any "framework". I guess the point is that straight-line Python code might be 10x-20x slower than C but an inefficient architecture can be 100x-1000x slower while being more complex than a good architecture.
- hedora 7y agoThe article goes out of its way to avoid describing the “untuned” setup, though the numbers look about right for a single threaded program without piles of frameworks and serializers in the way. Apparently someone else also built an untuned setup from scratch, and got better numbers. I think the point of the article is that if the results are surprising to you, then you should try writing one too.
- bufferoverflow 7y agoLanguage / framework and DB choice does matter for performance. A lot. https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=db https://www.techempower.com/benchmarks/#section=data-r18&hw=... As you can see, the fastest Python implementation is just 11% of the top solution in Rust. And your key/value store choice matters a lot. Redis is well known, but it's much slower than Tarantool or LMDB. Which is slower than RocksDB or Aerospike (though take this claim with a grain of salt, they can perform differently under different loads (writes, reads, updates, deletes) and different number of concurrent requests).
- enitihas 7y agoRedis is slower than LMDB? This sounds very hard to believe, since redis operates purely in memory, and all the data structures are designed with that in mind. LMDB does disk writes for every key value set operation. So I find it hard to believe redis is slower than LMDB
- james_s_tayler 7y agoRedis writes it's replication log to disk.
- enitihas 7y agoThat happens on a configured time interval, not on every request unlike lmdb.
- kjeetgill 7y agoI don't know for sure, but I'd imagine it's dominated by network. Which kinda the point of the article: benchmarks need detail and context to interpret well.
- kiadimoondi 7y agoSome of those DBs solve different use cases than others. I wouldn't use Redis as an embedded DB (unless benchmarking indicated it fit my needs better), as the user of that software benefits most in using it with multi-machine access in mind. Embedded use cases are where RocksDB, LMDB, TokyoDB/KyotoDB, SQLite, etc. come into play. Keeping the use case in mind (and it's possible evolution) will help pick the best tool for the job.
- thedance 7y agoFWIW, as a Xoogler, I find I can get pretty far with 1) abseil, 2) grpc, 3) protobuf, and 4) groping around in every open source Google project to find the thing I need (for example, until recently, Cord could be found in random projects but not in abseil). I don't know what it's like for ex-bookfaces but this gets me a good way to my goal, and as a bonus I get to use stuff I wasn't able to at G, like C++17, or io_uring, or even jemalloc.
- enitihas 7y agoOutside of google, what other C++ libraries do you think help in writing services in C++. C++ lacks a huge amount of libraries compared to something like java, e.g, I can't even find a half decent http client which handles all the important stuff.
- joana035 7y agoThis looks like a good opportunity to make the existing ones a bit better :-)
- thedance 7y agoI don't know. I'd be very squicked out to expose a C++ HTTP client to the big bad world, i.e. as a web crawler. For private use, technically speaking gRPC contains an HTTP client. More likely I'd fork a process in a safer language and direct it to make HTTP calls.
- hermitdev 7y agoBoost Beast seems to be pretty robust. Personally, I'd feel fine using it. I know the author Vinnie is very active with the community and very receptive and responsive to bug reports.
- hermitdev 7y agoI think boost.org is the first place to stop. Boost is often used as a proving ground for future standard libs. The guideline support lib [0] is also another tool. ACE [1] is also another (older) framework that adds a lot. I try and avoid ACE, despite having contributed, there may still be value add, depending on your need. I've also seen interesting things from Abseil and Folly. [0] https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines [1]https://www.dre.vanderbilt.edu/~schmidt/ACE.html https://www.dre.vanderbilt.edu/~schmidt/ACE.html
- nartz 7y agoThanks for this - but its the age-old argument of dynamic language vs static/compiled language mixed with hey bloated libraries! and hey bloated features! etc. 0. Performance isn't everything, especially to a lot of companies where having something at all is more important than just being the fastest 1. Dynamic languages are generally require less LOC, which generally equates to faster implementations 2. Rails, Django, <your favorite dynamic language framework> sure are generally bloated, but being able to drop in a library for nearly anything you need cannot be overlooked. Especially for small-midsize companies, spinning up another app server is generally easier than writing a whole bunch of multi-threaded code. 3. The appserver is generally not the bottleneck, rather, of course, its the database.
- kiadimoondi 7y agoExactly. And even then, database choice faces some of the same selection criteria. What's better supported, does it have well tested and documented client implementations (or do I have the time to write it myself), what features do I need, what features should I anticipate needing, is the performance good enough for what I plan on doing.
- jchrisa 7y agoIf you want multi-datacenter consistency, then the best of transaction protocols will still measure latency in terms of the speed of light multiplied by the distance between datacenters.
- saber6 7y agoCan you list examples? Most latency sensitive stuff I’ve worked on was RTT-based probes - however in one special case, it was one way times derived via synced clocks.
- deleted 7y ago[deleted]
- lmilcin 7y ago> Dynamic languages are generally require less LOC, which generally equates to faster implementations That's not what I observed. My understanding is dynamic languages promote people to think more higher level and care less about performance and program structure. While dynamic constructs can be efficient, the freedom they give is often misused by developers to create baroque structures that hurt performance mainly because developers don't know how to use them efficiently.
- j88439h84 7y agoAs mentioned last time, The modern Python async frameworks like Starlette would have no trouble handling this amount of traffic. https://www.starlette.io/ https://www.starlette.io/
- derwiki 7y agoExciting! I've never heard of Starlette. I set up example.py and ran the same `ab` commands mentioned in this post and got around 2600 RPS. I fresh started the server and threw 100k requests at it with `ab -c 100 -n 100000`, which timed out: apr_socket_recv: Operation timed out (60) Total of 49189 requests completed Same thing when omit `-c 100`. Am I doing something wrong?
- j88439h84 7y agoNot sure, I can't reproduce the timeout. I'm seeing "5829.85 [#/sec] (mean)" on my (nearly 10-year-old) computer which is doing other stuff. Not too shabby, I think.
- derwiki 7y agoA friend helped me figure it out: https://danielmendel.github.io/blog/2013/04/07/benchmarkers-beware-the-ephemeral-port-limit/ https://danielmendel.github.io/blog/2013/04/07/benchmarkers-...
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- samkidman 7y agoI tried this out with unicorn and this minimal config.ru which I believe replicates the author's setup: class TestApp def self.call(env) [200, [], ['Blah']] end end run TestApp ab -n 10000 http://localhost:8080/ http://localhost:8080/ --> ~1900 RPS (~8400 RPS) ab -k -n 10000 http://localhost:8080/ http://localhost:8080/ --> ~1900 RPS (~26000 RPS) ab -c 100 -n 10000 http://localhost:8080/ http://localhost:8080/ --> ~2500 RPS (~14500 RPS) I have no idea why it performed better on the concurrent benchmark. This was on my 2.3GHz i5 mac book pro. I think about 1/5th the performance of something closer to the machine is quite decent.
- jiofih 7y agoWith the machine she used, there are dozens of options. Node, go, Java, rust, crystal, h2o, will all easily outperform those numbers while offering a ton of features and robust ecosystems. Anyone can cook up a plain http server, making a real application out of it is the other 98% of the work.
- derwiki 7y agoI tried running as simple an HTTP server as possible with go: package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Foo") }) http.ListenAndServe(":8088", nil) } and when I run it with `ab -c 100 -n 100000`, it falls over before 10k requests: $ ab -c 100 -n 100000 http://127.0.0.1:8088/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) apr_socket_recv: Operation timed out (60) Total of 6388 requests completed I'm extremely new to go so maybe I'm doing something wrong. Could someone help me understand?
- jiofih 7y agoYou probably need to increase max open sockets using `ulimit`. Plus ab kinda sucks, use wrk instead.
- derwiki 7y agoAhh this explains it: https://danielmendel.github.io/blog/2013/04/07/benchmarkers-beware-the-ephemeral-port-limit/ https://danielmendel.github.io/blog/2013/04/07/benchmarkers-...