9 ms·
Build an Elixir Redis Server that’s faster than HTTP
- anonymousDan 5y agoAs I understand it its just a way of building a generic service using the Redis protocols instead of HTTP. For example if you have a microservice architecture where services currently communicate over HTTP, you could potentially replace it with the Redis protocol. What I don't understand is how it compares to other rpc protocol formats (e.g. protocolbuffers).
- hansonkd13 5y agoAuthor here, combining Redis protocol and MsgPack is a much more platform independent way of building a protocol. The redis protocol and Msppack are both only a a hundred lines or so for a parser. Meaning you can build your own from scratch in a new language if one isn’t supported. Its also stupid fast. Compared to protocol buffers which can be extremely complicated to grok on the binary level. I built a more robust API RPC for Python here based on Redis and MsgPack: https://github.com/hansonkd/tino https://github.com/hansonkd/tino
- __turbobrew__ 5y agoDid you consider ASN.1 for the protocol?
- e12e 5y agoLol. No offense, but I did a report in uni comparing a few alternatives (asn.1, json/xml over http, protobuf v1 (it's a while ago). And on one hand, sure asn.1 exists, on the other there's been some issues in for example nfs. I don't think you want asn.1 today - but maybe cap'n'proto. But i think redis is probably an interesting approach. Not sure how many of these beyond whatever/http2 have a sane pipelining+authenticated encryption story? I suppose you could "dictate" security at the ip level via vpn/wireguard or something. Or use Unix sockets.
- __turbobrew__ 5y agoWhat are the problems with ASN.1? It seems to work in 5G and LTE just fine? ASN.1 is simply a way to have portable data structures — are your problems with ASN.1 that it cannot effectively implement portable data structures or is your problem with the systems that have been built upon ASN.1?
- e12e 5y agoI seem to recall the issue was with the tooling around generating clients and servers and preserving type information - but it's been almost 20 years and I don't have my notes on hand. I also think there were some nfs security issues relating to asn.1
- e12e 5y agoMight have been something along the lines of this: https://www.infosecmatter.com/metasploit-module-library/?mm=auxiliary/dos/freebsd/nfsd/nfsd_mount https://www.infosecmatter.com/metasploit-module-library/?mm=... I guess that (and related errors in openssl and bouncy castle) might not be due to problems with asn.1 per se, but rather difficulty in writing safe libraries for parsing in C.
- jammycrisp 5y agoNo use in changing things if it's working for you, but you might be interested in trying out msgspec (https://jcristharif.com/msgspec/ https://jcristharif.com/msgspec/) in tino instead of using msgpack-python. The msgpack encoder/decoder is faster, and it comes with structured type validation (like pydantic) for no extra overhead (https://jcristharif.com/msgspec/benchmarks.html https://jcristharif.com/msgspec/benchmarks.html).
- wallyqs 5y agoNATS has a similar protocol to Redis and optimized for this use cause of doing pub/sub based low latency Request/Response instead of HTTP. The payload is opaque so you can use msgpack if needed, and the protocol also supports sending headers now: https://docs.nats.io/nats-protocol/nats-protocol#protocol-messages https://docs.nats.io/nats-protocol/nats-protocol#protocol-me... (disclaimer: I'm one of the NATS maintainers :) )
- jeffbee 5y ago> extremely complicated to grok on the binary level. That seems like a weird claim. Protobuf on the wire only has four types: short fixed-length values, long fixed-length values, variable-length values with continuation-bit encoding, and length-prefixed values, where the length is continuation-bit encoded. Msgpack has 37 different wire types!
- dljsjr 5y agoIf the idea is to leverage the Redis protocol for an “API”, what are the benefits to this approach over using the built-in `pub-sub` and the regular old C implementation of Redis Server? I could maybe see some really specific use-cases for this but for probably 90% of cases implementing a distributed process API should be able to go over pubsub fine shouldn’t it? Or does pubsub have some sort of massive overhead? EDIT: Just because it’s tangential to the topic at hand, and since I’m up here at the top, I also wanna throw out a nod to libraries like `nng` and `nanomsg` which are spiritual successors to ZeroMQ and they have functionality like brokerless RPC/pubsub built in as messaging models. I don’t see tools like this talked about a lot in this space cuz systems software isn’t the sexiest but if you need to embed a small and lightweight messaging endpoint in your backend stuff then look at those as well. No horse in the race, just like sharing useful tools with people. https://nng.nanomsg.org https://nng.nanomsg.org
- mst 5y agoIf it's straight RPC there's no need for the extra moving parts, and the simpler your topology the fewer ways for it to go wrong. I often build TPC based "protocols" that are just newline delimited json in each direction, it's a nice middle ground between using an HTTP POST and something like gRPC.
- dljsjr 5y agoI’m not 100% sure I’m understanding but if the idea is to implement a “traditional”/synchronous RPC mechanism you can do that using the PubSub functionality trivially and I’m not sure which additional moving parts it adds unless you’re in a situation where you have to cluster Redis (which has a reputation for being a PITA but isn’t that hard honestly). The only use case that really jumps out is when you don’t want a broker because you don’t want a single point of failure but now you’re embedding a Redis server implementation in every one of the services in your mesh and I’m not convinced that’s much better but I can see where it might be helpful.
- anonymousDan 5y agoI don't think this is anything to do with Redis server, you are simply using the Redis wire protocol/parser? I could be wrong...
- VWWHFSfQ 5y agoIt seems like kind of a fun hack but I'm not sure what this is getting you. Is this some kind of an RPC-style thing built on Redis? And I'm not sure where the comparison to HTTP is coming from. I have a lot of questions.
- secondcoming 5y agoIt's making the correct argument that handling HTTP is costly, and you don't always need it. People use it because it's really easy to get something up and running with HTTP short term, but long term there are better ways of doing server-server communication.
- hansonkd13 5y agoAuthor here. EXACTLY. I’m showing that it is just as easy to use a different protocol than HTTP. Instead of reaching for an HTTP framework to do RPC, I’m showing that Redis can be just as easy.
- handrous 5y agoYou're doing God's work. Anything reminding people that there's more to the Internet than the Web is a step in the right direction.
- VWWHFSfQ 5y ago> People use it because it's really easy to get something up and running with HTTP short term And because there are decades of best-practices and standard ways to do very common things like authentication, caching, signaling errors, load-balancing, logging. Maybe a homemade RPC doesn't need any of that, but I would bet that at some point it will need at least some of it. And now you have to implement it all yourself. This is cool though and kinda fun to think about!
- e12e 5y ago> decades of best-practices and standard ways to do (...) caching Essentially either you want RPC, or you might want caching. If you want to put a varnish cache in there, probably stick with http. If you know you're doing (very high thro / low latency) RPC - this might make a lot of sense.
- nathell 5y agoTangentially related: I wish more protocols used netstrings [0] as transport. This would trivially eliminate a whole class of bugs that are all too easy to commit, especially in low-level languages. [0]: https://cr.yp.to/proto/netstrings.txt https://cr.yp.to/proto/netstrings.txt
- deleted 5y ago[deleted]
- latch 5y agoIn Elixir / Erlang, you can put a socket in either active or passive mode. In passive mode, you need to explicitly call recv/2 or recv/3 to get data, very similar to a traditional socket API. This is what this code appears to be doing. But if you want better performance, you use active mode. In active mode, the runtime is receiving data on your behalf, as fast as possible, and sending it to the socket's owning process (think a goroutine) just as fast. Data is often waiting there for you, not just already in user space, but in your Elixir process's mailbox. (Also, this doesn't block your process the way recv/2 does, so you could handle other messages to this process.) You could imagine doing something similar with 2 goroutines and a channel. Where 1 goroutine is constantly recv from the socket and writing to a buffered channel, and the other is processing the data. One problem with active mode, and to some degree how messages work in Elixir in general, is that there's no back pressure. Messages can accumulate much faster than you're able to process them. So, instead of active mode, you can use "once" mode or {active, N} mode (once being like {active, 1}. In these modes you get N messages sent to you, at which point it turns into passive mode so you can recv manually. You can put the socket into active/once mode at any point in the future.
- spockz 5y agoYou could implement backpressure by either adding a buffer (which you already have here) or by rejecting requests optionally with more data on how hard and long to back off.
- jadbox 5y agoIf you can tolerate (debounced) data loss in the buffer, a ring buffer works really well with predictable memory and performance.
- new_stranger 5y agoSince you relate this example to Go, would you mind sharing thoughts about how heavy I/O from network sockets compares between the two or gotchas that might not be apparent to Erlang/Go developers about the other?
- alberth 5y agohttps://stressgrid.com/blog/webserver_benchmark/ https://stressgrid.com/blog/webserver_benchmark/ These benchmarks, while dated and haven't been tested with the newly released updates to address these dynamics - might interest some folks.
- tayo42 5y agoSomething seems wrong here. Over 1second on average to handle 1k http requests? I'm not sure the conclusion is valid. The test seems very wrong. Http being slow seems like a wild thing to say. Apache can serve that much
- jeremyjh 5y agoThere is no way to get around the fact that the HTTP protocol adds overhead (serialization and parsing of additional data) at the application layer. Binary RPC protocols are faster, and that is why Redis uses one of those instead of HTTP. There is no debate about that, it is simply a set of trade-offs that balances out differently for different use cases.
- tayo42 5y agoYour confused too, resp is a text protocol. So is http. This isn't binary vs text protocol expermint. None of the http parsing should take seconds like this suggests.a simple protocol might be faster to handle but another post down below makes a similar point where something implented well can respond is micro seconds. Really all this shows is that handling text off a socket is faster then this http framework in eleixr
- jeremyjh 5y agoRESP can transfer raw binaries, the only overhead is a message length. In the TFA they are talking about using MessagePack for the payloads, so it is a binary protocol.
- tayo42 5y agohttp transfers binary, what does that even mean? the only thing the message pack thing is used for is encoding the value in the requests. but all of that is irrelevant because thats not what i was talking about in the first place. the test is poorly done. An average latency of 17ms to handle a simple request with the resp protocol is a red flag it self that something is wrong. Seconds to have an http response returned is a problem. All this article is showing is elixir and the libraries used are incredibly slow. Calling HTTP slow, based off the tests done in this article is just wrong, because the whole test is slow and not testing the right thing. Micro bench marking of the parsing functions would be the right thing to do here. really someone should profile the server and see what is taking seconds to respond. it wont be the parsing related functions parsing http really isnt that big of a deal. its a simple protocol, the action on line one, headers split by : and the body. resp has a simmilar amount of work too, uses multiple lines
- jeffbee 5y agoSeems like the author should have stopped when they concluded that it takes 2 seconds to serve an HTTP request before publishing this and embarrassing themselves. gRPC C++ serves HTTP/2 in less than 120 microseconds, which includes server-to-server network flight time. https://performance-dot-grpc-testing.appspot.com/explore?dashboard=5180705743044608 https://performance-dot-grpc-testing.appspot.com/explore?das...
- sb8244 5y agoIt's 1000 iterations in 2 seconds. That may be including connection setup time as well, I didn't dive into whether there's keepalive or not.
- lpgauth 5y agoI’m sorry but this benchmark is not testing the underlining protocol but the clients themselves (pooling mechanism, pipelining, keep-alive, etc). The Redis protocol is probably faster to parse than HTTP but not a 100x factor.
- thamer 5y agoUsing the Redis protocol doesn't seem like the most efficient way to implement a fast client/server architecture. The Redis protocol[1] is text-based, where each command is an array with a header describing the number of elements in the command, and each element is prefixed by `$` and its length, with abundant \r\n to separate all those (this is a simplification, but almost all commands sent to Redis follow this array format). For example, to send "SET my-key hello" to Redis, this is a 3-element array (the initial *3) with elements of length 3, 6, and 5. So you'd send: *3\r\n $3\r\n SET\r\n $6\r\n my-key\r\n $5\r\n hello\r\n (^ all of those would be joined together, this is presented on multiple lines for readability) Decoding lengths from their textual representation is inefficient in both space and time, and the various \r\n add to the request and response sizes without adding anything useful to a program that uses it (it is of course useful for humans typing those commands, but who does that?). Even using something as common as protobuf would likely be more efficient, with smaller requests and responses – although object creation can lead to memory pressure for complex messages. But really, since Elixir leverages Erlang, why not have a basic common protocol with pattern matching on binaries? This would make a lot more sense than using a text-based protocol, in my opinion. [1] https://redis.io/topics/protocol https://redis.io/topics/protocol
- eternalban 5y agoOne of the things, among many then unique features, that charmed me about Redis when I first encountered it when it first surfaced (2008?) was the ability to open a telnet session to Redis and type commands see the responses. It also hugely helped in writing drivers for it. As to your intuition (“seems”), the fact is that Redis is a very high performance server. I think the key is considering what fraction of time of servicing a request is spent on parsing the requests — given the network hop and other non-protocol computations, it is likely to be negligible.
- latch 5y agoI think Redis would have been successful even without a text-heavy protocol. Redis took a middle ground and [ascii]-length prefixed responses (though I don't think it was initially like this). That's a reasonable and likely necessary compromise. A bit similar to how HTTP can include a content-length. You'd hate to be scanning large payloads for that trailing \r\n. So Redis terminates with \r\n (which isn't strictly necessary for all message types) and ascii encodes the types and length prefix. Everything else is just bytes. Here's (1) a paper that looked at text vs binary protocols. They looked at it in the context of MonetDB though, which has, by far, the worse text-based protocol I've ever seen (so much so that even their official drivers have (had?) plenty of protocol-related bugs) (1) - https://15721.courses.cs.cmu.edu/spring2018/papers/14-networking/p1022-muehleisen.pdf https://15721.courses.cs.cmu.edu/spring2018/papers/14-networ...
- brhsagain 5y agoAt first I was wondering, wait, then how do you talk to the server from the browser? ...Wait, if you control both the client and server, why even use HTTP? I feel like there's this distressing trend where web programmers export their practices everywhere, and over time, those become the new accepted "primitives" instead of the actual primitives. What used to be a socket is now apparently a whole black-box protocol, where swapping one protocol with another to get speed gains is now noteworthy, and what used to be just FFI or a shared library is now a whole client/server architecture. This is basically the intellectual lineage of stuff like "language servers" where every keystroke your editor heap allocates a JSON tree, serializes it, initiates an HTTP connection, sends it over, reads the response, deserializes it into more heap allocated nodes, etc.... when the actual stated goal (making code intelligence consumable by any client) could be achieved using much less. Standby as I follow up on this with my own post, "Build a TCP server that's faster than the Redis protocol."
- Kaze404 5y agoThank god I have a machine manufactured in the last decade, or else that would be a problem.
- brhsagain 5y agoOkay, but then why optimize at all? Computers are fast, why not just run HTTP everywhere? What’s the point of the article? My Mac mini runs at three whole billion clock cycles per second. If we’re going to depend on hardware to make our argument, we’re all basically running supercomputers. And yet everything is slow, and my tools lag for no reason, the web in general is slow. Which is all fine, but I thought computers were supposed to be fast? Somehow we’ve arrived at a point where on the one hand, computers are supposed to be fast enough to support these huge skyscrapers of abstraction, but on the other hand… the result is mind blowingly slow.
- blackoil 5y agoInstead of stereotyping and blaming someone, better to find cause in ecosystem. Except for last few years most non HTTP communication systems were complex, platform specific and with enterprisey bloat. May not be for this instance, but if people are gravitating towards a bad tooling alternative must be worse by some important parameter.
- trimbo 5y ago14ms for an echo server is pretty slow. 1.8s for an HTTP echo server of the same data... I don't even know how this can possibly be valid. Something's wrong, or I misunderstood the table.
- tardyp 5y agoIt looks like websocket+msgpack would have similar overhead once the initial initial overheard is passed. But you won't miss all the http reverse proxy ecosystem.
- giridharkannan 5y agoHow come HTTP be this slow? If it's slow, then the problem is on the library that we use for parsing HTTP and not HTTP protocol itself. In this case, both HTTP and Redis use TCP. If HTTP header and data fit in 1 MTU, there should be no difference. No way searching for 2 CRLF that too in 1 MTU data size be 100x slower