How ipdata serves 25M API calls from 10 infinitely scalable global endpoints for $150 a month - High Scalability -
Hey @jonathankosgei. Good stuff, I always love seeing performance enthusiasts.
There are a few small details that aren't included in your article that are really important if you care about performance and I'm not sure you're aware of these, so I'm sharing so everyone can learn:
First, you're missing this immensely important quote from the japronto Github readme:
They intentionally compared apples to oranges to skew the statistics. In fact, Go's fasthttp library is only 18% slower than the entirety of japronto while Go only runs a single worker.
If you use multiprocessing properly (built into Golang, super easy to enable), you get way higher performance than japronto (as you'd expect).
All the others in the benchmark can also handle themselves much better than what the japronto devs published.
Second, if you're making a call to redis for every visitor (before replying, and assuming only one call to redis), your actual throughput limit is less than 100k reqs/sec because you're now dependent on redis. This number is from a benchmark on a bigger instance than yours, but you can use
redis-benchmarkto figure out your number.Third, the japronto benchmark gets multiplied by a very high factor because it enables HTTP pipelining. Problem: none of the real world browsers support HTTP pipelining. Chrome used to have it but subsequently removed it and it's now unsupported.
And lastly, a personal tip: change your structure to use a message bus or queue. I use microservices written in Golang with NATS (https://nats.io/) for messaging. NATS handles over 10 million messages per second on my one server.
If it's an API where the client isn't waiting for a response, you can reply with 200 OK after validating the request with a web server, and then publish the task on the message queue. Any of the background workers (NATS can be clustered, and microservices can be hosted on any number of servers) then pick up the job and execute it - but your response was already sent, so response time will be blazing fast.
For requests where you need a response from the worker, NATS supports request/response, so that's also not a problem.
Anything in that infrastructure can be scaled both vertically and horizontally: load balancers, nginx servers in front of your Golang HTTP servers (I use nginx to deal with initial authentication like JWT and serving static assets because nginx is amazingly fast), background workers, NATS cluster, MySQL/MariaDB/... manual cluster.
I hope this is helpful to someone. I use this architecture to serve several hundred million requests per day on a € 50 server that's usually at 10% - 15% CPU, where each incoming request also relies on an external API (over HTTPS) and workers handle it in ~1ms.
(Note: I love this stuff. Really love it. But most of this isn't practical advice for IndieHackers or their startups, it's way overkill unless you're doing very niche things.)
Hey Sebastien! I love this stuff too! :)
I've gotten a lot of criticisms about the Japronto benchmarks. I found this exhaustive repo https://github.com/tbrand/which_is_the_fastest by a third party that seems to confirm the developer's claims. Would love to hear what you think about it :)
Thanks for sharing your setup, and for mentioning NATs, I'll definitely refer to your answer when I'm building on servers again (I'm serverless for the foreseeable future :).
My eventual architecture ended up paralleling yours, except I use Kinesis as a queue instead of NATs where I push all my logs and use Redis RQ workers to process the logs and update a count locally that's then replicated to the local caches of our global endpoints.
I couldn't get away with running on a single server :) to keep latencies low for users globally. And that's cheaper to do serverless.
Also, I'm curious what API you're running? I'd love to see what you're working on :)
One small point: that benchmark tests different routers instead of just the HTTP libraries. I personally use nginx for routing (and simultaneously load balancing) and my Golang receiver is a single handler without routing. Routing can quickly become expensive.
Here's an extensive Golang web framework benchmark that has better benchmarks (different processing time benchmarks, latency, allocations, different concurrency levels): https://github.com/smallnest/go-web-framework-benchmark
Keep in mind that japronto doesn't support HTTP/2 and japronto is only fast because it's actually written in C, wrapped with Python. Nothing wrong with that, but if you have to do any processing during your request you'll be using Python, which will be considerably slower than other languages.
This HN thread has a lot of good information: https://news.ycombinator.com/item?id=13539767
It's super niche and needs a lot of backstory, it's related to the open source repository I maintain (currently on break because the third party provider this project relies on is temporarily down): https://github.com/RocketMap/RocketMap/
Pretty cool project :)
Thanks for the links, fasthttp looks pretty good according to those benchmarks.
I have no strong feelings about japronto and am actually looking to learn go, especially since there seem to be substantial performance enhancements to be had.
You'll be running near C/C++ speeds on everything you do. Processing-heavy tasks are orders of magnitude faster than e.g. Python (assuming you didn't write a C library for it and are calling C via Python).
Here are a few things to get started: