How Fast can A Single Instance of Redis be?
docs.keydb.dev
docs.keydb.dev
Still, nice to see the modules API being put to interesting use cases.
I assume the AWS network is pretty noisy.
Years ago I built a layer on top of Redis that could also run in the browser, so I could cache data there. Even pull from other browsers with WebRTC, and fallback to server only when needed.
Over time, I eventually made the Redis component a module, so it could be swapped out with other systems. It has always proved to be the fastest, except for an LMDB adapter someone in the community wrote ( https://github.com/notabugio/gun-lmdb ).
(I'm probably being thick; the benchmark code is probably linked and I'm just not seeing it.)
I recently maintained some nodejs & expressjs stuff. Neither the Redis clients (or the original developers of our stuff) have any concept of backpressure (throttling). In our case, HTTP request received would cause a Redis request. For whatever reason, expressjs (or nodejs) event loop thingie processes Request preempt other stuff. So Redis responses would pile up in Redis itself, causing Redis to ABEND (out of memory).
Trying to explain backpressure, queueing, throttling to a bunch of junior devs who LOVE LOVE LOVE nodejs... Definitely was not my favorite gig.
--
PS- I started using Redis a few gigs ago. I really wanted to hate it. I'm primarily a Java dev and was esthetically offended by the NoSQL fad. But turns out Redis is awesome. And antirez is now a personal hero. I truly wish I was more like him.
I used the 'queue' (submodule?) from caolan's terrific 'async' module to limit the number of Redis requests in flight.
https://caolan.github.io/async/v3/docs.html#queue
https://www.npmjs.com/package/async
As said above, this prevents the Redis responses from piling up on the Redis server, waiting to be sent.
To troubleshoot: If the Redis CLIENT LIST's output length list, output memory, or both, are growing without ever shrinking, then I'd bet one delicious apple fritter your app's Redis client isn't processing responses fast enough. Check out the 'obl', 'oll', 'omem' stats.
https://redis.io/commands/client-list
--
Fronting Redis calls with async.queue makes the client code gnarly. So I banged out a (very minimal) client with an internal queue.
I had been weighing releasing it. And then if there's any interest, then maybe flesh out the client. There's some other notions I had wanted to try out.
I'm NOT a nodejs developer. So I hadn't fully worked out how to do proper I/O, with proper timeouts, retry, backoff. So for instance, I'd want the async.queue concurrency limit to be adaptive.
That should (in theory) be handled on layer 7, not really on the network end (beyond regular TCP flow control), whereas this article is mostly about optimization on the network layer.
If you're fiddling with redis to the point where you're optimizing to use DPDK/OpenOnload/Exablaze/etc you've probably already exhausted the typical optimization paths (redis pipelining, tuning your queries, potentially implementing a stripped down client library implementing RESP, etc).
But first I'll try to explain what I think I was seeing.
Nodejs & expressjs. A couple high use REST API endpoints. Thundering herds. HTTP server can handle say max 200 rps (while maintaining target P99). Redis server can only handle sustained 100 rps.
Where does the throttle go? How is it implemented?
In my prior Java experience, I got throttling "for free" by tuning the thread pools.
With nodejs & expressjs, being single threaded with an event loop, the only solution I figured out was to throttle our app's Redis client.
What I really wanted is end-to-end backpressure. What I've done in the past is a postfix (mail server) inspired queuing system (work piled up, no blocking). What I would have settled for is an expressjs (or equiv) 'frontdoor' that throttled new socket accepts and new HTTP requests. Extra credit if the app level load balancer was aware of this frontdoor, and therefore more responsive.
If there's some app agnostic throttle buildable at layer 7, I definitely want to learn about it.
And you have to use nginx plus to get the backpressure support on the ingest side. If my levels of capability and capacity were a little higher I’d be tempted to start writing a reverse proxy in Rust.
Just remember you need to give DPDK one whole NIC and it's using polling so 100% CPU usage on polling cores.
Nagle’s algorithm for the oldest, maybe a couple others, possibly some code I’ve written.
https://doc.dpdk.org/guides-16.04/sample_app_ug/l3_forward_p...
Valid question I'd say.
This is a Redis module, so will only work with Redis. Although I don't see its implementation (?), it appears to connect Redis' Unix Socket interface to a network stack running on DPDK (a user-space low-level network interface).
In SolarFlare world, ef_vi is their library to DPDK -- it is a packet buffer interface. They then have OpenOnload (transparent acceleration that cooperates well with the kernel) and TCPDirect, a proprietary userspace TCP/UDP library that has its own interface. It's even higher performance than OpenOnload because it doesn't have to coordinate with the kernel and you manage the sharing of the network resources.
SolarFlare has a DPDK driver too. OpenOnload doesn't accelerate UNIX sockets.
One thing this module doesn't accelerate is epoll... I think a properly tuned SolarFlare solution would be higher performing -- especially on the same machine with TCP-loopback acceleration. But you don't know until you try it...
edit: added note about epoll
https://community.mellanox.com/s/article/vma-improves-redis-...
Already tried using '-fno-omit-frame-pointer' and '-O0'
# $CMD is a command starting a redis-server and creating traffic
perf record --freq=10000 --all-cpus -g -- $CMD
perf script --input=perf.data | ./stackcollapse-perf.pl > out.perf-folded
./flamegraph.pl out.perf-folded > perf-redis.svg
perf record --call-graph dwarf,32768 -f 999 -- $CMD
if the following does not work or you work on something older than Haswell: perf record --call-graph lbr -f 999 -- $CMD
Be careful with the frequency. Use cycles:up as the event (with -e) for general cpu time, and other stuff like LLC-load-misses cycle_activity.stalls_l3_miss as an example on a Kaby Lake system. Use perf list
to search for the right event name. On the Broadwell i5/dualcore+HT Laptop I see cycle_activity.stalls_l2_miss as the equivalent, due to it apparently not having an L3 cache. cycle_activity.stalls_mem_any highlights code where the CPU is doing nothing while waiting on memory.For de-inlining I found simpleperf from the android-ndk to be the only tool not wastefully spawning one addr2line for each-single-address. Yes, that takes ages to process. Yes, I gave up and used simpleperf, which caches this. And yes, I considered patching perf-tools to use the pipe-based interface to addr2line.
Hotspot unfortunately appears unable to distinguish time spent in a function between the different inline stacks inside said function, so I had to forego heavy link-time-optimization that got 5-10% without much else, because there was no meaningful insight left into what part spend how long computing.
And please, please refrain from -O0 when you want performance. Either to use the performance or to measure it. Instead add -g or -ggdb in there, to force dwarf debug info to get line info and stack frame unwinding, the latter without relying on the frame pointer. Though, for the unwinding itself, lbr does a great job. Just keep in mind that it's max depth is limited by the CPU generation, and can't by bypassed/increased.
---
FWIW, the site is up for me.
Using 4 threads it is simple to get 2x the performances of single-threaded Redis (even if yet the reading part is not threaded).
That's not to say its' bad, but is probably seeing higher than normal traffic. I've been interested in static content generation again.
The site is a static HTML site generated by Docusaurus [0]. That seems like it should be very fast and lightweight for serving tons of concurrent requests.
In the page's response header, it shows that the server is NGINX on Ubuntu. Ahhh, and "X-Powered-By: Express". I suspect that's the bottleneck, serving static assets via Node.js+Express, instead of directly with NGINX.
I also see in the document foot that it tries to load a script from http://localhost:35729/livereload.js. That could be a sign that the site is a development build, not optimized for production..?
Redis is not a database. Let's begin with that.
”A database is an organized collection of data, generally stored and accessed electronically from a computer system. Where databases are more complex they are often developed using formal design and modeling techniques." [1]
...which makes Redis sound like a database to me. If I am not technically correct, feel to educate me.
Side note: I try to focus on what problems we're trying to solve, because it's hard enough to get people to sync up on that. I've found focusing on being right is inversely proportional to the health of my relationships.
One thing I think that filesystems can learn from databases is the notion of a compound primary key. It would be neat if app-files were identified by an (app, type, id) tuple. This would bring the advantages of both the posix and the windows filesystem layouts.
For instance if we had(app=firefox, type=/usr/bin, id=main). Then we could easily find all firefox files by querying by app. Or we could easily find all binaries in PATH by querying by type.
ps. I think this would work better than the overly general tag-based filesystem people sometimes propose.
We can model that in a filesystem as well, of course. So if I want one filtering by file type and one filtering by month of creation, then I can create /files/$program/$file and then ln -s /files/$program/$file /files/month/$month/$file
Absolutely
So if not a database, what is it?